echarts迁徙图

1,098 阅读4分钟

这是我参与11月更文挑战的第3天,活动详情查看:2021最后一次更文挑战

需求介绍

之前在大屏展示项目中,涉及到了基于echarts: 5.0.2,实现全国与北京市的迁徙图。

  1. 如果全国地图没有数据则只展示北京市数据,如果存在优先展示全国数据。 image.png

  2. 迁入迁出标签可点击,年份可切换,需要展示对应数据

  3. 全国地图可下钻到北京地图,北京地图可返回全国地图

image.png

基础准备

  1. 地图数据和省市数据 目前主流的地图和省市数据有两个,一个是hcharts, 还有一个是datav。我们需要根据需要选择合适的数据和结构。

  2. 根据地图数据生成地图:echarts.registerMap('china', china);

  3. series中添加迁徙路径:

    {
      name: item[0],
      type: 'lines',
      zlevel: 2,
      symbol: ['none', 'arrow'],
      symbolSize: 10,
      effect: {
        show: true,
        period: 6,
        trailLength: 0,
        symbol: planePath,
        symbolSize: 15
      },
      lineStyle: {
        color: (params) => dealColor(params, item),
        type: 'dashed',
        width: 2,
        opacity: 0.6,
        curveness: 0.2
      },
      //这里就是迁徙的数据
      data: convertData(item[1])
    },
    
  4. series中添加落点的effectScatter:

    {
      name: item[0],
      type: 'effectScatter',
      coordinateSystem: 'geo',
      zlevel: 2,
      rippleEffect: {
        brushType: 'fill'
      },
      label: {
        show: true,
        position: 'right',
        formatter: '{b}',
        color: 'inherit'
      },
      symbolSize(val) {
        if (val && val.length) {
          return val[2] * 280;
        }
      },
      itemStyle: {
        color: (params) => dealColor(params, item)
      },
      data: item[1].map((dataItem) => ({
        name: dataItem[1].name,
        value: allCityData[dataItem[1].name]
      })),
    }
    
  5. 使用setOption更新地图: chinaCanvas.setOption(option);

  6. 每次切换地图的时候需要销毁之前的echarts实例,如果不删除的话,长时间的切换会导致页面越来越卡,虽然在我的代码中只使用了两个元素用来挂在全国地图和北京地图,但是每次都会创建新的echarts实例。这里有两种方法,我试了都可以。但是有人说使用dispose()方法会有问题,不过我没有遇到。不知道是不是已经解决了。

    // 销毁echarts图
    destoryCanvas(id) {
      const dom = document.getElementById(id);
      // dom.removeAttribute('_echarts_instance_');
      // dom.innerHtml = '';
      const echartsObj = echarts.getInstanceByDom(dom);
      if (echartsObj) {
        echartsObj.dispose();
      }
    },

整体设计

逻辑上有下面几点

  1. 切换年份后,重新请求数据,重新绘制地图
  2. 切换迁入迁出,更新地图数据
  3. 点击北京市,进入北京地图,右上角显示返回全国地图按钮

UI还原上最重要的就是地图边框的实现。 首先我采用的方法是将echarts地图设为透明的,鼠标覆盖上去选中的地方会有颜色变换,之后将UI带有边框的背景图放入echarts地图的后面。

  1. 我的页面有响应式设计,所以要注意背景图和ehcarts地图的位置要匹配,否则会出现鼠标覆盖上去样式和背景不重合。
  2. 一开始UI使用的是hcharts的地图GeoJSON数据,但是我使用的是dataV的数据。这样导致怎么都和UI背景图不重合。

后来记得又看到了一种方法,好像是将另外一个echarts地图作为背景。具体内容记不太清楚了,之后再查一下吧。。。

注意事项

  1. 地图数据变换时,使用setOption()修改地图数据,不需要重新绘制地图
  2. 地图切换时,要记得销毁之前创建的实例,否则长时间操作会导致页面卡顿

代码展示

drawCanvas(moveInOutData) {
      this.destoryCanvas('mapCanvas');
      const series = [];
      echarts.registerMap('china', china);
      const chinaCanvas = echarts.init(document.getElementById('mapCanvas'));
      const planePath = 'path://M1705.06,1318.313v-89.254l-319.9-221.799l0.073-208.063c0.521-84.662-26.629-121.796-63.961-121.491c-37.332-0.305-64.482,36.829-63.961,121.491l0.073,208.063l-319.9,221.799v89.254l330.343-157.288l12.238,241.308l-134.449,92.931l0.531,42.034l175.125-42.917l175.125,42.917l0.531-42.034l-134.449-92.931l12.238-241.308L1705.06,1318.313z';
      // 转换市级数据
      function convertData(data) {
        const res = [];
        for (let i = 0; i < data.length; i += 1) {
          const dataItem = data[i];
          const fromCoord = allCityData[dataItem[0].name];
          const toCoord = allCityData[dataItem[1].name];
          if (fromCoord && toCoord) {
            res.push({
              coords: [fromCoord, toCoord]
            });
          }
        }
        return res;
      }

      const color = this.toolList.map(el => el.color);
      function dealColor(params, item) {
        if (item[1][params.dataIndex][1].type === '迁入') {
          return color[0];
        }
        return color[1];
      }
      // 迁入数据
      const moveData = moveInOutData.filter(item => item[0].name === '北京' || item[1].name === '北京');
      if (moveData && moveData.length) {
        [['北京', moveData]].forEach((item, i) => {
          series.push(
            {
              name: item[0],
              type: 'lines',
              zlevel: 2,
              symbol: ['none', 'arrow'],
              symbolSize: 10,
              effect: {
                show: true,
                period: 6,
                trailLength: 0,
                symbol: planePath,
                symbolSize: 15
              },
              lineStyle: {
                color: (params) => dealColor(params, item),
                type: 'dashed',
                width: 2,
                opacity: 0.6,
                curveness: 0.2
              },
              data: convertData(item[1])
            },
            {
              // 目的地信息
              name: item[0],
              type: 'effectScatter',
              coordinateSystem: 'geo',
              zlevel: 2,
              rippleEffect: {
                brushType: 'fill'
              },
              label: {
                show: true,
                position: 'right',
                formatter: '{b}',
                color: 'inherit'
              },
              symbolSize(val) {
                if (val && val.length) {
                  return val[2] * 280;
                }
              },
              itemStyle: {
                color: (params) => dealColor(params, item)
              },
              data: item[1].map((dataItem) => ({
                name: dataItem[1].name,
                value: allCityData[dataItem[1].name]
              })),
            }
          );
        });
      }
      const option = {
        geo: {
          map: 'china',
          left: 0,
          top: 0,
          right: 0,
          bottom: 0,
          zoom: 1,
          aspectScale: 1,
          label: {
            show: false
          },
          roam: false,
          regions: [{
            name: '北京',
            label: {
              show: true,
              color: 'rgba(242, 233, 69, 1)'
            },
            itemStyle: {
              borderCap: 'square',
              borderWidth: 0,
              areaColor: '#2EA1D7',
              borderColor: 'rgba(0, 10, 25, 0)',
            }
          }],
          itemStyle: {
            borderCap: 'square',
            areaColor: 'rgba(7, 86, 157, 0)',
            borderColor: 'rgba(0, 10, 25, 0)',
          },
          emphasis: {
            itemStyle: {
              borderWidth: 0,
              areaColor: '#2EA1D7',
              borderColor: 'rgba(7, 86, 157, 1)',
            },
            label: {
              show: true,
            }
          }
        },
        series
      };
      chinaCanvas.setOption(option);
      chinaCanvas.on('click', params => {
        if (params.name === '北京') {
          this.showChinaMap = false;
          this.destoryCanvas('mapCanvas');
          this.drawBeijingCanvas(moveInOutData);
        }
      });
      this.chinaCanvas = chinaCanvas;
    },