效果图
我们先看一下效果图,大致知道最终是什么样的(若是有道友也要做类似的效果,可以在阅读完毕以后,直接复用哦😄)
大致需求如下
- 绘制出安徽省【以及省内的各个城市、区县】地图
- 市和区县都有对应的数据,使用3D柱状图展示
- 具有下钻功能和返回功能
- 数据柱状图的颜色的深浅,取决于数据的大小
实现步骤
1. 地图的依赖
绘制3D效果的地图这里我们采用Echarts5中的geo3D和bar3D,用到的依赖版本如下:
js
"dependencies": {
"echarts": "^5.3.2",
"echarts-gl": "^2.0.9",
"vue": "^3.3.4",
"element-plus": "2.0.4",
},
echarts-gl主要是专门做3D效果图,普通的可视化echarts够用了
2. 地图JSON信息字段简介
- 首先,绘制地图需要地图数据,地图数据一般是一个大的JSON, JSON中记录地图相关信息
- 地理边界行政区域是由一个个不规则的点连成线组成的,就像几何
geometry
图形中的多边形MultiPolygon
一样。所以地图的JSON数据中会有一个字段,记录了地图边界坐标coordinates
数据 - 当然这个大JSON数据也会记录其他的区域信息,比如区域行政编码、区域名称、区域中心点、是否有子集区域等信息。请看下图笔者的标注:
3. 地图JSON来源
- 普通地图需求的话,去阿里云提供的地图选择网站上下载就行了
- 网址:datav.aliyun.com/portal/scho...
- 图示:
笔者下载好,按照行政编码命名,放在本地目录下,如下图:
注意,若是需要更加精细的地图数据,比如地图要下钻到街道、乡镇啥的,阿里云就没法满足了。需要大家找收费地图软件,或者特殊的方式进行获取到地图数据。懂的都懂,不细说
4. 用一个Echarts组件,进行组件化开发
- 这里大家可以自己封装,或者使用 vue-echarts 官方支持的组件,笔者用的版本是
"vue-echarts": "^6.0.2",
- 组件化开发,提升效率
- 附上自己简单封装的,一个意思(这一块可跳过)
html
<template>
<div ref="eChaDom" :style="{ height: h }" />
</template>
<script setup>
import { watch, onMounted, onBeforeUnmount, shallowRef, defineEmits } from "vue";
import * as echarts from "echarts";
import debounce from 'lodash/debounce'
const emit = defineEmits(["click"]) // 定义emit的click事件用于抛出
const props = defineProps({
h: { // Echarts画布默认高度
type: String,
default: '360px'
},
options: { // Echarts画布默认配置项
type: Object,
default: () => ({})
},
theme: { // Echarts画布默认主题
type: String,
default: 'dark'
}
})
const eChaDom = shallowRef(null); // 画布挂载的实例dom
const chart = shallowRef(null) // Echarts实例
const init = () => {
chart.value = echarts.init(eChaDom.value, props.theme) // 初始化画布
chart.value.setOption(props.options); // 根据配置项画效果
chart.value.on('click', function (params) { // 绑定监听事件,并抛出
emit("click", params)
});
window.addEventListener('resize', debounce(resizeFn, 360)) // 监听resize做自适应
}
const resizeFn = () => {
chart.value.resize()
}
onMounted(() => {
init()
})
watch(
() => props.options, // 监听配置项,并重新绘制
(newOptions) => {
chart.value.clear() // 先移除旧的,再重画一个新的
chart.value.setOption(newOptions);
},
{ deep: true }
)
onBeforeUnmount(() => { // 销毁时移除监听resize事件
window.removeEventListener('resize', resizeFn)
})
</script>
5. 使用组件绘制地图
注意事项:
- 地图想要使用,要先拿到地图数据,再注册
- 注册完了,还要更改Echarts中的配置项,更改geo3D使用的map地图
- 更改后,再重绘即可看到更换后的新地图了
html
<template>
<div class="tenBox">
<eCha :options="options" @click="jump" h="600px" />
</div>
</template>
<script setup>
import * as echarts from "echarts"; // 这里引入echarts为了去注册地图
import "echarts-gl"; // 引入echarts-gl使用其中的geo3D和bar3D组件
import { reactive } from "vue";
import cloneDeep from 'lodash/cloneDeep'
import eCha from "@/components/eCha/index.vue"; // echarts二次封装的组件
// 默认是安徽省地图(先引入,再注册)
import geoJson from "./map/340000.json";
echarts.registerMap('340000', geoJson);
const options = { ... } // 配置项见下方
// 点击柱状图跳转下钻更换地图
const jump = async (params) => {
await changeMap(params.data.code, params.name) // 地图编码和地图名称
}
// 下钻更换地图
const changeMap = (mapCode, mapName) => {
return new Promise((resolve, reject) => {
import(`./map/${mapCode}.json`).then(data => {
echarts.registerMap(mapCode, data) // 下钻第一步,把引入的地图数据先注册一下
options.geo3D.map = mapCode // 然后再让geo3D使用这个地图
options.series[0].data = getBar3DData(data.default) // 变换一下bar3D的相关数据
resolve(true)
}).catch((err) => {
alert(`暂无【 ${mapName} 】地图数据,请自行下载`)
})
})
}
const randomNum = () => Math.random().toFixed(1) * 90 + 10; // 生成【1,100】之间的随机数用于填充3D柱状图的数据
const getBar3DData = (mapData) => {
const bar3DData = mapData.features.map((item) => {
let centroidList = cloneDeep(item.properties.centroid)
centroidList.push(randomNum())
return {
name: item.properties.name,
/**
* bar3D的data需要的数据是一个数组,数组每一项是对象,对象要有name和value属性,当然
* 也可以另外添加别的属性。其中的value属性值也是一个数组,数组中有三项,分别是:
* ['经度坐标','纬度坐标','经纬度坐标定位的点的数值']
* 经纬度坐标定位的点的数值的大小,即为bar3D柱状图的高度。
* */
value: centroidList,
code: String(item.properties.adcode) // 地图编码是字符串类型的
}
})
return bar3DData
}
options.series[0].data = getBar3DData(geoJson)
</script>
<style>
.tenBox { width: 100%; height: 90vh; position: relative; }
.btn { position: absolute; top: 12px; left: 12px; }
</style>
6. 对应options配置项
js
const options = reactive(
{
"renderer": "canvas", // canvas or svg
"backgroundColor": "#333", // 优先于theme="dark"
"tooltip": {
"show": true,
formatter(params) {
let [Longitude, latitude, nums] = params.value
let result =
"<div>" +
`<span style='font-weight: 700'>${params.seriesName}</span>` + '<br />' +
params.marker + '' + params.name + '<br />' +
'中心位置经度:' + Longitude + '<br />' +
'中心位置纬度:' + latitude + '<br />' +
'对应数量:' + nums + '人'
"</div>"
return result
}
},
"geo3D": {
// 注册需要使用的地图
"map": "340000",
// 地图每个区域的样式
"itemStyle": {
"color": "#007aff",
"opacity": 0.8,
"borderWidth": 2.4,
"borderColor": "#fff"
},
// 视觉控制相关
"viewControl": {
"autoRotate": false,
"autoRotateAfterStill": 3,
"distance": 160,
"minAlpha": 5,
"maxAlpha": 90,
"minBeta": -360,
"maxBeta": 360000,
"animation": true,
"autoRotateSpeed": 3
},
// 鼠标悬浮强调相关
"emphasis": {
"label": {
"show": true,
"color": "#000",
"fontSize": 18
},
"itemStyle": {
"color": "#3CABFA"
}
},
// 地图区域标题相关
"label": {
"show": true,
"position": "bottom",
"color": "#000",
"fontSize": 14,
"lineHeight": 18
},
// shading值为color时light配置无效
"shading": "lambert",
"light": {
"main": {
"intensity": 1,
"shadow": true,
"shadowQuality": "medium",
"alpha": 120,
"beta": 120
},
"ambient": {
"intensity": 0.6
},
"ambientCubemap": {
"diffuseIntensity": 66,
"specularIntensity": 97
}
},
"top": -54,
"left": 0,
},
"series": [
{
"name": "城市人口",
"type": "bar3D",
"minHeight": 4.2, // 坑,需要大于0,否则最低的柱状图渲染会出问题
"coordinateSystem": "geo3D",// 采用geo3D的坐标系统
"barSize": 2.4, // 柱状图的粗细
"shading": "lambert", // lambert比color看着更加真实
"bevelSize": 0.72, // 长方体向圆柱的过渡效果
"label": {
"show": true,
"distance": 0.36,
"color": "#333",
"textStyle": {
"fontWeight": "bold"
},
formatter(params) {
return params.value[2] + '人'
}
},
"data": [] // bar3D需要的数据
}
],
// 使用dataRange给Bar3D柱状图按照数值大小区间上颜色
"dataRange": {
"x": "left",
"y": "bottom",
"textStyle": {
"color": "#fff"
},
"splitList": [
{
"start": 76,
"label": "大于76",
"color": "#b80909"
},
{
"start": 51,
"end": 75,
"label": "51~75",
"color": "#e64546"
},
{
"start": 26,
"end": 50,
"label": "26~50",
"color": "#f57567"
},
{
"start": 0,
"end": 25,
"label": "0~25",
"color": "#ff9985"
}
],
"padding": [48, 0, 24, 18]
}
}
)
几个注意事项
1. geo3D搭配bar3D的点击事件只能在点击柱状图时,才会触发
- 如下图,点击地图区域没反应,点击柱子才有反应
- 查了不少资料,原因是echarts-gl对于点击事件支持的不到位
- 最终笔者得到结论:
- 若是要这种3D地图柱状图效果的话,那就接受只能点击柱子才去下钻
- 若是接受不了必须要点击柱子才能下钻,那就换成map3D等其他的地图,支持点击地图区域下钻的
2. bar3D得设置最小高度minHeight,否则值最小的3D柱子会变成一张纸
如下图:
关键代码配置项:
js
{
"name": "城市人口",
"type": "bar3D",
"minHeight": 4.2, // 坑...
"data": []
}
原因浅析:
- 若是不设置minHeight,Echarts在绘制3D柱子时,会把数值最小的那个柱子当成基准高度0
- 如高度分别是 [2, 5 ,8] , Echarts绘制就变成了 [0, 3, 6]了
- 假设我们设置最小高度minHeight为4.2,那么Echarts绘制就变成了 [6.2, 9.2 ,12.2]
- 这样有起始最小高度了,绘制就不会出问题了
笔者当时被这个问题困扰了一个多小时,尴尬了...
3. bar3D的data需要的数据格式要符合标准
如下的本例的格式附上(不符合标准如经纬度坐标对应数值,3D柱状图错位不显示之类的)
js
[
{
"name": "合肥市",
"value": [
117.360447,
31.762594,
82
],
"code": "340100"
},
{
"name": "芜湖市",
"value": [
118.13997,
31.160935,
37
],
"code": "340200"
},
{
"name": "蚌埠市",
"value": [
117.330324,
33.107951,
46
],
"code": "340300"
},
{
"name": "淮南市",
"value": [
116.773391,
32.47172,
91
],
"code": "340400"
},
{
"name": "马鞍山市",
"value": [
118.369758,
31.637138,
82
],
"code": "340500"
},
{
"name": "淮北市",
"value": [
116.74441,
33.720047,
28
],
"code": "340600"
},
{
"name": "铜陵市",
"value": [
117.566978,
30.888245,
82
],
"code": "340700"
},
{
"name": "安庆市",
"value": [
116.451502,
30.574825,
10
],
"code": "340800"
},
{
"name": "黄山市",
"value": [
118.076088,
29.904836,
100
],
"code": "341000"
},
{
"name": "滁州市",
"value": [
118.107896,
32.543514,
91
],
"code": "341100"
},
{
"name": "阜阳市",
"value": [
115.709049,
32.916536,
100
],
"code": "341200"
},
{
"name": "宿州市",
"value": [
117.213919,
33.860274,
19
],
"code": "341300"
},
{
"name": "六安市",
"value": [
116.23409,
31.659225,
10
],
"code": "341500"
},
{
"name": "亳州市",
"value": [
116.185025,
33.435253,
91
],
"code": "341600"
},
{
"name": "池州市",
"value": [
117.371465,
30.282082,
73
],
"code": "341700"
},
{
"name": "宣城市",
"value": [
118.857477,
30.684955,
73
],
"code": "341800"
}
]
4. 使用regions去控制geo3D的地图区域指定颜色
比如给 合肥市 区域背景设置为绿色,如下示例效果图
对应regions代码
js
"geo3D": {
// 注册需要使用的地图
"map": "340000",
"shading": "color", // lambert // shading值为color时light配置无效
"light": {
"main": { ...... },
"ambient": {...... },
},
// 控制代码
"regions": [
{
"name": "合肥市",
"itemStyle": {
"color": "green"
}
},
]
},
注意!注意!注意!
- 假如,使用regions控制背景色了,那么shading模式要改为color了,否则背景色会被"shading": "lambert" 对应的 light配置项干扰
- 再一个,shading值为color时light配置自动无效了,相当于没写了
- 所以,regions是搭配"shading": "color"使用的
这个地方也是一个坑,一定要注意哦。笔者当时被坑了好几个小时
5. 报错:Cannot read properties of null (reading 'getRoots') at ZRender2.clear (zrender.js:210:34)
报错截图:
报错原因:
这个是github的一个issue,不影响正常使用上生产的
issue地址:github.com/ecomfe/echa...
总结
- 编程大致就是:
- 学习规则
- 使用规则
- 进一步理解规则
- 最终自定义规则
规则与规则之间的组合使用,有一些限制...
github仓库代码附上
笔者推到github仓库了,欢迎大家去star一下哦。后续会在这个仓库中添加一些Echarts相关的代码效果文章啥的