使用 echarts 实现世界地图、中国地图、以及飞线、标牌、3D柱状图绘制

8,484 阅读4分钟

一、需求

1. 世界地图效果图

01.png

02.png

在世界地图上实现飞线效果、IP地址显示

2. 中国地图效果图

03.png

在中国地图上实现黄、蓝标牌、 3D 柱状图等,并带有十段线

二、开发说明

echarts v5.0.0+ node v12.0.0+ react class 组件形式

三、开发步骤

引入 echarts

  • npm 下载
npm install echarts
  • cdn 引入
<script
  type="text/javascript"
  src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"
></script>

开始使用

  • 引入地图资源,注册地图(这里以世界地图为例)
import worldJson from "./world.json";

class WorldMap extends Component {
    ...
    componentDidMount() {
        this.createMap();
    }

    createMap = () => {
        constructor(props) {
            super(props);
            this.state = {
                options: {},
                ipOptions: {},
                mapChart: null,
            };
        }
       ... //获取配置数据
        
        const dom = document.getElementById(componentConfig.id); // 保证每个地图唯一
        var mapChart = echarts.init(dom); // 初始化
        echarts.registerMap("world", worldJson); // 注册地图
        this.setState({ options, ipOptions }); // 飞线、IP都存state
        mapChart.setOption(displayMode === 0 ? options : ipOptions); // 显示方式切换;配置地图选项
        this.setState({ mapChart })
    };
}

配置地图选项 options

  • 地图飞线的配置实现
    const options = {
      radius: '100%',
      tooltip: {
        trigger: 'item',
      },
      legend: {
        orient: 'horizontal', //图例的排列方向
        // textStyle: { color: '#1a1e45' },
        x: 'left', //图例的位置
        y: '-20000000000000',
      },
      visualMap: {
        //颜色的设置  dataRange
        // textStyle: { color: '#1a1e45' },
        x: 'left',
        y: 'bottom',
        // splitList: [{ start: 0, end: 150000 }],
        show: false,
        // text:['高','低'],// 文本,默认为数值文本
        color: [flyColor], // 组件配置变量
      },
      geo: {
        map: 'world', // 此处需与注册地图命名保持一致
        type: 'map',
        zoom: 1.2,
        label: {
          normal: {
            show: false,
            textStyle: {
              color: '#FFFFFF',
            },
          },
          emphasis: {
            show: false,
          },
        },
        roam: false, //是否允许缩放
        itemStyle: {
          normal: {
            color: bgColor, //地图背景色,用到组件配置的变量
            borderColor: borderColor, //省市边界线
            borderWidth: 1,
            textStyle: '#fff',
          },
          emphasis: {
            areaColor: selectColor, //悬浮背景
          },
        },
        data: [],
      },
      series: this.getSeries(centerPoint, mainData, flyLineArr, coordData, flyDirection),  // 飞线的配置
    }

      //飞线配置
      getSeries = (centerPoint, mainData, flyLineArr, coordData, flyDirection) => {
        let series = [];
        let centerPointName = Object.keys(centerPoint)[0] || '北京区域中心';
        let centerPointValue = Object.values(centerPoint)[0] || [116, 39];
        [[centerPointName, flyLineArr]].forEach((item, i) => {
        series.push(
            {
            type: 'lines',
            coordinateSystem: 'geo',
            zlevel: 2,
            effect: {
                show: true,
                period: 5, //箭头指向速度,值越小速度越快
                trailLength: 0, //特效尾迹长度[0,1]值越大,尾迹越长重
                symbol: 'arrow', //箭头图标
                symbolSize: 5, //图标大小
                color: mainData.iconColor, // 图标颜色
            },
            lineStyle: {
                normal: {
                show: true,
                width: 1, //尾迹线条宽度
                opacity: 1, //尾迹线条透明度
                curveness: 0.3, //尾迹线条曲直度
                color: mainData.flyColor, // 飞线颜色 - 细线
                },
            },
            data: this.convertData(item[1], coordData, flyDirection, centerPointValue),
            },
            {
            type: 'effectScatter',
            radius: '100%',
            coordinateSystem: 'geo',
            zlevel: 2,
            rippleEffect: { //涟漪特效
                period: 4, //动画时间,值越小速度越快
                brushType: 'stroke', //波纹绘制方式 stroke, fill
                scale: 3, //波纹圆环最大限制,值越大波纹越大
                color: mainData.rippleColor,
            },
            label: {
                normal: {
                show: false,
                position: 'right', //显示位置
                offset: [5, 0], //偏移设置
                formatter: (params) => {
                    return params.data.name //圆环显示文字
                },
                fontSize: 13,
                },
                emphasis: {
                show: false,
                },
            },
            symbol: 'circle',
            symbolSize: (val) => {
                return 5 //圆环大小
            },
            itemStyle: {
                normal: {
                show: true,
                // areaColor: mainData.pointColor,
                // color: mainData.pointColor,
                areaColor: "#ade9f4",
                color: "#ade9f4",
                },
                emphasis: {
                // areaColor: mainData.pointColor,
                areaColor: "#ade9f4",
                },
            },
            data: item[1].map((dataItem) => {
                return {
                //在这里定义你所要展示的数据
                name: dataItem[0].name,
                value: coordData[dataItem[0].name]?.concat([dataItem[0].value]),
                }
            }),
            },
            //中心点
            {
            type: 'effectScatter',
            radius: '100%',
            coordinateSystem: 'geo',
            zlevel: 15,
            rippleEffect: {
                period: 4,
                brushType: 'stroke',
                scale: 4,
                color: '#FFD246',
            },
            label: {
                normal: {
                show: false,
                position: 'right',
                //offset:[5, 0],
                color: '#FFD246',
                formatter: '{b}',
                textStyle: {
                    color: '#FFD246',
                },
                },
                emphasis: {
                show: false,
                color: '#FFD246',
                },
            },
            symbol: 'circle',
            symbolSize: 5,
            itemStyle: {
                color: '#FFD246',
            },
            data: [
                {
                name: item[0],
                value: coordData[item[0]]?.concat([10]),
                },
            ],
            }
        )
        })
        return series
    };

  • 配置 IP 地址显示
    const ipOptions = {
      tooltip: {
        backgroundColor: "rgba(0,0,0,0)",
        trigger: "axis",
      },
      legend: {
        show: false,
      },
      geo: {
        map: 'world',
        type: 'map',
        zoom: 1.2,
        label: {
          normal: {
            show: false,
            textStyle: {
              color: '#FFFFFF',
            },
          },
          emphasis: {
            show: false,
          },
        },
        roam: false, //是否允许缩放
        itemStyle: {
          normal: {
            color: bgColor, //地图背景色
            borderColor: borderColor, //省市边界线00fcff 516a89
            borderWidth: 1,
            textStyle: '#fff',
          },
          emphasis: {
            areaColor: selectColor, //悬浮背景
          },
        },
        data: [],
      },
      series: [
        {
          tooltip: {
            show: false,
          },

          type: "effectScatter",
          coordinateSystem: "geo",
          rippleEffect: {
            scale: 10,
            brushType: "stroke",
          },
          showEffectOn: "render",
          itemStyle: {
            normal: {
              color: "#00FFFF", //ip 涟漪颜色
            },
          },
          label: {
            normal: {
              color: "#fff",
            },
          },
          symbol: "circle",
          // symbolSize: [10, 5],
          symbolSize: [10, 5],
          data: this.convertIPData(ipData, ipCoordData),
          zlevel: 1,
        },
        {
          type: "scatter",
          coordinateSystem: "geo",
          itemStyle: {
            color: "#00FFF6",  // 光标颜色
          },
          symbol: img.arrow,
          // symbol: 'arrow',
          symbolSize: [44, 34],
          symbolOffset: [0, -10],
          // symbolRotate: 180,
          z: 999,
          data: this.convertIPData(ipData, ipCoordData),
        },
        {
          type: "scatter",
          coordinateSystem: "geo",
          label: {
            normal: {
              show: true,
              formatter: function (params) {
                let name = params.name;
                let value = params.value[2];
                let text = `{fline|${value}}`;
                return text;
              },
              color: "#fff",
              rich: {
                fline: {
                  padding: [0, 25],
                  color: "#fff",
                  fontSize: 14,
                  fontWeight: 600,
                },
                tline: {
                  padding: [0, 27],
                  color: "#ABF8FF",
                  fontSize: 12,
                },
              },
            },
            emphasis: {
              show: true,
            },
          },
          itemStyle: {
            color: "#00FFF6",
          },
          symbol: img.ipbg,
          // symbol: "roundRect",
          symbolSize: [125, 32],
          symbolOffset: [0, -35],
          z: 999,
          data: this.convertIPData(ipData, ipCoordData),
        },
      ],
    };

    // IP数据转换
    convertIPData = (data, gdGeoCoordMap) => {
        // 校验
        if (!data) { return }
        let res = [];
        for (let i = 0; i < data.length; i++) {
            let geoCoord = gdGeoCoordMap[data[i].name];
            if (geoCoord) {
                res.push({
                name: data[i].name,
                value: geoCoord.concat(data[i].value),
                });
            }
        }
        return res;
    };

至此,即可完成世界地图的基本配置。

四、开发过程问题及解决方案

1.中国地图和世界地图如何配置?

两者在注册地图时切换地图资源即可展现不同的地图效果。

    echarts.registerMap("china", chinaJson);
    echarts.registerMap("world", worldJson);    

注:中国地图十段线的配置也在地图资源数据内完成。

2.世界地图飞线方向如何控制?

飞线涉及到飞线中心点,飞线方向由coordArr控制,调换coordArr内坐标位置即可改变飞线攻击方向。

注:若需要实现多飞线中心,则需修改地图数据结构。

  convertData = (data, coordData, flyDirection, centerPointValue) => {    
   if (!data) { return } //判空
   let res = []
   for (let i = 0; i < data.length; i++) {
     let dataItem = data[i]
     let fromCoord = coordData[dataItem[0].name]
     let toCoord = centerPointValue //中心点地理坐标 
     if (fromCoord && toCoord) {
       let coordArr = [{
           coord: fromCoord, // 飞线去往哪里
           value: dataItem[0].value,
         }, {
           coord: toCoord, // 飞线从哪里出发
         },
       ];
       res.push( flyDirection === 0 ? coordArr : coordArr.reverse() ) // 通过 flyDirection 控制飞线方向
     }
   }
   return res
 }
3.地图自定义图标如何引入?

echarts 的series方法中的symbol属性可以引入图片路径,一共有三种方法:

  1. 链接引入

在symbol中直接引入图片的路径,注意格式,要加image://

   {
        type: "scatter",
        coordinateSystem: "geo",
        label: { ... },
        itemStyle: { color: "#00FFF6",},
        symbol: "image://http:...",
        symbolSize: [600, 240],
        symbolOffset: [360, -150],
        symbolRotate: 180,
        z: 999,
        data: this.convertIPData2(dataCenter, ipCoordData),
    }    
  1. base64格式引入

base64格式引入,需要注意base64代码串不能换行

   {
        type: "scatter",
        coordinateSystem: "geo",
        label: { ... },
        itemStyle: { color: "#00FFF6",},
        symbol: "image://data:image/png;data:image/png;base64,iVBORw...",
        symbolSize: [600, 240],
        symbolOffset: [360, -150],
        symbolRotate: 180,
        z: 999,
        data: this.convertIPData2(dataCenter, ipCoordData),
    }    
  1. svg图引入

svg图可用notepad++软件或者记事本打开,将属性值复制出来前面加path://即可

   {
        type: "scatter",
        coordinateSystem: "geo",
        label: { ... },
        itemStyle: { color: "#00FFF6",},
        symbol: "path://M512....",
        symbolSize: [600, 240],
        symbolOffset: [360, -150],
        symbolRotate: 180,
        z: 999,
        data: this.convertIPData2(dataCenter, ipCoordData),
    }    
4.中国地图边缘高亮实现

04.png

echarts提供的seriesitemStyle -normal-boderColor设置的是各省份之间的边界线颜色,要实现单独的地图边缘高亮,可用geo属性结合series生成两个地图叠加,实现想要的效果。

const options = {
    ...,
    geo: {
        silent: true,
        radius: '100%',
        map: "china",
        zoom: 1.20,
        // zoom: 0.8,
        // top: "-6%",
        label: {
            normal: {
            show: false,
            textStyle: {
                color: "#fff",
            },
            },
            emphasis: {
            textStyle: {
                color: "#fff",
            },
            },
        },
        roam: false,
        itemStyle: {
            normal: {
            areaColor: "rgba(0,255,255,.02)",
            borderColor: {
                type: 'linear', //设置边缘色渐变
                x: 0,
                y: 1,
                x2: .5,
                y2: 0,
                colorStops: [
                    {
                        offset: 0, color: '#f7e914'
                    }, {
                        offset: 0.5, color: '#fbaa0e'
                    }, {
                        offset: 1, color: '#306a9f'
                    }],
                global: false
            },
            borderWidth: 7,
            shadowColor: "#35a8ff",
            // shadowColor: "#3071a7",
            shadowOffsetX: 0,
            shadowOffsetY: 38,
            shadowBlur: 45,
            },
            emphasis: {
            areaColor: "transparent", //悬浮背景
            textStyle: {
                color: "#fff",
            },
            },
        },
    },

      series: [
        // 地图
        {
          map: 'china',
          type: 'map',
          radius: '100%',
          zoom: 1.20,
          label: {
            normal: {
              show: false,
              textStyle: {
                color: '#FFFFFF',
              },
            },
            emphasis: {
              show: false,
            },
          },
          roam: false, //是否允许缩放
          itemStyle: {
            normal: {
              // color: bgColor, //地图背景色
              areaColor: bgColor, //地图背景色
              borderColor: borderColor, //内边缘颜色
              borderWidth: 2,
              textStyle: '#fff',
            },
            emphasis: {
              areaColor: selectColor, //悬浮背景
            },
          },
          data: [],
        },
        ... //其他配置
    ]
}
5. 如何在地图上实现3D柱状图

05.png

地图上元素的配置均在optionsseries内完成,柱状图分主干、顶部、底部 三部分绘制实现,代码如下:

series: [
    ... //其他配置
    // 柱状体的主干
    {
        type: "lines",
        zlevel: 5,
        effect: {
            show: false,
            // period: 4, //箭头指向速度,值越小速度越快
            // trailLength: 0.02, //特效尾迹长度[0,1]值越大,尾迹越长重
            // symbol: 'arrow', //箭头图标
            // symbol: imgDatUrl,
            symbolSize: 5, // 图标大小
        },
        lineStyle: {
            width: 60, // 尾迹线条宽度
            // color: "#f60", //柱状颜色
            color: {
                type: 'linear', // 线性渐变
                x: 0,             // x:  从左向右 1 ——> 0
                y: 0,             // y:  从上向下 1 ——> 0
                x2: 0,            // x2: 从右向左 1 ——> 0
                y2: 1,            // y2: 从下向上 1 ——> 0
                colorStops: [
                { offset: 0, color: '#ffd43c' },
                { offset: 1, color: '#bf5b2d' }
                ]
            },
            opacity: .8, // 尾迹线条透明度
            curveness: 0, // 尾迹线条曲直度
        },
        label: {
            show: 0,
            position: "end",
            formatter: "245",
        },
        silent: true,
        data: this.lineData(dataCenter, ipCoordData),
    },
    // 柱状体的顶部
    {
        type: "scatter",
        coordinateSystem: "geo",
        // geoIndex: 0,
        // zlevel: 5,
        label: {
        normal: {
            show: true,
            formatter: () => { return ''; },
            color: "#fff",
        },
        emphasis: {
            show: true,
        },
        },
        itemStyle: {
        color: "#fff", //##柱状顶部颜色
        // opacity: .9,
        },
        symbol: img.orange,
        symbolSize: [60, 96],
        symbolOffset: [0, -30],
        z: 999,
        // silent: true,
        data: this.scatterData(dataCenter, ipCoordData),
    },
    // 柱状体的底部
    {
        type: "scatter",
        coordinateSystem: "geo",
        geoIndex: 0,
        zlevel: 4,
        label: {
        formatter: "{b}",
        position: "bottom",
        color: "#fff",
        fontSize: 36,
        distance: 10,
        show: true,
        },
        symbol: "circle",
        symbolSize: [60, 30],
        itemStyle: {
        color: '#f60',
        opacity: .6,
        },
        silent: true,
        data: this.scatterData2(dataCenter, ipCoordData),
    },
]

柱状体各部分data数据转换

  // 柱状体的主干
  lineData = (dataCenter, ipCoordData) => {
    if (!dataCenter) { return }
    return dataCenter.map((item) => {
      return {
        coords: [
          ipCoordData[item.name], 
          [
            ipCoordData[item.name],
            ipCoordData[item.name] + item.times * this.lineMaxHeight(dataCenter),
          ],
        ],
      };
    });
  }
  // 柱状体的顶部
  scatterData = (dataCenter, ipCoordData) => {
    if (!dataCenter) { return }
    return dataCenter.map((item) => {
      return [
        ipCoordData[item.name],
        ipCoordData[item.name] + item.times * this.lineMaxHeight(dataCenter),
      ];
    });
  }
  // 柱状体的底部
  scatterData2 = (dataCenter, ipCoordData) => {
    if (!dataCenter) { return }
    return dataCenter.map((item) => {
      return {
        name: item.name,
        value: ipCoordData[item.name],
      };
    });
  }

6.中国地图黄色标牌如何配置?

自定义标牌的实现也在optionsseries内配置完成。

series:[
    ..., //其他配置
    // 黄色标牌
    {
        type: "scatter",
        coordinateSystem: "geo",
        label: {
        normal: {
            show: true,
            formatter: function (params) {
            let value = params.value[2];
            let times = params.data.times;
            let system = params.data.system;
            let text = `{fline|数据中心(${value})}\n{tline|攻击总量:${times} 次}\n{tline|攻击系统:${system} 个}`;
            return text;
            },
            color: "#fff",
            rich: { // 富文本配置
                fline: {
                    padding: [0, 5], // 调整标牌文字位置
                    backgroundColor: {
                    type: 'linear',
                    x: 0,
                    y: 0,
                    x2: 1,
                    y2: 0,
                    colorStops: [{
                        offset: 0, color: '#f7c91c' // 0% 处的颜色
                    }, {
                        offset: 1, color: '#ffffff00' // 100% 处的颜色
                    }],
                    globalCoord: false // 缺省为 false                
                    },
                    lineHeight: 72,
                    fontSize: 42,
                    width: 500,
                    height: 80,
                },
                tline: {
                    padding: [0, 25],
                    color: "#fff",
                    fontSize: 36,
                    lineHeight: 60,
                    width: 500,
                },
            },
        },
        emphasis: {
            show: true,
        },
        },
        itemStyle: {
            color: "#00FFF6",
        },
        symbol: img.yellow, //自定义图标
        symbolSize: [600, 240],
        symbolOffset: [360, -150],
        z: 999,
        data: this.convertIPData2(dataCenter, ipCoordData),
    },
]

五、经验总结

  1. 关于UI图效果还原,基本都能在options上配置实现,可多测试看效果。

  2. echarts 组件案例合集:

    www.isqqw.com/

    www.ppchart.com/#/

    codepen.io/