大屏开发 / ECharts / geo(地图)

1,074 阅读4分钟

什么是ECharts

ECharts是一款基于JavaScript数据可视化图表库,提供了丰富的图表,包括了常规的折线图柱状图散点图饼图K线图,用于统计的盒形图,用于地理数据可视化的地图、热力图线图,用于关系数据可视化的关系图、treemap、旭日图,多维数据可视化的平行坐标,还有用于 BI 的漏斗图仪表盘,并且支持图与图之间的混搭。是一款成熟、易用、广泛使用的JS图标库。

官网地址:echarts.apache.org/zh/index.ht…

伪3D地图的需求

之前我也做过一些大屏开发,用的基本上都是ECharts,少部分用的是G2,所以基本上是对ECharts很熟悉了,只不过之前都是用ECharts做图表,这次需要在大屏中间放一块中国地图,并且支持下转到各个省,每个区域支持选中和做toolTip提示。

  • 展示中国地图
  • 可以下转到各个省
  • 支持选中和展示toolTips
  • 最好有3D效果
  • 有随机选中区域的动态效果,hover的时候不展示随机效果
  • 其他图表(不在这篇文章的讨论范围)

实现思路

  1. 通过多张地图叠加产生3D的效果
  2. 通过点击事件,获取下级地图数据,重新配置ECharts的Option刷新地图。

数据来源(阿里云数据可视化平台):datav.aliyun.com/portal/scho…

具体的实现

初始化ECharts

1.安装ECharts

npm install ECharts

2.引入ECharts

import * as echarts from "echarts";

3.初始化ECharts

  • 通过id初始化了ECharts
  • 加载了中国地图
  • 初始化点击事件,实现下转到省
export class BigScreenMap {
    myChart = null
    constructor({ id }) {
    this.myChart = echarts.init(document.getElementById(id)); // 初始echarts
    //加载中国地图
    this.toMap({
      baseMapName: "china",
      mainMapName: "empchina"
    });
    this.myChart.on("click", params => {
      // 处理点击事件
      try {
        if (params.name) {
          // 点击下转到省
          this.toMap({
            baseMapName: params.name,
            mainMapName: params.name
          });
        }
      } catch (error) {}
    });
    // 动态选中效果
    this.dynamicSelectOrg();
  }
}

4.加载地图

这里通过判断地图是否已经注册了,如果地图未注册则请求具体的地图参数,并且注册到ECharts里面

  async toMap(options) {
    try {
      if (proviceNameObject[options.baseMapName]) {
        if (!this.mapCache[options.baseMapName]) {
          const res = await getMapJson(proviceNameObject[options.baseMapName]);
          echarts.registerMap(options.baseMapName, res);
        }
        if (!this.mapCache[options.mainMapName]) {
          const res = await getMapJson(proviceNameObject[options.mainMapName]);
          echarts.registerMap(options.mainMapName, res);
        }
        this.baseMapName = options.baseMapName;
        this.mainMapName = options.mainMapName;
        const option = this.createOption();
        this.myChart.clear();
        this.myChart.setOption(option);
        this.mapCache(options);
      }
    } catch (error) {}
  }

5.设置ECharts参数

  • 使用多张地图叠加错位造成视觉上的3D效果
  • 加载了撒点
  //设置参数
  createOption() {
    // 省会城市Coordinate 隐射表
    const provinceCoordinates = {
      安徽: [117.194778, 31.86577],
      北京: [116.403694, 39.949459],
      重庆: [106.553263, 29.556681],
      福建: [119.292069, 26.144144],
      甘肃: [103.856737, 36.094212],
      广东: [113.239359, 23.185545],
      广西: [108.345678, 22.861984],
      贵州: [106.616332, 26.707352],
      海南: [110.350983, 19.968035],
      河北: [114.508772, 38.083783],
      河南: [113.644099, 34.769161],
      黑龙江: [126.522207, 45.801617],
      湖北: [114.361594, 30.601078],
      湖南: [112.926605, 28.217167],
      吉林: [125.326383, 43.797768],
      江苏: [118.832137, 32.038322],
      江西: [115.851775, 28.672488],
      辽宁: [123.486653, 41.682522],
      内蒙古: [111.785972, 40.849642],
      宁夏: [106.257585, 38.482579],
      青海: [101.851432, 36.622494],
      山东: [117.194778, 36.652148],
      山西: [112.595453, 37.858034],
      陕西: [109.026378, 34.350591],
      上海: [121.518142, 31.211845],
      四川: [104.132697, 30.561282],
      天津: [117.286764, 39.001295],
      西藏自治区: [91.144205, 29.649484],
      新疆维吾尔自治区: [87.667116, 43.817754],
      云南: [102.881681, 24.866897],
      浙江: [120.211934, 30.274265],
      香港: [114.242011, 22.272474],
      澳门: [113.579709, 22.169692],
      台湾: [121.591732, 25.034634]
    };

    // 需要展示的省会城市数据
    const getProvincialCapitals = names => {
      if (this.provicePoint && this.provicePoint.length > 0) return this.provicePoint;
      let res = [];
      const len = names.length;
      for (let i = 0; i < len; i++) {
        let geoCoord = provinceCoordinates[names[i]];
        if (geoCoord) {
          res.push({
            name: names[i],
            value: geoCoord
          });
        }
      }
      return res;
    };
    const positionY = this.baseMapName === "china" ? 70 : 50;
    const layoutSize = this.baseMapName === "china" ? 120 : 100;
    let option = {
      tooltip: {
        show: true
      },

      geo: [
        // 边框 map
        {
          zlevel: 6,
          id: "mainMapName",
          map: this.mainMapName,
          // 取消一些交互

          // 位置大小调整
          layoutCenter: ["50%", `${positionY}%`], //位置
          layoutSize: `${layoutSize}%`, //大小

          // 框颜色
          itemStyle: {
            color: "transparent",
            opacity: 1,
            borderWidth: 1,
            borderColor: "#73A8FF",
            borderCap: "round",
            shadowBlur: 20,
            shadowColor: "#97BBE4"
          },
          tooltip: {
            trigger: "item",
            show: true,
            enterable: true,
            formatter: this.toolTipHtmlFun,
            // position: [10, 10],
            backgroundColor: "transparent",
            borderColor: "transparent",
            extraCssText: "box-shadow: none;",
            borderWidth: 0,
            padding: 0
          },
          emphasis: {
            itemStyle: {
              areaColor: "#6BA2FE",
              color: "#6BA2FE"
            }
          }
        },
        // 顶层map
        {
          map: this.baseMapName,
          zlevel: 5,
          z: 4,

          // 位置大小调整
          layoutCenter: ["50%", `${positionY}%`], //位置
          layoutSize: `${layoutSize}%`, //大小

          // 调整颜色
          itemStyle: {
            // areaColor:'#264684',
            areaColor: "#032A74",
            borderWidth: 1.2,
            borderCap: "round",
            borderColor: "RGBA(81, 123, 165, 1)"
            // shadowColor: "#fff",
            // shadowBlur: 20,
          }
        },
        // 第二层
        {
          // 下层地图,防3d效果
          zlevel: 5,
          z: 3,

          map: this.mainMapName,
          silent: true,

          // 位置大小调整
          layoutCenter: ["50.07%", `${positionY + 0.1}%`], //位置
          layoutSize: `${layoutSize}%`, //大小
          // 颜色
          itemStyle: {
            borderWidth: 0,
            borderCap: "round",
            areaColor: "#B8FFFF"
          }
        },
        // 第三层
        {
          // 下层地图,防3d效果
          zlevel: 5,
          z: 2,

          map: this.mainMapName,
          silent: true,

          // 位置大小调整
          layoutCenter: ["50.14%", `${positionY + 1}%`], //位置
          layoutSize: `${layoutSize}%`, //大小

          // 颜色
          itemStyle: {
            borderWidth: 0,
            borderCap: "round",
            areaColor: "#B8FFFF"
          }
        },
        // 第四层
        {
          // 下层地图,防3d效果
          zlevel: 5,
          z: 1,

          map: this.mainMapName,

          silent: true,

          // 位置大小调整
          layoutCenter: ["50.21%", `${positionY + 1.3}%`], //位置
          layoutSize: `${layoutSize - 0.5}%`, //大小
          // 颜色
          itemStyle: {
            borderColor: "rgba(0, 0, 0, 0.3)",
            borderWidth: 1,
            borderCap: "round",
            areaColor: "#B8FFFF",
            shadowBlur: 10,
            shadowColor: "rgba(0, 0, 0, 0.6)"
          }
        }
      ],
      series: [
        // 其他省份
        {
          name: "otherProvincialCapital",
          // symbol 形状修改
          type: "scatter",
          symbol: "circle",
          symbolSize: this.baseMapName != "china" ? [34, 49] : 4,
          // //背景
          symbol: this.baseMapName != "china" ? "image://" + mapPointBG : "",
          symbolOffset: [0, "-100%"],
          // 显示层级
          zlevel: 7,
          z: 1,
          coordinateSystem: "geo",
          geoIndex: 1,
          itemStyle: {
            //坐标点颜色
            show: true,
            color: "#fff"
          },
          // label样式
          label: {
            show: this.baseMapName != "china" ? false : true,
            position: "top",
            formatter: "{b}",
            fontSize: 10,
            color: "rgba(255,255,255,0.8)",
            emphasis: {
              show: true
            },
            padding: 3,
            borderWidth: 0,
            borderRadius: 2,
            // borderColor: "rgba(7, 19, 37, 1)",
            fontWeight: 300,
            backgroundColor: "rgba(255, 255, 255, 0.1)" // 字体背景色
          },
          silent: true,
          data: getProvincialCapitals(this.needShowProvincialCapitals)
        }
      ]
    };

    return Object.freeze(option);
  }

6.动态选中区域

  • 通过定时器随机高亮和showTip展示选中效果
  //是否要动态
  dynamicSelectOrg() {
    let timer = null;
    let timeoutTimer = null;
    this.myChart.on("mouseover", params => {
      // mouseover
      clearInterval(timer);
      clearTimeout(timeoutTimer);
      this.unselectedMapOrigin();
      this.selectName = "";
    });
    this.myChart.on("globalout", params => {
      //globalout
      clearInterval(timer);
      clearTimeout(timeoutTimer);
      timeoutTimer = setTimeout(() => {
        timer = setInterval(() => {
          this.selectedMapOrign();
        }, 3000);
      }, 3 * 1000);
    });
    timer = setInterval(() => {
      this.selectedMapOrign();
    }, 3000);
  }
  selectedMapOrign() {
    try {
      this.unselectedMapOrigin();
      const randomNum = this.getRandomInt(0, this.mapNameData[this.mainMapName].length);
      this.selectName = this.mapNameData[this.mainMapName][randomNum];
      //高亮
      this.myChart.dispatchAction({
        type: "highlight",
        geoId: "mainMapName",
        name: this.selectName
      });
      //showTip

      this.myChart.dispatchAction({
        type: "showTip",
        geoId: "mainMapName",
        name: this.selectName
      });
    } catch (error) {}
  }
  unselectedMapOrigin() {
    try {
      if (this.selectName) {
        //高亮
        this.myChart.dispatchAction({
          type: "downplay",
          geoId: "mainMapName",
          name: this.selectName
        });
      }
    } catch (error) {}
  }

最终实现的效果如下

image.png

image.png