Vue3+Echarts5使用geo3D搭配bar3D实现3D地图柱状图下钻效果

效果图

我们先看一下效果图,大致知道最终是什么样的(若是有道友也要做类似的效果,可以在阅读完毕以后,直接复用哦😄)

大致需求如下

  • 绘制出安徽省【以及省内的各个城市、区县】地图
  • 市和区县都有对应的数据,使用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来源

笔者下载好,按照行政编码命名,放在本地目录下,如下图:

注意,若是需要更加精细的地图数据,比如地图要下钻到街道、乡镇啥的,阿里云就没法满足了。需要大家找收费地图软件,或者特殊的方式进行获取到地图数据。懂的都懂,不细说

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相关的代码效果文章啥的

仓库地址:github.com/shuirongshu...

相关推荐
2401_8576009521 分钟前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_8576009521 分钟前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL22 分钟前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
轻口味27 分钟前
Vue.js 核心概念:模板、指令、数据绑定
vue.js
2402_8575834932 分钟前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake1 小时前
Vue3之性能优化
javascript·vue.js·性能优化
ddd君317742 小时前
组件的声明、创建、渲染
vue.js
前端没钱3 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
顽疲4 小时前
springboot vue 会员收银系统 含源码 开发流程
vue.js·spring boot·后端
羊小猪~~4 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5