先看效果展示图
目录
- 准备工作
- 一, 绘制3D地图
- 1,调用官网地址接口获取
- 2,去官网下载中国地图的json数据到本地,本地引入
- 二, 南海诸岛小窗化显示
- 1, 手动过滤掉,只保留小窗化的南海诸岛
- 2, 代码层面过滤掉,只保留小窗化的南海诸岛
- 三, 省、市、县三级地图下钻及回钻
- 1, 下钻
- 2, 回钻
- 四, 添加点位飞线图
- 五, 点位弹窗轮播展示
- 常见问题
- 1, 南海诸岛小窗不显示或者小窗和大图同时显示
- 2, 鼠标移入地图不显示弹窗,移入点位上显示弹窗
每部分的源码在文章最后的链接中
准备工作
不知道或者不熟悉的可以去官网看看: 点击前往Echarts官网
各个省、市、县的地图json下载,点击前往
插件下载
npm i echarts --save // 我的版本 ^5.2.0
一, 绘制3D地图
这种3D效果其实多层map对象集合组成的,通常在option里面的geo中
html
<div class="about"> <div id="myMap"></div> </div>
option配置项
option里面的控制3D效果的主要是这个geo模块,里面5个对象表示5层地图,层数越多,就越立体,一般三五层就差不多了,每个都偏移了一点点,从而形成的3D立体效果
optionMap: { .... geo: [ { map: 'china', aspectScale: 1, zoom: 0.9, layoutCenter: ['50%', '50%'], layoutSize: '100%', show: true, roam: false, label: { show: false, // 各个省市县的名字 color: '#fff', }, itemStyle: { // 每块的样式 areaColor: { type: 'linear', x: 1200, y: 0, x2: 0, y2: 0, colorStops: [ { offset: 0, color: 'rgba(3,27,78,0.75)', }, { offset: 1, color: 'rgba(58,149,253,0.75)', }, ], global: true, // 缺省为 false }, borderColor: '#c0f3fb', borderWidth: 0.8, }, emphasis: { itemStyle: { show: false, color: '#fff', areaColor: 'rgba(0,254,233,0.6)', }, label: { show: true, color: '#fff', }, }, }, // 重影 { type: 'map', map: 'china', zlevel: -1, aspectScale: 1, zoom: 0.9, layoutCenter: ['50%', '51%'], layoutSize: '100%', roam: false, silent: true, itemStyle: { borderWidth: 1, borderColor: 'rgba(58,149,253,0.8)', shadowColor: 'rgba(172, 122, 255,0.5)', shadowOffsetY: 5, shadowBlur: 15, areaColor: 'rgba(5,21,35,0.1)', }, }, { type: 'map', map: 'china', zlevel: -2, aspectScale: 1, zoom: 0.9, layoutCenter: ['50%', '52%'], layoutSize: '100%', roam: false, silent: true, itemStyle: { borderWidth: 1, borderColor: 'rgba(58,149,253,0.6)', shadowColor: 'rgba(65, 214, 255,0.6)', shadowOffsetY: 5, shadowBlur: 15, areaColor: 'rgba(5,21,35,0.1)', }, }, { type: 'map', map: 'china', zlevel: -3, aspectScale: 1, zoom: 0.9, layoutCenter: ['50%', '53%'], layoutSize: '100%', roam: false, silent: true, itemStyle: { borderWidth: 1, borderColor: 'rgba(58,149,253,0.4)', shadowColor: 'rgba(29, 111, 165,1)', shadowOffsetY: 15, shadowBlur: 10, areaColor: 'rgba(5,21,35,0.1)', }, }, { type: 'map', map: 'china', zlevel: -4, aspectScale: 1, zoom: 0.9, layoutCenter: ['50%', '54%'], layoutSize: '100%', roam: false, silent: true, itemStyle: { borderWidth: 5, borderColor: 'rgba(5,9,57,0.8)', shadowColor: 'rgba(29, 111, 165,0.8)', shadowOffsetY: 15, shadowBlur: 10, areaColor: 'rgba(5,21,35,0.1)', }, }, ], .... }, publicUrl: 'https://geo.datav.aliyun.com/areas_v3/bound/',
js
两种绘制方法
1,调用官网地址接口获取
import axios from 'axios' let myChartMap = null methods:{ // 初始化 async initChart() { //获取中国地图的json数据,直接拿过来用 let chinaGeoJson = await this.getGeoJson('100000_full.json') this.initEcharts(chinaGeoJson.data, 'china') }, //echarts绘图 initEcharts(geoJson, name) { // 注册3D地图 this.$echarts.registerMap(name, geoJson) myChartMap.setOption(this.optionMap) }, //获取地图json数据 async getGeoJson(jsonName) { return await axios.get(this.publicUrl + jsonName) }, }, mounted() { myChartMap = this.$echarts.init(document.getElementById('myMap')) this.initChart() }
2,去官网下载中国地图的json数据到本地,本地引入
本地引入的比较简洁点,但是在地图下面模块就不好用了,总不可能把全国的省市县地图json全下载下来吧,当然也可以,这样项目体积就比较大了
methods:{ initEcharts() { this.$echarts.registerMap('china', require('../util/china.json')) myChartMap.setOption(this.optionMap) }, }, mounted() { myChartMap = this.$echarts.init(document.getElementById('myMap')) this.initEcharts() }
看看效果图
哎,这里出现了一个问题,南海诸岛和南海诸岛小窗化一起出来了,以下有两个解决方法
二, 南海诸岛小窗化显示
1, 手动过滤掉,只保留小窗化的南海诸岛
手动删除海南省其余json,只保留海南省的. 和剔除海南省边界json
本地china.json
全局搜JD,删除properties对象就可以了.
手动将上面两块代码删除就可以小窗化显示了.
2, 代码层面过滤掉,只保留小窗化的南海诸岛
相同的部分就不再展示了
formatJson这个方法做的就是剔除海南省其余json,只保留海南省的. 和剔除海南省边界json
methods:{ // 南沙诸岛以缩略图展示 async formatJson(chinaGeoJson) { chinaGeoJson.features.forEach((v) => { if (v.properties && v.properties.name == '海南省') { v.geometry.coordinates = v.geometry.coordinates.slice(0, 1) } }) // 过滤掉海南诸岛边界线 chinaGeoJson.features = chinaGeoJson.features.filter((item) => item.properties.adcode !== '100000_JD') return chinaGeoJson }, // 初始化 async initChart() { let chinaGeoJson = await this.getGeoJson('100000_full.json') // this.initEcharts(chinaGeoJson.dat, 'china') // 过滤掉海南省其他部分的json之后的中国地图json let formatChinaGeoJson = await this.formatJson(chinaGeoJson.data) this.initEcharts(formatChinaGeoJson, 'china') }, ... }
过滤后,就能正常显示了,请看下图
三, 省、市、县三级地图下钻及回钻
原理: 通过点击事件点击的省份,获取该省的行政区划编码—>以这个编码作为参数去调接口获取该省的地图json—>重新渲染到页面上—>递归操作
1, 下钻
publicUrl: 'https://geo.datav.aliyun.com/areas_v3/bound/', // 地图json接口地址 level: 0, // 当前层级 historyData: [], // 缓存上一级的信息 adName: '中国', // json对应的中文name alladcode: '', // 中文名对应的行政区划编码 ..... methods:{ ...... // 初始化 async initChart() { // 获取地图name对应的adcode行政区划编码 let geoJson = await this.getGeoJson('all.json') this.alladcode = geoJson.data // 获取中国地图json,南海诸岛小窗化 let chinaGeoJson = await this.getGeoJson('100000_full.json') let formatChinaGeoJson = await this.formatJson(chinaGeoJson.data) this.initEcharts(formatChinaGeoJson, 'china') }, // 格式图表,数据处理 formatChart(geoJson, name) { // 页面上回钻按钮的name this.adName = name == 'china' ? '中国' : name // 注册3D地图 this.$echarts.registerMap(name, geoJson) // 下钻的时候地图大小设置,不设置会有部分地图超出屏幕 this.optionMap.geo.forEach((v) => { v.map = name v.layoutSize = name == 'china' ? '180%' : '100%' }) myChartMap.clear() myChartMap.setOption(this.optionMap) }, //echarts绘图 initEcharts(geoJson, name) { this.formatChart(geoJson, name) // 存储当前下钻的层级和地图json信息 this.level++ this.historyData.push({ geoJson, name }) // 点击事件 myChartMap.off('click') myChartMap.on('click', (params) => { // 获取中文名对应的行政区划编码,调接口用的 let clickRegionCode = this.alladcode.filter((areaJson) => areaJson.name === params.name)[0].adcode // 没有区县的地级市,东莞,中山,儋州,三沙,嘉峪关, 这5个市比较特殊,没有下一级区县,在获取地图json官网接口可以看出来,中国,省,市,这三级的地图json的接口是 '行政区划+_full.json'的,而最后一级区县的地图json接口是 '行政区划+.json'.由于这5个地级市没有区县,所以他们就是最后一级,所以他们接口参数格式是'行政区划+.json' let adcodeArr = ['460400', '460300', '441900', '442000', '620200'] let regJson = `${clickRegionCode}_full.json` // 区县或者没有区县的地级市,行政区划最后两位不是00的就是最后一级,adcodeArr除外 if (clickRegionCode.toString().slice(-2) != '00' || adcodeArr.includes(clickRegionCode.toString())) { regJson = `${clickRegionCode}.json` } // 调接口 this.getGeoJson(regJson) .then((regionGeoJson) => { // 递归 this.initEcharts(regionGeoJson.data, params.name) }) .catch((err) => { // 异常处理 this.level = 0 this.historyData = [] this.initEcharts(require('@/util/china.json'), 'china') }) }) }, //获取地图json数据 async getGeoJson(jsonName) { return await axios.get(this.publicUrl + jsonName) },
2, 回钻
上面下钻不是缓存了当前层的json信息吗,直接拿过来用就好了,接口都不用调.
回钻可以通过按钮点击进行回钻,也可以监听鼠标右键进行回钻,我这边就用按钮了,简单点
<el-button type="primary" size="mini" title="点击返回上一级" class="btn" @click="upside">{{ adName }}</el-button> ... methods:{ // 返回上一级 upside() { // 如果是最顶层就返回,没得回钻了 if (this.historyData.length <= 1) return // 回钻一层,level减减, historyData移出一层 this.level-- this.historyData.pop() let upData = this.historyData[this.level - 1] this.formatChart(upData.geoJson, upData.name) }, }
看看效果
四, 添加点位飞线图
每一条飞线都是由一组起始点的经纬度组成的,起点就是点位的经纬度,终点就是自定义的一组点位,可以项目所在地的经纬度.
项目里面就调接口获取点位就可以了,这里就用假数据展示了,只要数据格式一样就行
// 点位数据 this.resData = [ { name: '广州市', value: [113.2644, 23.1291], }, { name: '成都市', value: [104.0657, 30.6598], }, { name: '苏州市', value: [120.6195, 31.2995], }, { name: '北京市', value: [116.404, 39.9042], }, { name: '连云港市', value: [119.1676, 34.5934], }, { name: '南京市', value: [118.7674, 32.0415], }, { name: '杭州市', value: [120.1535, 30.2874], }, { name: '乌鲁木齐市', value: [87.6168, 43.7928], }, { name: '拉萨市', value: [91.11, 29.97], }, { name: '西安市', value: [108.953, 34.2779], }, { name: '南宁市', value: [108.32006, 22.82402], }, ] ... ... import { isPointInMultiPolygon } from '@/util/echartTool.js' // 这个是判断点位是否属于当前区域,地图下钻的时候,过滤掉不属于该区域的点位 series: [ { type: 'lines', // 飞线图 zlevel: 2, effect: { show: true, period: 3, //箭头指向速度,值越小速度越快 trailLength: 0.03, //特效尾迹长度[0,1]值越大,尾迹越长重 symbol: 'arrow', //箭头图标 symbolSize: 6, //图标大小 }, lineStyle: { color: '#EE5652', width: 1, //尾迹线条宽度 opacity: 1, //尾迹线条透明度 curveness: 0.3, //尾迹线条曲直度 }, data: [], symbol: ['none', 'circle'], //飞线起点终点点位样式 symbolSize: 10, // 飞线起点终点点位大小 }, { type: 'effectScatter', zlevel: 3, coordinateSystem: 'geo', emphasis: { label: { show: true, position: 'top', color: '#fff', formatter: '{b|{b}}', }, }, data: [], symbol: 'circle', symbolSize: [20, 10], itemStyle: { color: 'orange', shadowBlur: 10, shadowColor: 'orange', }, effectType: 'ripple', showEffectOn: 'render', //emphasis移入显示动画,render一开始显示动画 rippleEffect: { scale: 5, brushType: 'stroke', }, }, ], ..... //无关的代码就不展示了,上面都有 // 初始化 async initChart() { // 获取地图name对应的adcode行政区划编码 let geoJson = await this.getGeoJson('all.json') this.alladcode = geoJson.data let chinaGeoJson = await this.getGeoJson('100000_full.json') let formatChinaGeoJson = await this.formatJson(chinaGeoJson.data) // 鼠标移入点位的弹窗 let rich = { b: { color: '#fff', backgroundColor: { image: require('../../public/home/point.png'), }, padding: [20, 30], fontSize: 14, align: 'center', }, } this.optionMap.series[1].emphasis.label.rich = rich // 控制鼠标移入点位弹窗显示点位信息 this.initEcharts(formatChinaGeoJson, 'china') }, // 格式化点位飞线数据 filterLines(currentData) { let dataLines = [] let center = [120.537612, 31.276282] // 飞线图终点的地位经纬度 currentData.forEach((v) => { let lonLat = [v.value[0], v.value[1]] dataLines.push({ coords: [lonLat, center], // 高版本的飞线数据组 }) // 这个是低版本的 // dataLines.push([ // { // coord: lonLat, // 起始点 // value: 0, // }, // { // coord: center, // 中心点 // }, // ]) }) return dataLines }, // 格式图表 formatChart(geoJson, name) { // 过滤出当前地图的点位,过滤掉不属于改地图区域的点位 let currentData = this.resData.filter((v) => { return isPointInMultiPolygon([v.value[0], v.value[1]], geoJson.features) }) this.adName = name == 'china' ? '中国' : name this.$echarts.registerMap(name, geoJson) this.optionMap.geo.forEach((v) => { v.map = name v.layoutSize = name == 'china' ? '180%' : '100%' }) this.optionMap.series[0].data = this.filterLines(currentData) this.optionMap.series[1].data = currentData myChartMap.clear() myChartMap.setOption(this.optionMap) }, .....
echartTool.js 射线法判断点位是否属于某个区域内
// 判断当前经纬度是否在规定区域内 export function isPointInMultiPolygon(point, multiPolygons) { for (let polygon of multiPolygons) { let coordinates = polygon.geometry.coordinates if (isPointInAnyPolygon(point, coordinates)) { return true } } return false } export function isPointInAnyPolygon(point, polygons) { for (let polygon of polygons) { if (isPointInPolygon(point, polygon[0])) { return true } } return false } export function isPointInPolygon(point, polygon) { // 射线法判断点是否在多边形内部 let x = point[0], y = point[1] let inside = false for (let i = 0, j = polygon.length - 1; i < polygon.length; j = i++) { let xi = polygon[i][0], yi = polygon[i][1] let xj = polygon[j][0], yj = polygon[j][1] let intersect = yi > y !== yj > y && x < ((xj - xi) * (y - yi)) / (yj - yi) + xi if (intersect) { inside = !inside } } return inside }
看看效果
五, 点位弹窗轮播展示
定时器循环展示点位信息,当前点位高亮且显示弹窗,其余不显示.
.... series: [ { type: 'effectScatter', zlevel: 3, coordinateSystem: 'geo', emphasis: { label: { show: true, position: 'top', color: '#fff', formatter: '{b|{b}}', }, }, label: { show: false, position: 'top', color: '#fff', }, data: [], symbol: 'circle', symbolSize: [20, 10], itemStyle: { color: 'orange', shadowBlur: 10, shadowColor: 'orange', }, effectType: 'ripple', showEffectOn: 'render', //emphasis移入显示动画,render一开始显示动画 rippleEffect: { scale: 5, brushType: 'stroke', }, }, ], ... // 初始化 async initChart() { .... // 鼠标移入点位的弹窗 let rich = { b: { color: '#fff', backgroundColor: { image: require('../../public/home/point.png'), }, padding: [20, 30], fontSize: 14, align: 'center', }, } this.optionMap.series[0].emphasis.label.rich = rich this.optionMap.series[0].label.rich = rich this.initEcharts(formatChinaGeoJson, 'china') }, // 点位颜色高亮 highLightPoint(currentData, index) { currentData.forEach((v, key) => { let flag = key == index ? 'aqua' : 'orange' v.itemStyle = { color: flag, shadowColor: flag, } }) this.optionMap.series[0].label.formatter = (e) => { return e.name === currentData[index].name ? `{b|${e.name}}` : '' } }, // 格式图表 formatChart(geoJson, name) { // 过滤出当前地图的点位 let currentData = this.resData.filter((v) => { return isPointInMultiPolygon([v.value[0], v.value[1]], geoJson.features) }) this.adName = name == 'china' ? '中国' : name this.$echarts.registerMap(name, geoJson) this.optionMap.geo.forEach((v) => { v.map = name v.layoutSize = name == 'china' ? '180%' : '100%' }) this.optionMap.series[0].data = currentData // 点位颜色高亮,初始进入第一个高亮 this.highLightPoint(currentData, 0) this.optionMap.series[0].label.show = true myChartMap.clear() myChartMap.setOption(this.optionMap) if (charTimer) { clearInterval(charTimer) charTimer = null } // 点位大于2个才循环轮播 if (currentData.length > 1) { let i = 0 charTimer = setInterval(() => { i++ if (i >= currentData.length) i = 0 this.highLightPoint(currentData, i) myChartMap.setOption(this.optionMap) }, 1000 * 3) } }, ....
看效果
常见问题
1, 南海诸岛小窗不显示或者小窗和大图同时显示
小窗不显示大概率是注册3D地图的时候用的中文名.
this.$echarts.registerMap(name, geoJson) 绘制全国地图的时候这个name必须是china,写中国的话很可能导致南海诸岛不显示.
小窗和大图同时显示
就下图这种情况,南沙诸岛重复显示,上面第二大点已经给出解决方法了
2, 鼠标移入地图不显示弹窗,移入点位上显示弹窗
把全局的toolip关掉
.... option:{ tooltip: { show: false, }, series:[ ... { ... // 控制鼠标移入高亮,改变位置,就类似于弹窗了 emphasis: { label: { show: true, position: 'top', color: '#fff', formatter: '{b|{b}}', // 自定义样式 }, }, // 默认的弹窗样式 label:{ } .... }, ] }
所有示例代码已上传,点击前往下载