看看地图还能玩出些什么花来 !

2,340 阅读12分钟

由于近期一直在调整数据大屏,而这次的改动主要是涉及地图的展示效果 -- 实现双地图的联动切换,省份地图随着全国地图的轮播切换而展示各省的详细分布数据。

那么这次就先来讲讲地图的基本配置,主要介绍地图功能的实现,相信在了解地图基本的配置后联动的功能就更容易实现了。

获取地图数据

要实现地图的第一步当然是准备好相应地区的 json 数据啦!!!

地图 JSON 文件来源我们可以在  DATAV 阿里云提供的地图数据,从中下载项目所需的地区 json 数据用于地图的注册。

将地图的 json 数据下载后有三种使用方式

  • 保存在项目本地的静态资源中
  • 将 json 挂载到全局,通过window调用
  • 也可以将 json 数据保存在服务器上,通过 fetch 请求所需要的 json数据,有助于实现地图的动态加载,例如实现地图的地区切换

1.保存  json 文件到本地

把下载好的数据保存在项目的静态资源中,通过 require 引入即可实现地图的注册

 // 注册地图类型
this.mapRegister = require('@/assets/mapData/world.json') 
echarts.registerMap('world', this.mapRegister)

注册的地图名称可以自定义,例如当前设置的地图名称为 'world',

注意此时注册的地图 'world' 与后续地图配置的 series 中的 map 属性相对应,map 用来指定所使用的地图。

但是将 json 数据保存在项目中的缺点就是会导致项目的资源过大,在需要维护多个地图数据的情况下,JSON 数据文件会不断增加,不利于项目的维护,甚至会影响项目的加载速度。

那么我们就可以选择将地图数据维护在云端或者服务器上通过链接的形式获取地图数据。

2.将链接挂载到全局

对于链接形式的地图数据我们可以选择将链接挂载到项目的全局

public\index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title>
  </head>
  <body>
    <noscript>
      <strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app"></div>
    <!-- 获取地图数据 用于生成地图 该数据以 js 的格式存放-->
    <script src="https://static.distributetop.com/assets/erp/js/world.js"></script>
  </body>
</html>

注册地图: 通过 window. 可以获取到刚刚在全局挂载的数据,实现地图的注册。

//  echarts 中通过  window._worldJson 获取 js 中的数据
echarts.registerMap('world',window._worldJson)

但是这个方法不太适合地图的动态切换,那么让我们看看最后一种 fech请求方式。

3. 请求地图数据

通过 fetch 请求地图数据的资源

	// fetch()接收到的数据是一个 Stream 对象,通过.json()将 fetch 请求到的数据转为json 
  const mapData = await fetch("https://xxxx.aliyuncs.com/geojson/zhejiang.json")
  const map = await mapData.json() // 而 json() 是一个异步操作
  echarts.registerMap('zhejiang', map)
  if (this.provinceData.length > 0) {
    this.init()
  }
  this.getDataTimer = setInterval(async () => {
    await this.getMapData()
  }, 15000)

当然使用 fetch 的前提是该资源是能被允许跨域请求的,例如将 方法2 中 script 加载的资源链接放到fetch中请求时会出现以下的报错信息,这是出现了跨域的问题。

为什么同一个资源在 script 引用 和 fetch 请求 两种资源获取方式下会出现两种不同的情况呢?

那是因为在script标签的src属性中引用资源时,浏览器会允许跨域。但是在使用 fetch I进行请求时,浏览器会根据其CORS策略来判断是否允许该请求。如果服务器没有设置允许跨域请求的响应头,浏览器就会阻止该请求。

因此,即使是同一个资源,在不同的资源获取方式下,浏览器的CORS策略也可能不同。

当出现跨域问题是可以选择在服务器端配置响应头,添加'Access-Control-Allow-Origin'头,将其设置为允许访问该资源的域名。如果你没有控制服务器端,可以尝试使用代理来获取资源,或者设置请求的mode为'no-cors',但是这样可能会导致一些限制。

地图的具体实现

接下来我们选择以浙江地图为例

在 series 中设置 echarts 类型为 map,通过 data 传入为所要显示的区域数据;

visualMap:视觉映射组件,用于映射地区对应数值的颜色

selectedMode:false 可以关闭点击区域时的默认颜色

<template>
  <div class='wrapper'>
    <!-- echart 地图 -->
    <div id="echartsMap">
    </div>
  </div>
</template>

<script>
import * as echarts from 'echarts';
var cityData = [                        // 显示地区对应的数值
    {name: '杭州', value: 41},
    {name: '温州', value: 98},
    {name: '宁波', value: 47},
    {name: '湖州', value: 31},
    {name: '嘉兴', value: 39},
    {name: '绍兴', value: 70},
    {name: '金华', value: 20},
    {name: '衢州', value: 18},
    {name: '舟山', value: 80},
    {name: '台州', value: 40},
    {name: '丽水', value: 35},
];

var nameMap = {                 // 名称对应关系
    '杭州市': '杭州',
    '温州市': '温州',
    '宁波市': '宁波',
    '湖州市': '湖州',
    '嘉兴市': '嘉兴',
    '绍兴市': '绍兴',
    '金华市': '金华',
    '衢州市': '衢州',
    '舟山市': '舟山',
    '台州市': '台州',
    '丽水市': '丽水',
}
export default {
  name:'',
  components:{
  },
  data() {
    return {
        chart:null
    }
  },
  computed: {
  },
  created() {},
  mounted() {
    this.init()
  },
  methods: {
    init() {
     const mapData = await fetch("https://xxxx.aliyuncs.com/geojson/zhejiang.json")
     const map = await mapData.json()
     echarts.registerMap('zhejiang',map)
      var option = {
        //设置背景颜色
        // backgroundColor: '#404a59',
        tooltip: {           //提示框组件
            trigger: 'item',
            formatter: '{b}{c} ',
            show:true
        },
        visualMap: {   // 配置视觉映射组件
          left: 'right',  
          min: 0,
          max: 100,
          show: true,
          realtime: true,
          calculable: true,
          inRange: {
            color: [
              '#ffffbf',
              '#fee090',
              '#fdae61',
              '#f46d43',
            ]
          },
          text: ['value', ''],  //两端的文本
           //设置字体颜色
          textStyle: {
            // color: '#ffffff'
          }         
        },
        
        series: [{
            name: '地图数据',
            type: 'map',
            data: cityData,    
            map: 'zhejiang', // 使用 registerMap 注册的地图名称。
            roam: true,      //是否开启鼠标缩放和平移漫游
          	selectedMode: false, // 关闭点击地图时设置的颜色
            label: {
              show: true
            },
            nameMap: nameMap, //自定义地区的名称映射
           itemStyle: {
            normal: {
              borderWidth: 0,//边际线大小
              borderColor:'#00ffff',//边界线颜色
              areaColor:'#09295b'//默认区域颜色
            },
            emphasis: {
              show: true,
              areaColor: '#258aff',//鼠标滑过区域颜色
              label: {
                show: true,
                textStyle: {
                  color: '#fff'
                }
              }
            }
           },
             
        }]
      };
      this.chart = echarts.init(document.getElementById('echartsMap'));
      this.chart.setOption(option);
    }
  },
}
</script>

<style lang='scss' scoped>
  #echartsMap {
    width: 100%;
    height: 98vh;
  }
</style>

实现效果如下图所示:

名称映射

nameMap  与 data 对应关系

nameMap  能将地图 json 中定义的地区名称映射成自定义的形式,而 series 中传入的 data 数据要与映射后的名称保持一致,否则无法根据传入的数据匹配相应的地区。

var cityData = [                     // 显示地区对应的数值
    {name: '杭州', value: 41},
    {name: '温州', value: 98},
    {name: '宁波', value: 47},
    {name: '湖州', value: 31},
    {name: '嘉兴', value: 39},
    {name: '绍兴', value: 70},
    {name: '金华', value: 20},
    {name: '衢州', value: 18},
    {name: '舟山', value: 80},
    {name: '台州', value: 40},
    {name: '丽水', value: 35},
];

// 名称对应关系 地区映射成的 名称 必须与 cityData 中的 name 字段保持一致
var nameMap = {                 
    '杭州市': '杭州',
    '温州市': '温州',
    '宁波市': '宁波',
    '湖州市': '湖州',
    '嘉兴市': '嘉兴',
    '绍兴市': '绍兴',
    '金华市': '金华',
    '衢州市': '衢州',
    '舟山市': '舟山',
    '台州市': '台州',
    '丽水市': '丽水',
}

visualMap 参数配置

视觉映射组件,用于进行『视觉编码』,也就是将数据映射到视觉元素。

如果当前只需要展示有数据的地区而不展示视觉映射组件也可以将 visualMap 中的 show 的属性设置为 false,虽然设置后 visualMap组件将不再存在,但是对地图的数据展示并不影响,对应地区的数据还是会根据相应的色块显示。

visualMap配置:

visualMap=[    
    {
        show:true,             //是否显示 visualMap-continuous 组件。如果设置为 false,不会显示,但是数据映射的功能还存在
        type: 'continuous',    // 定义为连续型 viusalMap
        min:10,                   //指定 visualMapContinuous 组件的允许的最小值
        max:100,                //指定 visualMapContinuous 组件的允许的最大值
        range:[15, 40],        //指定手柄对应数值的位置。range 应在 min max 范围内
        calculable:true,        //是否显示拖拽用的手柄(手柄能拖拽调整选中范围)
        realtime:true,            //拖拽时,是否实时更新
        inverse:false,            //是否反转 visualMap 组件
        precision:0,               //数据展示的小数精度,默认为0,无小数点
        itemWidth:20,            //图形的宽度,即长条的宽度。
        itemHeight:140,         //图形的高度,即长条的高度。
        align:"auto",               //指定组件中手柄和文字的摆放位置.可选值为:'auto' 自动决定。'left' 手柄和label在右。'right' 手柄和label在左。'top' 手柄和label在下。'bottom' 手柄和label在上。
        text:['High', 'Low'],       //两端的文本
        textGap:10,                 //两端文字主体之间的距离,单位为px
        dimension:2,               //指定用数据的『哪个维度』,映射到视觉元素上。『数据』即 series.data。 可以把 series.data 理解成一个二维数组,其中每个列是一个维度,默认取 data 中最后一个维度
        seriesIndex:1,             //指定取哪个系列的数据,即哪个系列的 series.data,默认取所有系列
        hoverLink:true,            //鼠标悬浮到 visualMap 组件上时,鼠标位置对应的数值 在 图表中对应的图形元素,会高亮
        inRange:{                  //定义 在选中范围中 的视觉元素
            color: ['#121122', 'rgba(3,4,5,0.4)', 'red'],
            symbolSize: [30, 100]
        },
        outOfRange:{  //定义 在选中范围外 的视觉元素。
            color: ['#121122', 'rgba(3,4,5,0.4)', 'red'],
            symbolSize: [30, 100]
        },
        zlevel:0,                  //所属图形的Canvas分层,zlevel 大的 Canvas 会放在 zlevel 小的 Canvas 的上面
        z:2,                                         //所属组件的z分层,z值小的图形会被z值大的图形覆盖
        left:"center",                              //组件离容器左侧的距离,'left', 'center', 'right','20%'
        top:"top",                                   //组件离容器上侧的距离,'top', 'middle', 'bottom','20%'
        right:"auto",                               //组件离容器右侧的距离,'20%'
        bottom:"auto",                              //组件离容器下侧的距离,'20%'
        orient:"vertical",                         //图例排列方向
        padding:5,                                   //图例内边距,单位px  5  [5, 10]  [5,10,5,10]
        backgroundColor:"transparent",            //标题背景色
        borderColor:"#ccc",                         //边框颜色
        borderWidth:0,                               //边框线宽
        textStyle:mytextStyle,                      //文本样式
        formatter: function (value) {                 //标签的格式化工具。
            return 'aaaa' + value;                    // 范围标签显示内容。
        }
    },
    {
        show:true,                          //是否显示 visualMap-continuous 组件。如果设置为 false,不会显示,但是数据映射的功能还存在
        type: 'piecewise',                  // 定义为分段型 visualMap
        splitNumber:5,                      //对于连续型数据,自动平均切分成几段。默认为5段
        pieces: [                           //自定义『分段式视觉映射组件(visualMapPiecewise)』的每一段的范围,以及每一段的文字,以及每一段的特别的样式
            {min: 1500},                     // 不指定 max,表示 max 为无限大(Infinity)。
            {min: 900, max: 1500},
            {min: 310, max: 1000},
            {min: 200, max: 300},
            {min: 10, max: 200, label: '10 到 200(自定义label)'},
            {value: 123, label: '123(自定义特殊颜色)', color: 'grey'}, // 表示 value 等于 123 的情况。
            {max: 5}                        // 不指定 min,表示 min 为无限大(-Infinity)。
        ],
        categories:['严重污染', '重度污染', '中度污染', '轻度污染', '良', '优'],  //用于表示离散型数据(或可以称为类别型数据、枚举型数据)的全集
        min:10,                             //指定 visualMapContinuous 组件的允许的最小值
        max:100,                             //指定 visualMapContinuous 组件的允许的最大值
        minOpen:true,                       //界面上会额外多出一个『< min』的选块
        maxOpen:true,                       //界面上会额外多出一个『> max』的选块。
        selectedMode:"multiple",           //选择模式,可以是:'multiple'(多选)。'single'(单选)。
        inverse:false,                      //是否反转 visualMap 组件
        precision:0,                        //数据展示的小数精度,默认为0,无小数点
        itemWidth:20,                       //图形的宽度,即长条的宽度。
        itemHeight:140,                     //图形的高度,即长条的高度。
        align:"auto",                       //指定组件中手柄和文字的摆放位置.可选值为:'auto' 自动决定。'left' 手柄和label在右。'right' 手柄和label在左。'top' 手柄和label在下。'bottom' 手柄和label在上。
        text:['High', 'Low'],               //两端的文本
        textGap:10,                          //两端文字主体之间的距离,单位为px
        showLabel:true,                     //是否显示每项的文本标签
        itemGap:10,                          //每两个图元之间的间隔距离,单位为px
        itemSymbol:"roundRect",             //默认的图形。可选值为: 'circle', 'rect', 'roundRect', 'triangle', 'diamond', 'pin', 'arrow'
        dimension:2,                          //指定用数据的『哪个维度』,映射到视觉元素上。『数据』即 series.data。 可以把 series.data 理解成一个二维数组,其中每个列是一个维度,默认取 data 中最后一个维度
        seriesIndex:1,                        //指定取哪个系列的数据,即哪个系列的 series.data,默认取所有系列
        hoverLink:true,                      //鼠标悬浮到 visualMap 组件上时,鼠标位置对应的数值 在 图表中对应的图形元素,会高亮
        inRange:{                             //定义 在选中范围中 的视觉元素
            color: ['#121122', 'rgba(3,4,5,0.4)', 'red'],
            symbolSize: [30, 100]
        },
        outOfRange:{                            //定义 在选中范围外 的视觉元素。
            color: ['#121122', 'rgba(3,4,5,0.4)', 'red'],
            symbolSize: [30, 100]
        },
        zlevel:0,                                   //所属图形的Canvas分层,zlevel 大的 Canvas 会放在 zlevel 小的 Canvas 的上面
        z:2,                                         //所属组件的z分层,z值小的图形会被z值大的图形覆盖
        left:"center",                              //组件离容器左侧的距离,'left', 'center', 'right','20%'
        top:"top",                                   //组件离容器上侧的距离,'top', 'middle', 'bottom','20%'
        right:"auto",                               //组件离容器右侧的距离,'20%'
        bottom:"auto",                              //组件离容器下侧的距离,'20%'
        orient:"vertical",                        //图例排列方向
        padding:5,                                   //图例内边距,单位px  5  [5, 10]  [5,10,5,10]
        backgroundColor:"transparent",            //标题背景色
        borderColor:"#ccc",                         //边框颜色
        borderWidth:0,                               //边框线宽
        textStyle:mytextStyle,                      //文本样式
        formatter: function (value) {                //标签的格式化工具。
            return 'aaaa' + value;                   // 范围标签显示内容。
        }
    }
];

地图轮播

设置定时器,通过下标来控制显示的区域,

echarts 实例中的 dispatchAction 方法可以触发组件的高亮、tooltip 提示框等事件,

还可以通过 dispatchAction 触发 downplay 事件取消指定的高亮数据。

data(){
	return {
    ...
    index:-1,
    timer:null
  }
}
mounted(){
  // 地图组件挂载后触发地图的轮播事件
  this.init()
  this.mapActive()
  this.mouseEvents()
}
  
beforeDestroy(){
  // 一定要记得清除定时器
  clearInterval(this.timer)
},
  
methods:{
    init(){
      ...
    },
    // 地图轮播
    mapActive () {
      const dataLength = cityData.length
      if (this.timer) {
        // 停止定时器,清除之前的高亮
        clearInterval(this.timer)
        this.timer = ''
      }
      // 用定时器控制高亮
      this.timer = setInterval(() => {
        // 清除之前的高亮
        this.charts.dispatchAction({
          type: 'downplay',
          seriesIndex: 0,
          dataIndex: this.index,
        })
        if (this.index === dataLength - 1) {
          this.index = 0
        } else {
          this.index++
        }
        // 当前下标高亮
        this.charts.dispatchAction({
          type: 'highlight',
          seriesIndex: 0,
          dataIndex: this.index,
        })
        this.charts.dispatchAction({
          type: 'showTip',
          seriesIndex: 0,
          dataIndex: this.index,
        })
      }, 3000)
    },
  
    // 鼠标移入移出
    mouseEvents () {
      // 鼠标划入时高亮当前所在的区域
      this.charts.on('mouseover', (value) => {
        if (this.timer) {
          // 停止定时器,清除之前的高亮
          clearInterval(this.timer)
          this.timer = ''
        }
        this.charts.dispatchAction({
          type: 'downplay',
          seriesIndex: 0,
          dataIndex: this.index,
        })
        
        this.charts.setOption(this.option, true)

        // 当前下标高亮
        this.charts.dispatchAction({
          type: 'highlight',
          seriesIndex: 0,
          dataIndex: value.dataIndex,
        })
      })
      // 鼠标划出重新定时器开始
      this.charts.on('mouseout', () => {
        this.mapActive()
      })
    },
}

地图的实现效果

地图的联动

相信大家读到这里对地图的配置都有一定的了解,那对于双地图的联动就更容易实现啦!

在这里我简单的介绍一下地图联动功能的实现思路:

在联动切换的过程中主要涉及到的是组件间的传值问题,由于父组件中高亮区域的省份发生了变化触发了子组件的省份地图发生对应的更新。

  1. 在全国地图(父组件)中点击切换省份地区时当前所展示的高亮省份发生变化

  2. 子组件监听到地区发生变化后动态地更新注册对应的省级地图,并调用 echarts 实例提供的 setOption() 重置子组件的地图数据。

总结

这篇文章主要记录了我在实现地图可视化过程中的几个配置问题,

其中包括地图的注册,根据不同形式的 json 数据采用对应的获取方式;以及关闭 visualMap 组件的展示只用于显示地区数据的色块;关闭地图自带的点击样式;还有实现最后的地图轮播功能。

往期精彩:

想要实现散点图轮播效果的同学可以参考前面的这篇文章:

控制台报错 -- setInterval 超时警告 !!!(散点轮播过程中产生的报错排查)

以上 希望对大家能有所帮助 😜