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...

相关推荐
_codeOH6 小时前
Vue 3 vs React 19:框架还在卷,核心原理就这些
前端·vue.js
英勇无比的消炎药7 小时前
新手必看玩转TinyRobot一定要避开这些坑
前端·vue.js
英勇无比的消炎药7 小时前
别再盲目混用AI组件库和传统组件库差距原来这么大
前端·vue.js
英勇无比的消炎药9 小时前
前端提效神器全新AI组件库TinyRobot改写日常开发模式
前端·vue.js
英勇无比的消炎药10 小时前
前端提效神器TinyRobot
前端·vue.js
CDwenhuohuo10 小时前
uni 背景色渐变 全屏
前端·javascript·vue.js
爱怪笑的小杰杰10 小时前
Vue 项目交付第三方开发,如何隐藏核心 JS 源码?
前端·javascript·vue.js
小二·10 小时前
Vue 3 组合式 API 进阶实战
前端·javascript·vue.js
rising start11 小时前
九、vue3 组件通信:全场景详解
前端·vue.js·typescript
编程技术手记12 小时前
Vue Scoped CSS 与动态创建 DOM 的兼容性问题
前端·css·vue.js