vue3+echarts绘制某省区县地图

vue3+echarts绘制某省区县地图

工作中经常需要画各种各样的图,echarts是使用最多的工具,接近春节,想把之前画的echarts图做一个整合,方便同事和自己随时使用,因此用vue3专门写了个web项目,考虑之后不断完善

其中有这么个需求,需要展示某省各区县的数据,写在vue3项目中,最终展示结果如下:

大体的思路如下:

  1. 在阿里云dataV数据可视化平台获取数据
  2. 整合某省各区县的数据成为一个单独的文件
  3. echarts中注册这个省的地图
  4. echarts画图

主要用的程序语言是JavaScript和Python

下面详细介绍,有些技术细节也是自己经常遇到的,通过这段时间强化训练,感觉对echarts越来越熟练了

一、阿里云dataV地图数据获取

首先上地址,阿里云数据可视化平台,感谢阿里和高德提供如此牛逼的工具

然后选择点击自己所需的省份,比如上面图示的河北

接下来依次点击河北省各地级市,比如我点了石家庄,此时右侧出现了一个json链接,如下图,复制那个链接

如果浏览器装了解析json文件的插件,就会显示这个json文件的数据,如果安装插件,应该会直接把这个json文件下载下来,json数据如下:

接下来,依次去点击河北其他城市的地图,并获取数据,也可以写个爬虫的程序,挺简单的

二、将各地市的数据,整合成一个省的数据

其实思路就是把单个json中的features提取出来,然后整合到一个json文件中去

把上一步下载好的所有文件放到一个目录中,如folder

接下来用Python处理一下

python 复制代码
    features = []  # 初始化 features 列表
    for file in os.listdir(folder):
        filename = os.path.join(folder, file)
        try:
            with open(filename, 'r', encoding='utf-8') as f:
                data = json.load(f)
                features.extend(data.get('features', []))
        except (IOError, json.JSONDecodeError) as e:
            print(f"Error reading JSON file {filename}: {e}")

    json_file = {
        "type": "FeatureCollection",
        "features": features    
    }

    # 导出为 JSON 文件
    output_file_path = 'hebei_combined.json'
    with open(output_file_path, 'w', encoding='utf-8') as output_file:
        json.dump(json_file, output_file, ensure_ascii=False, indent=2)

通过处理后,得到的数据样式如下:

我不太喜欢在vue项目中直接使用json,因为很多情况下都需要异步引入,对于没有后端的项目,写起来比较费劲。更为致命的是,echarts对各种异步的操作非常不友好,经常在等待数据的时候,发现数据还没有返回,就会各种报错。我更倾向于把数据写入到js文件中,然后对外暴露,实际上这个项目我也是这么操作的,我把json里的内容放进同名js文件中,然后按需向外暴露,对象名为hebeiAreas

三、echarts注册地图

echarts中注册地图非常简单,就两步:

  1. 导入地图数据
  2. 注册

体现在程序中如下:

javascript 复制代码
import * as echarts from "echarts";
import { hebeiAreas } from '@/assets/js/areasOfProvince/hebei_combined'
// 注册
echarts.registerMap('proMap', hebeiAreas)

这里的注册需要写在正确的地方,如果只画一个图,写在哪里都无所谓,如果涉及到多个省份的切换,我建议写在切换成功的地方,或者是重绘地图的地方

四、绘图以及踏过的坑

绘图就是正儿八经写代码,我先上完整代码

html 复制代码
<template>
    <div class="container">
        <div class="top">
            <el-select v-model="province" placeholder="请选择省份" @change="choosePro" style="width: 120px">
                <el-option v-for="(item, index) in provinces" :key="index" :label="item.label"
                    :value="item.value"></el-option>
            </el-select>
            <el-button type="primary" style="margin-left: 10px;" @click="changeData">更换数据</el-button>
            <input ref="input" type="file" style="display: none" @change="handleFileChange" />

        </div>
        <div class="proMap" ref="proMap">

        </div>
    </div>
</template>

<script setup>
import { ref, onMounted, h } from 'vue'
import { provinces } from './data/provinceName'
import { ElMessage, ElNotification } from 'element-plus'
import * as echarts from "echarts";
import { areas } from '@/assets/js/areas'
import { hebeiAreas } from '@/assets/js/areasOfProvince/hebei_combined'

const province = ref('hebei')
const provinceZH = ref('河北')
const provinceCode = ref('13')
const proMap = ref()

const drawData = ref([])
const maxData = ref(100)

const getMaxData = () => {
    const arr = []
    arr.push(drawData.value.map(item => item.value))
    maxData.value = Math.max(...arr[0])
}

const getData = () => {
    const areasOfCurrentProvince = areas.filter(item => item.provinceCode == provinceCode.value)
    areasOfCurrentProvince.forEach(item => {
        drawData.value.push({
            name: item.name,
            value: Math.floor(Math.random() * 101)
        })
    });
    getMaxData()
}

// 更换省份
const choosePro = () => {
    console.log(province.value)
    if (province.value != 'hebei') {
        ElNotification({
            title: '提醒',
            message: h('i', { style: 'color: teal' }, '省份到区县分块需要处理大量数据,功能待后期完成,现在只做了河北的'),
            duration: 0
        })
        province.value = 'hebei'
    }
}
// 更换数据
// 隐藏输入框的dom
const input = ref()
const changeData = () => {
    ElNotification({
        title: '提醒',
        message: h('i', { style: 'color: teal' }, '请务必使用当前省份下的区县数据,否则无法显示正确的数据'),
        duration: 0
    })

    input.value.click()
}
const handleFileChange = async event => {
    const file = event.target.files[0]
    const reader = new FileReader()
    reader.readAsText(file, "UTF-8")
    reader.onload = async (evt) => {
        const fileString = await evt.target.result
        const count = fileString.trim().split('\n').length
        console.log(count)
        const handleData = []
        for (let i = 0; i < count; i++) {
            const fileline = fileString.split("\n")[i].split('\t')
            handleData.push({ name: fileline[0], value: parseInt(fileline[1]) })
        }
        // 更换数据
        drawData.value = handleData

        getMaxData()
        drawProMap()
    }
}
// 画地图相关
let initMap
const drawProMap = () => {
    echarts.registerMap('proMap', hebeiAreas)
    if (initMap != null && initMap != "" && initMap != undefined) {
        initMap.dispose(); //销毁
    }
    initMap = echarts.init(proMap.value)
    initMap.setOption({
        backgroundColor: "transparent", // 设置背景色透明
        tooltip: {
            show: true,
        },
        
        visualMap: {
            text: ["", ""],
            showLabel: true,
            left: "200",
            bottom: "100",
            min: 0,
            max: maxData.value,
            inRange: {
                color: ["#edfbfb", "#b7d6f3", "#40a9ed", "#3598c1", "#215096"],
            },
            // splitNumber: 5,
            seriesIndex: "0",
        },
        series: [
            {
                type: "map",
                map: 'proMap',
                tooltip: {
                    trigger: 'item',
                    formatter: function (params) {
                        // params 包含了鼠标悬浮时的相关信息
                        return params.name + '<br/>' + '数值: ' + params.value;
                    }
                },
                zoom: 1,
                label: {
                    show: false, // 显示地市名称
                    color: "#000",
                    align: "center",
                },
                top: "10%",
                left: "center",
                aspectScale: 0.75,
                roam: true, // 地图缩放和平移
                itemStyle: {
                    borderColor: "#3ad6ff", // 省分界线颜色  阴影效果的
                    borderWidth: 1,
                    areaColor: "#F5F5F5",
                    opacity: 1,
                },
                // 控制鼠标悬浮上去的效果
                emphasis: {
                    // 聚焦后颜色
                    disabled: false, // 开启高亮
                    label: {
                        align: "center",
                        color: "#ffffff",
                    },
                    itemStyle: {
                        color: "#ffffff",
                        areaColor: "#0075f4", // 阴影效果 鼠标移动上去的颜色
                    },
                },
                z: 2,
                data: drawData.value,
            }
        ]
    })
    window.addEventListener("resize", () => {
        initMap.resize();
    });
}

onMounted(() => {
    getData()
    setTimeout(() => { drawProMap() }, 200)
})
</script>

<style lang="scss" scoped>
.top {
    padding: 5px;
    width: 100%;
    box-shadow: rgba(17, 17, 26, 0.1) 0px 0px 16px;
}

.proMap {
    height: 95%;
    width: 95%;
}
</style>

以上代码有自己踏过的不少坑,我都说明一下,肯定还有其他坑,一句话,echarts全是坑

  1. getData()是生成地图对应数据的方法,我这里用了随机数,数据格式如下:

    javascript 复制代码
    [
        {name: '涞源县', value: 100},
        ....
    ]

    就是由key为name和value对象组成的数组

    getMaxData()是获取上面数组中的value的最大值,这主要是绘图的时候,图例范围的最大值设置

    drawProMap()是绘制地图的方法

  2. 坑1:注意onMounted钩子中的写法:

    javascript 复制代码
    onMounted(() => {
        getData()
        setTimeout(() => { drawProMap() }, 200)
    })

    挂载组件之前,先要获取数据,然后组件出现,就应该有图出现,这里我设置了0.2s的延时画图,原因是需要先等dom渲染完成后再画图,不然会直接报错

  3. 坑2:画图dom的宽和高必须要先设置,看我的样式:

    css 复制代码
    .proMap {
        height: 95%;
        width: 95%;
    }

    这必须写,不然图出不来,还会报警说无法获取dom的宽高

  4. 坑3:地图dom的初始化问题:

    看相关的代码

    javascript 复制代码
    // 画地图相关
    let initMap
    const drawProMap = () => {
        echarts.registerMap('proMap', hebeiAreas)
        if (initMap != null && initMap != "" && initMap != undefined) {
            initMap.dispose(); //销毁
        }
        initMap = echarts.init(proMap.value)
        /*
        省略其他代码
        */
    }

    一般情况下,可能我们会在画图的时候,直接就是:

    javascript 复制代码
    const initMap = echarts.init(proMap.value)

    上来就直接初始化画图的dom,可能的情况是,如果是在相同的dom上重绘echarts图,控制台就会报警(并非报错,效果会正常出现),说这个dom上本来就存在echarts图,所以在初始化之前,正确的操作是判断dom上的echarts图是否占用,占用的话,就销毁,也就是initMap.dispose();

  5. 从上面的代码可以看出,省份是可以选择的,数据也是可以改的

    • 先说改数据。改数据的逻辑是设定一个隐藏的input dom元素,为什么要用input,因为input可以打开文件,如下代码,其中type="file"就是打开文件,change是文件改变的事件

      html 复制代码
      <input ref="input" type="file" style="display: none" @change="handleFileChange" />

      由于这个dom是隐藏的(style="display: none"),所以打开文件应该是由按钮来控制,也就是下面这行代码

      html 复制代码
      <el-button type="primary" style="margin-left: 10px;" @click="changeData">更换数据</el-button>

      它俩的逻辑关系是:

      • 点击按钮,隐藏的input按钮实现点击事件,如下代码:

        javascript 复制代码
        const input = ref()
        const changeData = () => {
            ElNotification({
                title: '提醒',
                message: h('i', { style: 'color: teal' }, '请务必使用当前省份下的区县数据,否则无法显示正确的数据'),
                duration: 0
            })
        
            input.value.click()
        }
      • 接下来就会触发input的文件打开功能,选定文件后,就会执行handleFileChange方法,在这个方法中,使用了处理txt文本文件的方法,需要注意其中的异步操作,并且有处理换行以及按tab分割的逻辑,这里需要根据个人的项目进行适配,处理好数据后,替换画图的数据即可,然后执行获取最大值和画图方法,相关代码如下:

        javascript 复制代码
        const handleFileChange = async event => {
            const file = event.target.files[0]
            const reader = new FileReader()
            reader.readAsText(file, "UTF-8")
            reader.onload = async (evt) => {
                const fileString = await evt.target.result
                const count = fileString.trim().split('\n').length
                console.log(count)
                const handleData = []
                for (let i = 0; i < count; i++) {
                    const fileline = fileString.split("\n")[i].split('\t')
                    handleData.push({ name: fileline[0], value: parseInt(fileline[1]) })
                }
                // 更换数据
                drawData.value = handleData
        
                getMaxData()
                drawProMap()
            }
        }
    • 再说切换省份。我的代码中并没有实现切换省份的逻辑,因为需要大量的数据支撑,也就是说要把全国34个省级行政区划(包括港澳台)的地图文件都获取,切换成功后,异步引入地图文件,并注册地图,然后画图,这一步我在其他地方实现过,同样存在坑

      坑4:异步导入json文件,首次绘图会出现报错,报错如下:

      Error: Invalid geoJson format coordinate.charCodeAt is not a function

      但是刷新页面就正常了,查了一下相关的资料,有人解释是:

      因为 echarts 会绘制解析 json 之后 执行 decode 方法 后 会将其 UTF8Encoding 的值 从 true 改为false,第二次绘制 时如果 为 false 则 不需要走 decode 方法,如果每次都是新引入的 json,那每次都走 decode 就会报错


      复制代码
                              版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

      原文链接:https://blog.csdn.net/m0_37805167/article/details/122553278

他建议是 Object.assign({}, json) 拷贝一次,解释是异步获取的数据是只读的,echarts无法更改,所以会报错,需要拷贝一下,确实在某些情况下能解决,但通过路由切换到需要画图的页面上来时,依然会报错,目前还没有找到靠谱的解决方案,可能不用json而是用js会解决这个问题,需要我来确认

相关推荐
Darling02zjh36 分钟前
GUI图形化演示
前端
Channing Lewis39 分钟前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖1 小时前
Web 架构之状态码全解
前端·架构
showmethetime1 小时前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
左钦杨2 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3
NaclarbCSDN3 小时前
Java集合框架
java·开发语言·前端