ECharts + geoJSON 绘制地图_下钻功能_Vue

2019-09-27  本文已影响0人  MercuryWang

这篇文章会介绍「 H5 + 原生 JS」和「 封装 React 组件」中两种实现方式。

「 H5 + 原生 JS」

参考文章(1)中的代码已经实现了展示全国地图和点击省市自治区下钻的功能,但是每个区域的颜色是一样的。于是又结合了 参考文章(2)。效果图:

---- 源码地址

参考文章:
HTML5 Canvas实现中国地图 可展开地级市子地图
echarts实现中国地图数据展示

绘制 geoJSON 地址: http://geojson.io/

封装 React 组件

绘制地图调用的 echarts.registerMap(geoJSON) 这一方法,geoJSON 的具体实现请移步至 echarts搞定各种地图(想怎么画就怎么画)

1. 封装 geoJSON 数据,结构大致如下:

{
  "anhui": {
    "type": "FeatureCollection",
    "features": [
      {
        "id": "340100",
        "type": "Feature",
        "geometry": {
          "type": "Polygon",
          "coordinates": [
            "[NGHEDAHAPCFMPGTCFULUNGFELKJGDG@ECKMIGIBE@....."
          ],
          "encodeOffsets": [[119842, 32007]]
        },
        "properties": {
          "cp": [117.283042, 31.86119],
          "name": "合肥市",
          "childNum": 1
        }
      },
      {"id": ........},
    ]
}

2. 创建引入 geoJSON 文件的中间文件:

import {anhui} from './province/anhui';
import {aomen} from './province/aomen';
import {beijing} from './province/beijing';
// etc. 

export default [
  {
    anhui,
    aomen,
    beijing,
   // etc.
  },
];

3. 封装 Map 文件

import React, {Component} from 'react';
import echarts from 'echarts';
import styles from './Map.less';
import * as mapdata from './ProvinceData';
import {china} from './MapData/China';
import * as province from './MapData/province.js';

class CMap extends Component {
  componentDidMount() {
    this.setState({
      province,
    });
    china(echarts);
    this.initEcharts('china', '中国');
  }
  // 初始化echarts
  initEcharts(pName, Chinese_) {
    var myChart = echarts.init(document.getElementById('china-map'));
    var oBack = document.getElementById('back');
    var option = {
      title: {
        text: Chinese_ || pName,
        left: 'center',
      },
      tooltip: {
        trigger: 'item',
        formatter: '{b}<br/>{c}',
      },
      //左侧小导航图标
      visualMap: {
        show: true,
        x: 'left',
        y: 'top',
        splitList: this.props.splitList,
        color: ['#3D74CC', '#5A8EE0', '#6FA4F7', '#80B1FF', '#99C0FF', '#B3D0FF'],
      },

      series: [
        {
          name: Chinese_ || pName,
          type: 'map',
          mapType: pName,
          roam: false, //是否开启鼠标缩放和平移漫游
          data: this.props.mapData,
          top: '3%', //组件距离容器的距离
          zoom: 1.1,
          selectedMode: 'single',

          label: {
            normal: {
              show: true, //显示省份标签
              textStyle: {color: '#585858', fontSize: 12}, //省份标签字体颜色
            },
            emphasis: {
              //对应的鼠标悬浮效果
              show: true,
              textStyle: {color: '#aaa'},
            },
          },
          itemStyle: {
            normal: {
              borderWidth: 0.5, //区域边框宽度
              borderColor: '#A6E1FF', //区域边框颜色
              areaColor: '#fff', //区域颜色
            },

            emphasis: {
              borderWidth: 0.5,
              borderColor: '#FFD1A3',
              areaColor: '#FFDAA6',
            },
          },
        },
      ],
    };

    myChart.setOption(option);

    myChart.off('click');
    let _this = this;
    if (pName === 'china') {
      // 全国时,添加click 进入省级
      myChart.on('click', function(param) {
        for (var i = 0; i < mapdata.provincesText.length; i++) {
          if (param.name === mapdata.provincesText[i]) {
            //显示对应省份的方法
            const pName = mapdata.provinces[i];
            echarts.registerMap(mapdata.provincesText[i], _this.state.province.default[0][pName]);
            _this.initEcharts(mapdata.provincesText[i]);
            break;
          }
        }
        if (param.componentType === 'series') {
          var provinceName = param.name;
        }
      });
    } else {
      // 省份,添加双击 回退到全国
      myChart.on('dblclick', function() {
        _this.initEcharts('china', '中国');
      });
    }
  }

  render() {
    return (
      <div>
        <button
          onClick={() => {
            this.initEcharts('china', '中国');
          }}
        >
          返回全国
        </button>
        <div
          style={{marginTop: '20px', width: '100%', height: document.body.clientHeight * 0.7}}
          id="china-map"
        />
      </div>
    );
  }
}

export default CMap;

4. ProvinceData 数据:

export var provinces = [
  "shanghai",
  "hebei",
 // etc.
];

export var provincesText = [
  "上海",
  "河北",
  "山西",
// etc.
];

export var seriesData = [
  { name: "北京", value: "100" },
  { name: "天津", value: randomData() },
  { name: "上海", value: randomData() },
  { name: "重庆", value: randomData() },
// 其他城市数据.....
}];

function randomData() {
  return Math.round(Math.random() * 500);
}

5. 引用组件

import CMap from './Map.js';

<CMap mapData={mapData} />;

2020 更新升级版:

Vue 组件封装

其实 React 封装也是同理的,之前的版本是简易封装,看着就 low,这一版更新了比较多:

1. 准备 GEOJSON 数据

地图的 JSON 数据可以在这里下载
json-data/map

2. 准备 seriesData 数据

export const seriesData = [
  { name: '北京', value: '100' },
// ...
]

export const provincesdata = [
  { name: '朝阳市', value: randomData() },
//...
}

function randomData() {
  return Math.round(Math.random() * 500)
}

完整代码请参考:json-data/map/emap.js

3. 封装组件

// component/Map.vue

<template>
  <div class="container">
    <div ref="Map" :style="{ height: height, width: width }"></div>
    <el-button
      v-show="ifShowButton"
      class="primary-button"
      type="primary"
      @click="backToWholeNation"
      >返回全国</el-button
    >
  </div>
</template>

<script>
import echarts from 'echarts'
import { mapPath } from '@/static/js/util'
import { getTheme } from '@/static/js/theme'

export default {
  components: {},
// 定义可以接收的属性
  props: {
    lazyResize: {
      type: Number,
      default: 200
    },
    width: {
      type: String,
      default: '80vw'
    },
    height: {
      type: String,
      default: '80vh'
    },
  // map 的 option 都是由此 config 转换
    config: {
      type: Object,
      required: true
    }
  },

  data() {
    return {
      chart: null,
      option: null,
      mapInfo: {},
      mapKey: '中国',
      baseOption: {},
      ifShowButton: false
    }
  },

  mounted() {
    this.restoreChart()
  },

  beforeDestroy() {
    if (this.chart instanceof Object) {
      this.chart.clear()
      this.chart.dispose()
    }
    this.chart = null
    this.baseOption = null
  },

  methods: {
// 返回全国
    backToWholeNation() {
      this.mapKey = '中国'
      this.ifShowButton = false
      this.convertMapOption(this.$props.config, this.mapKey)
      this.getGeoJson().then(() => this.refreshChart())
    },
// 返回默认的 option 配置项
    getBaseOption() {
      return {
        textStyle: {},
        title: {},
        tooltip: {},
        legend: { show: false },
        dataset: [],
        series: []
      }
    },

    // 根据 props 的 config 配置 option
    convertMapOption(config, mapKey) {
      this.baseOption = this.getBaseOption()
      const { seriesData, provincesdata } = config

      // 地图标题
      this.baseOption.title = {
        top: 'top',
        left: 'center',
        text: mapKey,
        textStyle: {
          color: '#f3f3f3'
        }
      }

      // 视觉映射组件配置
      this.baseOption.visualMap = {
        type: 'continuous',
        top: 'center',
        left: 'left',
        calculable: true,
        color: [
          '#3b4994',
          '#8c62aa',
          '#a5add3',
          '#be64ac',
          '#5ac8c8',
          '#ace4e4'
        ]
      }

      // 提示框
      this.baseOption.tooltip = {
        trigger: 'item',
        formatter: '{b}<br/>{c}'
      }

      // 数据
      this.baseOption.series = [
        {
          name: mapKey,
          type: 'map',
          map: mapKey,
          roam: true,
          data: mapKey === '中国' ? seriesData : provincesdata,
          left: mapKey === '海南' ? '80%' : 'center',
          top: mapKey === '海南' ? '215%' : 'center',
          zoom: mapKey === '海南' ? 6 : 1.1,
          scaleLimit: {
            min: 0.5,
            max: 20
          },
          showLegendSymbol: false,
          label: {
            show: true
          },
          emphasis: {
            label: {
              color: '#545454'
            },
            itemStyle: {
              areaColor: null
            }
          },
          nameMap: {
            中华人民共和国: '中国'
          }
        }
      ]
    },

    // 初始化图表
    initChart() {
      this.chart = echarts.init(this.$refs.Map, getTheme(this.$props.config))
     
      this.chart.on('click', (params) => {
        // 这里做了限制,仅可下钻一级,如果有地级市的下属区域数据,可以改造这个方法
        if (this.mapKey === '中国') {
          this.mapKey = params.name
          this.ifShowButton = true
          this.convertMapOption(this.$props.config, this.mapKey)
          this.getGeoJson().then(() => this.refreshChart())
        }
      })
    },

    // 绘制图表
    refreshChart() {
      if (!this.chart) return false
      this.chart.setOption(this.baseOption || {}, true)
      // 添加根据视口缩放重绘功能
      if (this.lazyResize) {
        window.onresize = () => {
          setTimeout(() => {
            this.chart.resize()
          }, this.lazyResize)
        }
      }
    },

    // 根据 mapKey 获取 JSON 数据
    getGeoJson() {
    // 这个方法封装在 一个 util.js 文件,可以参考接下来的一段代码
      const mapInfo = mapPath[this.mapKey]
      return new Promise((resolve, reject) => {
        if (mapInfo instanceof Object) {
          if (mapInfo.registered) {
            resolve(this.mapKey)
            return this.mapKey
          }
        } else {
          return false
        }

        if (mapInfo instanceof Object && mapInfo.key) {
          import(`@/static/json-data/map/${mapInfo.filePath}.json`)
            .then((res) => {
              echarts.registerMap(this.mapKey, res)
              mapInfo.registered = true
              resolve(this.mapKey)
              return this.mapKey
            })
            .catch((error) => {
              throw error
            })
        } else {
          reject(false)
          return false
        }
      })
    },

    async restoreChart() {
      this.convertMapOption(this.$props.config, this.mapKey)
      await this.getGeoJson()
      this.initChart()
      this.refreshChart()
    }
  }
}
</script>
<style lang="less" scoped>
.container {
  height: 80vh;
  margin: 10vh 2vw;
  position: relative;
  .primary-button {
    position: absolute;
    top: 0;
    left: 5vw;
  }
}
</style>

获取 mapPath 的完整代码请参考 js/util.js

// util.js
export const mapPath = {
  中国: {
    key: 'china',
    name: '中国',
    filePath: 'china',
    registered: false
  },
//...
}

4. 引用组件

// pages/Map.vue
<template>
    <Map :config="config" :lazy-resize="lazyResize" />
</template>

<script>
import { seriesData, provincesdata } from '@/static/json-data/map/emap.js'
import Map from '@/components/Map.vue'

export default {
  components: {
    Map
  },

  data() {
    return {
      config: { seriesData, provincesdata, theme: 'default', mapKey: '中国' },
      lazyResize: 200,
    }
  },
}
</script>
上一篇下一篇

猜你喜欢

热点阅读