美文网首页
ECharts + geoJSON 绘制地图_下钻功能_Vue

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

作者: MercuryWang | 来源:发表于2019-09-27 13:39 被阅读0次

    这篇文章会介绍「 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>
    

    相关文章

      网友评论

          本文标题:ECharts + geoJSON 绘制地图_下钻功能_Vue

          本文链接:https://www.haomeiwen.com/subject/kioiuctx.html