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