vue3+echarts绘制某省区县地图

news2024/9/29 3:27:35

vue3+echarts绘制某省区县地图

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

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

image-20240125173609220

大体的思路如下:

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

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

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

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

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

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

image-20240125174337829

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

image-20240125174601709

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

image-20240125174758844

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

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

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

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

接下来用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)

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

image-20240125175455169

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

三、echarts注册地图

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

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

体现在程序中如下:

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

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

四、绘图以及踏过的坑

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

<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()是生成地图对应数据的方法,我这里用了随机数,数据格式如下:

    [
        {name: '涞源县', value: 100},
        ....
    ]
    

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

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

    drawProMap()是绘制地图的方法

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

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

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

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

    .proMap {
        height: 95%;
        width: 95%;
    }
    

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

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

    看相关的代码

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

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

    const initMap = echarts.init(proMap.value)
    

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

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

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

      <input ref="input" type="file" style="display: none" @change="handleFileChange" />
      

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

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

      它俩的逻辑关系是:

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

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

        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会解决这个问题,需要我来确认

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1414114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

3个精美的wordpress律师网站模板

暗红色WordPress律师事务所网站模板 演示 https://www.zhanyes.com/qiye/23.html 暗橙色WordPress律师网站模板 演示 https://www.zhanyes.com/qiye/18.html 红色WordPress律所网站模板 演示 https://www.zhanyes.com/qiye/22.html

喝汽水问题(c语言)

喝汽水&#xff0c;1瓶汽水1元&#xff0c;2个空瓶可以换一瓶汽水&#xff0c;给20元&#xff0c;可以喝多少汽水&#xff0c;这里用c语言来解释 可以先用列举法来说明&#xff0c;20可以买20个汽水&#xff0c;两个空瓶换一瓶就可以获得10个汽水&#xff0c;同理10个空瓶个可…

03-Redis缓存高可用集群

文章目录 1、Redis集群方案比较2、Redis高可用集群搭建redis集群搭建Java操作redis集群 4、Redis集群原理分析槽位定位算法跳转重定位Redis集群节点间的通信机制gossip通信的10000端口网络抖动 Redis集群选举原理分析集群脑裂数据丢失问题集群是否完整才能对外提供服务Redis集群…

开源CRM客户管理系统-FeelCRM

FeelCRM客户管理系统 开源项目介绍 FeelCRM客户管理系统&#xff0c;符合中小企业业务流程&#xff1b;支持线索管理、客户管理、商机管理、合同管理、审核管理等多个模块&#xff1b;希望能为广大中小企业以及开发者们提供一个更多的可能性&#xff1b;本版本是我公司跨语言…

Blender教程-物体的移动、旋转与缩放-04

一、新建一个立方体 ShiftA新建一个立方体用来演示。 二、物体的移动 xyz轴移动 点击下图图左侧的移动选项后&#xff0c;选中要移动的物体&#xff0c;会出现三个箭头的方向&#xff0c;这分别代表沿着x、y、z轴移动。xyz平面移动 这个小正方体代表沿着某一个面移动&#…

海康实时监控预览视频流接入web

我们采取的方案是后端获取视频流返回给前端&#xff0c;然后前端播放 海康开放平台海康威视合作生态致力打造一个能力开放体系、两个生态圈&#xff0c;Hikvision AI Cloud开放平台是能力开放体系的核心内容。它是海康威视基于多年在视频及物联网核心技术积累之上&#xff0c;…

【鸿蒙】大模型对话应用(一):大模型接口对接与调试

Demo介绍 本demo对接阿里云和百度的大模型API&#xff0c;实现一个简单的对话应用。 DecEco Studio版本&#xff1a;DevEco Studio 3.1.1 Release HarmonyOS API版本&#xff1a;API9 关键点&#xff1a;ArkTS、ArkUI、UIAbility、网络http请求、列表布局 官方接口文档 此…

黑盒测试用例的具体设计方法(7种)

7种常见的黑盒测设用例设计方法&#xff0c;分别是等价类、边界值、错误猜测法、场景设计法、因果图、判定表、正交排列。 &#xff08;一&#xff09;等价类 1.概念 依据需求将输入&#xff08;特殊情况下会考虑输出&#xff09;划分为若干个等价类&#xff0c;从等价类中选…

Azure Private endpoint DNS 记录是如何解析的

Private endpoint 从本质上来说是Azure 服务在Azure 虚拟网络中安插的一张带私有地址的网卡。 举例来说如果Storage account在没有绑定private endpoint之前&#xff0c;查询Storage account的DNS记录会是如下情况&#xff1a; Seq Name …

navicat 可以直接往 mysql导入excel表格

妈呀 还好 提前问了一下&#xff0c;不然哼哧哼哧在那里写&#xff0c;导入接口。。

Vulnhub靶场DC-2

本机IP&#xff1a;192.168.223.128 目标IP&#xff1a;192.168.223.131 目标搜索&#xff1a;nmap -sP 192.168.223.1/24 端口搜索&#xff1a;nmap -sV -A -p- 192.168.223.131 开放了80 7744端口 访问一下web 发现进不去 目标ip被重定向到www.dc-2.com 修改一下本地DNS l…

Android源码设计模式解析与实战第2版笔记(一)

第一章 走向灵活软件之路 — 面向对象的六大原则 优化代码的第一步 — 单一职责原则 单一职责原则的英文名称是Single Responsibility Principle&#xff0c;缩写是SRP。 SRP&#xff1a;就一个类而言&#xff0c;应该仅有一个引起它变化的原因。 一个类中应该是一组相关性很…

Windows Server 安装 Docker

一、简介 Docker 不是一个通用容器工具&#xff0c;它依赖运行的 Linux 内核环境。Docker 实质上是在运行的 Linux 服务器上制造了一个隔离的文件环境&#xff0c;所以它执行的效率几乎等同于所部署的 Linux 主机服务器性能。因此&#xff0c;Docker 必须部署在 Linux 内核系统…

多个SSH-Key下,配置Github SSH-Key

首先&#xff0c;检查 github 的连接性&#xff0c;因为DNS污染的原因&#xff0c;很多机器ping不通github&#xff0c;就像博主的机器&#xff1a; 怎么解决DNS污染的问题&#xff0c;博主查了很多教程&#xff0c;测试出一个有效的方法&#xff0c;那就是修改hosts文件。host…

Docker部署思维导图工具SimpleMindMap并实现公网远程访问

文章目录 1. Docker一键部署思维导图2. 本地访问测试3. Linux安装Cpolar4. 配置公网地址5. 远程访问思维导图6. 固定Cpolar公网地址7. 固定地址访问 SimpleMindMap 是一个可私有部署的web思维导图工具。它提供了丰富的功能和特性&#xff0c;包含插件化架构、多种结构类型&…

计算机网络:体系结构知识点汇总

文章目录 一、计算机网络概述1.1概念及功能1.2组成和分类1.3性能指标 二、体系结构与参考模型2.1分层结构、协议、接口、服务2.2OSI参考模型2.3TCP/IP参考模型 一、计算机网络概述 1.1概念及功能 计算机网络就是通过各个节点&#xff0c;这个节点包括终端的电脑&#xff0c;手…

Python编程 从入门到实践(项目二:数据可视化)

本篇为实践项目二&#xff1a;数据可视化。 配合文章python编程入门学习&#xff0c;代码附文末。 项目二&#xff1a;数据可视化 1.生成数据1.1 安装Matplotlib1.2 绘制简单的折线图1.2.1 修改标签文字和线条粗细1.2.2 校正图形1.2.3 使用内置样式1.2.4 使用scatter()绘制散点…

Go的单元测试

开发项目过程中&#xff0c;少不了单元测试&#xff1b;下面我们认识下单元测试&#xff1a; Go 语言测试框架可以让我们很容易地进行单元测试&#xff0c;但是需要遵循五点规则。 含有单元测试代码的 go 文件必须以 _test.go 结尾&#xff0c;Go 语言测试工具只认符合这个规…

风口抓猪-借助亚马逊云科技EC2服务器即刻构建PalWorld(幻兽帕鲁)私服~~~持续更新中

Pocketpair出品的生存类游戏《幻兽帕鲁》最近非常火&#xff0c;最高在线人数已逼近200万。官方服务器亚历山大&#xff0c;游戏开发商也提供了搭建私人专用服务器的方案&#xff0c;既可以保证稳定的游戏体验&#xff0c;也可以和朋友一起联机游戏&#xff0c;而且还能自定义经…

Linux初始相关配置

前言 在学完了Linux的相关基础命令后&#xff0c;在正式使用Linux系统之前&#xff0c;我觉得配置一些东西是很有意义的。 文章目录 前言1.权限配置&#xff0c;普通用户无法sudo提权2.vim配置3.vim其他操作4.动静态库5.gcc/g6.程序翻译的过程7.make/makefile8.cmake/CMakeLis…