Vue中Echarts地图+散点图,并且通过WebSocket即时通讯更新

先看下效果图
在这里插入图片描述
先看下后台返回的数据结构是什么样子的

[
  {
    "name":"黄金用户",
    "children":[
      {
        "name":"武汉",
        "value":[114.31,30.52]
      },
      {
        "name":"丹东",
        "value":[124.31,40.52]
      },
      {
        "name":"张家口",
        "value":[114.81,40.13]
      },
      {
        "name":"深圳",
        "value":[114.07,22.52]
      }
    ]
  },
  {
    "name":"白金用户",
    "children":[
      {
        "name":"金华",
        "value":[119.31,29.52]
      },
      {
        "name":"西安",
        "value":[108.31,34.52]
      }
    ]
  },
  {
    "name":"钻石用户",
    "children":[
      {
        "name":"成都",
        "value":[104.31,30.52]
      }
    ]
  }
]

好了,开始实现前端的代码

html

<div class="com-page">
 <div class="com-container" @dblclick="revertMap">
<div class="com-chart" ref="map_ref"></div>
 </div>
</div>

css

html,body,#app{
  width: 100%;
  height: 100%;
  padding: 0;
  margin: 0;
  overflow: hidden;
}
.com-page {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.com-container {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
}

.com-chart {
  width: 100%;
  height: 100%;
  overflow: hidden;
}

导入一个省份的js文件,里面有json数据

const name2pinyin = {
  安徽: 'anhui',
  陕西: 'shanxi1',
  澳门: 'aomen',
  北京: 'beijing',
  重庆: 'chongqing',
  福建: 'fujian',
  甘肃: 'gansu',
  广东: 'guangdong',
  广西: 'guangxi',
  贵州: 'guizhou',
  海南: 'hainan',
  河北: 'hebei',
  黑龙江: 'heilongjiang',
  河南: 'henan',
  湖北: 'hubei',
  湖南: 'hunan',
  江苏: 'jiangsu',
  江西: 'jiangxi',
  吉林: 'jilin',
  辽宁: 'liaoning',
  内蒙古: 'neimenggu',
  宁夏: 'ningxia',
  青海: 'qinghai',
  山东: 'shandong',
  上海: 'shanghai',
  山西: 'shanxi',
  四川: 'sichuan',
  台湾: 'taiwan',
  天津: 'tianjin',
  香港: 'xianggang',
  新疆: 'xinjiang',
  西藏: 'xizang',
  云南: 'yunnan',
  浙江: 'zhejiang'
}

export function getProvinceMapInfo (arg) {
  const path = `/static/map/province/${name2pinyin[arg]}.json`
  return {
    key: name2pinyin[arg],
    path: path
  }
}

data

data() {
return {
chartInstance: null, //初始化echartInstance对象
allData: null, //接收的后台数据
mapData: {}, // 所获取的省份的地图矢量数据
};
},

methods

initChart方法

 async initChart() {
     //初始化echartInstance对象
     //chalk是我们定义的主题,echarts官方有案例,怎么使用可以百度一下,不喜欢可以直接删掉
      this.chartInstance = this.$echarts.init(this.$refs.map_ref, "chalk");
      //获取中国地图的矢量数据
      const res = await axios.get(
        "http://localhost:8080/static/map/china.json"
      );
      this.$echarts.registerMap("china", res.data);
      const initOption = {
        title: {
          text: "▎ 商家分布",
          left: 20,
          top: 20,
        },
        geo: {
          type: "map",
          map: "china",
          top: "5%",
          bottom: "5%",
          itemStyle: {
            areaColor: "#CC0000",
            borderColor: "#333",
          },
        },
        legend: {
          left: "5%",
          bottom: "5%",
          orient: "vertical",
        },
      };
      this.chartInstance.setOption(initOption);
      this.chartInstance.on('click', async arg=>{
          //arg.name 得到所点击的省份
           const provinceInfo = getProvinceMapInfo(arg.name)
        // 需要获取这个省份的地图矢量数据
        // 判断当前所点击的这个省份的地图矢量数据在mapData中是否存在
        if (!this.mapData[provinceInfo.key]) {
          const ret = await axios.get('http://localhost:8080' + provinceInfo.path)
          this.mapData[provinceInfo.key] = ret.data
          this.$echarts.registerMap(provinceInfo.key, ret.data)
        }
        const changeOption = {
          geo: {
            map: provinceInfo.key
          }
        }
        this.chartInstance.setOption(changeOption)
      })
    },

getData方法

这里还是用http请求获取的数据,后面我再讲怎么用WebSocket获取我们的数据

async getData() {
      // 获取服务器的数据, 对this.allData进行赋值之后, 调用updateChart方法更新图表
      const { data: res } = await this.$http.get("map");
      this.allData = res;
      this.updateChart();
    },

updateChart方法

 updateChart() {
      //处理图表需要的数据
      // 图例的数据
      const legendArr = this.allData.map((item) => {
        return item.name;
      });

      const seriesArr = this.allData.map((item) => {
        // return的这个对象就代表的是一个类别下的所有散点数据
        // 如果想在地图中显示散点的数据, 我们需要给散点的图表增加一个配置, coordinateSystem:geo
        return {
          type: "effectScatter",
          rippleEffect: {
            scale: 5,
            brushType: "stroke",
          },
          name: item.name,
          data: item.children,
          coordinateSystem: "geo",
        };
      });
      const dataOption = {
        legend: {
          data: legendArr,
        },
        series: seriesArr,
      };
      this.chartInstance.setOption(dataOption);
    },

screenAdapter方法

//适配屏幕
 screenAdapter() {
      const titleFontSize = (this.$refs.map_ref.offsetWidth / 100) * 3.6;
      const adapterOption = {
        title: {
          textStyle: {
            fontSize: titleFontSize,
          },
        },
        legend: {
          itemWidth: titleFontSize / 2,
          itemHeight: titleFontSize / 2,
          itemGap: titleFontSize / 2,
          textStyle: {
            fontSize: titleFontSize / 2,
          },
        },
      };
      this.chartInstance.setOption(adapterOption);
      this.chartInstance.resize();
    },

revertMap方法

// 回到中国地图
    revertMap () {
      const revertOption = {
        geo: {
          map: 'china'
        }
      }
      this.chartInstance.setOption(revertOption)
    }

computed

selectTypes () {
      if (!this.allData) {
        return []
      } else {
        return this.allData.type.filter(item => {
          return item.key !== this.choiceType
        })
      }
    },
    showTitle () {
      if (!this.allData) {
        return ''
      } else {
        return this.allData[this.choiceType].title
      }
    },
    // 设置给标题的样式
    comStyle () {
      return {
        fontSize: this.titleFontSize + 'px'
      }
    },
    marginStyle () {
      return {
        marginLeft: (this.titleFontSize+10) + 'px'
      }
    }

mounted

  mounted() {
    this.initChart()
    this.getData()
    window.addEventListener('resize', this.screenAdapter)
    this.screenAdapter()
  },

destroyed

  destroyed() {
    window.removeEventListener('resize', this.screenAdapter)
  },

好了,完事,下面我把如何用WebSocket获取数据说一下

封装了一个WebSocket

export default class SocketService {
  /**
   * 单例
   */
  static instance = null
  static get Instance() {
    if (!this.instance) {
      this.instance = new SocketService()
    }
    return this.instance
  }

  // 和服务端连接的socket对象
  ws = null

  // 存储回调函数
  callBackMapping = {}

  // 标识是否连接成功
  connected = false

  // 记录重试的次数
  sendRetryCount = 0

  // 重新连接尝试的次数
  connectRetryCount = 0

  //  定义连接服务器的方法
  connect() {
    // 连接服务器
    if (!window.WebSocket) {
      return console.log('您的浏览器不支持WebSocket')
    }
    this.ws = new WebSocket('ws://localhost:9998')

    // 连接成功的事件
    this.ws.onopen = () => {
      console.log('连接服务端成功了')
      this.connected = true
      // 重置重新连接的次数
      this.connectRetryCount = 0
    }
    // 1.连接服务端失败
    // 2.当连接成功之后, 服务器关闭的情况
    this.ws.onclose = () => {
      console.log('连接服务端失败')
      this.connected = false
      this.connectRetryCount++
      setTimeout(() => {
        this.connect()
      }, 500 * this.connectRetryCount)
    }
    // 得到服务端发送过来的数据
    this.ws.onmessage = msg => {
      console.log('从服务端获取到了数据')
      // 真正服务端发送过来的原始数据时在msg中的data字段
      // console.log(msg.data)
      const recvData = JSON.parse(msg.data)
      const socketType = recvData.socketType
      // 判断回调函数是否存在
      if (this.callBackMapping[socketType]) {
        const action = recvData.action
        if (action === 'getData') {
          const realData = JSON.parse(recvData.data)
          this.callBackMapping[socketType].call(this, realData)
        } else if (action === 'fullScreen') {
          this.callBackMapping[socketType].call(this, recvData)
        } else if (action === 'themeChange') {
          this.callBackMapping[socketType].call(this, recvData)
        }
      }
    }
  }

  // 回调函数的注册
  registerCallBack (socketType, callBack) {
    this.callBackMapping[socketType] = callBack
  }

  // 取消某一个回调函数
  unRegisterCallBack (socketType) {
    this.callBackMapping[socketType] = null
  }

  // 发送数据的方法
  send (data) {
    // 判断此时此刻有没有连接成功
    if (this.connected) {
      this.sendRetryCount = 0
      this.ws.send(JSON.stringify(data))
    } else {
      this.sendRetryCount++
      setTimeout(() => {
        this.send(data)
      }, this.sendRetryCount * 500)
    }
  }
}

在main.js中进行连接,挂载原型

//对服务端进行连接
import SocketService from '../utils/socket_service'
SocketService.Instance.connect()
// 其他的组件  this.$socket
Vue.prototype.$socket = SocketService.Instance

然后在组件中

  created() {
    //在组件创建完成之后进行回调函数注册
    this.$socket.registerCallBack('trendData',this.getData)
  },
  
   mounted() {
    this.initChart();
    //发送数据给服务器,告诉服务器,我现在需要数据
    this.$socket.send({
      action:'getData',
      socketType:'trendData',
      chartName:'trend',
      value:''
    })
    window.addEventListener("resize", this.screenAdapter);
    this.screenAdapter();
  },
  
  destroyed() {
    window.removeEventListener("resize", this.screenAdapter);
    //取消
    this.$socket.unRegisterCallBack('trendData')
  },
  methods:{
  //res就是服务端发送给客户端的图表数据
    getData(res) {
      this.allData = res;
      this.updateChart();
    },
    }

这样就实现了后端发生变化,前端即时更新视图

至于为什么WebSocket这样封装,因为后台定了规则

const path = require('path')
const fileUtils = require('../utils/file_utils')
const WebSocket = require('ws')
// 创建WebSocket服务端的对象, 绑定的端口号是9998
const wss = new WebSocket.Server({
  port: 9998
})
// 服务端开启了监听
module.exports.listen = () => {
  // 对客户端的连接事件进行监听
  // client:代表的是客户端的连接socket对象
  wss.on('connection', client => {
    console.log('有客户端连接成功了...')
    // 对客户端的连接对象进行message事件的监听
    // msg: 由客户端发给服务端的数据
    client.on('message',async msg => {
      console.log('客户端发送数据给服务端了: ' + msg)
      let payload = JSON.parse(msg)
      const action = payload.action
      if (action === 'getData') {
        let filePath = '../data/' + payload.chartName + '.json'
        // payload.chartName // trend seller map rank hot stock
        filePath = path.join(__dirname, filePath)
        const ret = await fileUtils.getFileJsonData(filePath)
        // 需要在服务端获取到数据的基础之上, 增加一个data的字段
        // data所对应的值,就是某个json文件的内容
        payload.data = ret
        client.send(JSON.stringify(payload))
      } else {
        // 原封不动的将所接收到的数据转发给每一个处于连接状态的客户端
        // wss.clients // 所有客户端的连接
        wss.clients.forEach(client => {
          client.send(msg)
        })
      }
      // 由服务端往客户端发送数据
      // client.send('hello socket from backend')
    })
  })
}

有不懂的可以去我的github查看源代码,前后端都有,后端必须启动,前端才有显示,WebSocket我只配了Trend组件,其他全部一样的操作

github项目地址https://github.com/lsh555/Echarts

项目详情如下
在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_45389051/article/details/108890125