引言
Echarts 大家都不陌生吧,时常被用于绘制各种图表,也作为大屏可视化的常驻用户,这里就不多说了,今天主要是讲述一下 Echarts 的地图下钻,支持下钻到县、返回上一级。
准备工作
地图JSON数据
DataV.GeoAtlas地理小工具系列 (aliyun.com) 支持在线调用API和下载json资源(我这里是调用的API)
如果地图json API请求报错403,可参考这个解决办法 :地图请求阿里的geojson数据时,返回403Forbidden解决方案 - 掘金 (juejin.cn)
技术栈
-
vue: 3.3.7
-
vue-echarts: 6.6.1 (直接使用 Echarts 也是一样的,这个只是对 Echarts 的组件封装)
-
vite: 4.5.0
地图效果
**项目预览地址:**UnusualAdmin
**项目代码地址:**UnusualAdmin
实现
template
这里只需要一个 Echarts 节点和一个按钮就行了
<template>
<div :style="`height: ${calcHeight('main')};`" class="wh-full pos-relative">
<v-chart :option="mapOption" :autoresize="true" @click="handleClick" />
<n-button v-show="isShowBack" class="pos-absolute top-10 left-10" @click="goBack">返回</n-button>
</div>
</template>
获取mapJson
// 使用线上API
const getMapJson = async (mapName: string) => {
const url = `https://geo.datav.aliyun.com/areas_v3/bound/${mapName}.json`
const mapJson = await fetch(url).then(res => res.json())
return mapJson
}
// 使用本地资源
const getMapJson = async (mapName: string) => {
const url = `@/assets/mapJson/${mapName}.json`
const mapJson = await import(/* @vite-ignore */ url)
return mapJson
}
第二种方法(使用本地资源)存在问题:这个方法后续发现,vite打包不会把json文件打包到dist,线上会报错,目前没找到可靠的解决办法(如果放到public文件夹下会打包进去),故舍弃。
如果大家有什么解决这个问题的好办法,请在评论区留言,博主会一一去尝试的🙏🙏🙏
更新地图配置options
const setOptions = (mapName: string, mapData: any) => {
return {
// 鼠标悬浮提示
tooltip: {
show: true,
formatter: function (params: any) {
// 根据需要进行数据处理或格式化操作
if (params && params.data) {
const { adcode, name, data } = params.data;
// 返回自定义的tooltip内容
return `adcode: ${adcode}<br>name: ${name}<br>data: ${data}`;
}
},
},
// 左下角的数据颜色条
visualMap: {
show: true,
min: 0,
max: 100,
left: 'left',
top: 'bottom',
text: ['高', '低'], // 文本,默认为数值文本
calculable: true,
seriesIndex: [0],
inRange: {
color: ['#00467F', '#A5CC82'] // 蓝绿
}
},
// geo地图
geo: {
map: mapName,
roam: true,
select: false,
// 图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等。
selectedMode: 'single',
label: {
show: true
},
emphasis: {
itemStyle: {
areaColor: '#389BB7',
borderColor: '#389BB7',
borderWidth: 0
},
label: {
fontSize: 14,
},
}
},
series: [
// 地图数据
{
type: 'map',
map: mapName,
roam: true,
geoIndex: 0,
select: false,
data: mapData
},
// 散点
{
name: '散点',
type: 'scatter',
coordinateSystem: 'geo',
data: mapData,
itemStyle: {
color: '#05C3F9'
}
},
// 气泡点
{
name: '点',
type: 'scatter',
coordinateSystem: 'geo',
symbol: 'pin', //气泡
symbolSize: function (val: any) {
if (val) {
return val[2] / 4 + 20;
}
},
label: {
show: true,
formatter: function (params: any) {
return params.data.data || 0;
},
color: '#fff',
fontSize: 9,
},
itemStyle: {
color: '#F62157', //标志颜色
},
zlevel: 6,
data: mapData,
},
// 地图标点
{
name: 'Top 5',
type: 'effectScatter',
coordinateSystem: 'geo',
data: mapData.map((item: { data: number }) => {
if (item.data > 60) return item
}),
symbolSize: 15,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke'
},
label: {
formatter: '{b}',
position: 'right',
show: true
},
itemStyle: {
color: 'yellow',
shadowBlur: 10,
shadowColor: 'yellow'
},
zlevel: 1
},
]
}
}
渲染地图
const renderMapEcharts = async (mapName: string) => {
const mapJson = await getMapJson(mapName)
registerMap(mapName, mapJson); // 注册地图
// 为地图生成一些随机数据
const mapdata = mapJson.features.map((item: { properties: any }) => {
const data = (Math.random() * 80 + 20).toFixed(0) // 20-80随机数
const tempValue = item.properties.center ? [...item.properties.center, data] : item.properties.center
return {
name: item.properties.name,
value: tempValue, // 中心点经纬度
adcode: item.properties.adcode, // 区域编码
level: item.properties.level, // 层级
data // 模拟数据
}
});
// 更新地图options
mapOption.value = setOptions(mapName, mapdata)
}
实现地图点击下钻
// 点击下砖
const mapList = ref<string[]>([]) // 记录地图
const handleClick = (param: any) => {
// 只有点击地图才触发
if (param.seriesType !== 'map') return
const { adcode, level } = param.data
const mapName = level === 'district' ? adcode : adcode + '_full'
// 防止最后一个层级被重复点击,返回上一级出错
if (mapList.value[mapList.value.length - 1] === mapName) {
return notification.warning({ content: '已经是最下层了', duration: 1000 })
}
// 每次下转都记录下地图的name,在返回的时候使用
mapList.value.push(mapName)
renderMapEcharts(mapName)
}
返回上一级实现
// 点击返回上一级地图
const goBack = () => {
const mapName = mapList.value[mapList.value.length - 2] || '100000_full'
mapList.value.pop()
renderMapEcharts(mapName)
}
全部代码
<template>
<div :style="`height: ${calcHeight('main')};`" class="wh-full pos-relative">
<v-chart :option="mapOption" :autoresize="true" @click="handleClick" />
<n-button v-show="isShowBack" class="pos-absolute top-10 left-10" @click="goBack">返回</n-button>
</div>
</template>
<script setup lang="ts" name="EchartsMap">
import { use, registerMap } from 'echarts/core'
import VChart from 'vue-echarts'
import { CanvasRenderer } from 'echarts/renderers'
import { MapChart, ScatterChart, EffectScatterChart } from 'echarts/charts'
import { TitleComponent, TooltipComponent, LegendComponent, GridComponent, VisualMapComponent } from 'echarts/components'
import { calcHeight } from '@/utils/help';
use([
CanvasRenderer,
TitleComponent,
TooltipComponent,
LegendComponent,
GridComponent,
VisualMapComponent,
MapChart,
ScatterChart,
EffectScatterChart
])
const notification = useNotification()
const mapOption = ref()
const mapList = ref<string[]>([]) // 记录地图
const isShowBack = computed(() => {
return mapList.value.length !== 0
})
const getMapJson = async (mapName: string) => {
const url = `https://geo.datav.aliyun.com/areas_v3/bound/${mapName}.json`
const mapJson = await fetch(url).then(res => res.json())
return mapJson
}
const setOptions = (mapName: string, mapData: any) => {
return {
tooltip: {
show: true,
formatter: function (params: any) {
// 根据需要进行数据处理或格式化操作
if (params && params.data) {
const { adcode, name, data } = params.data;
// 返回自定义的tooltip内容
return `adcode: ${adcode}<br>name: ${name}<br>data: ${data}`;
}
},
},
visualMap: {
show: true,
min: 0,
max: 100,
left: 'left',
top: 'bottom',
text: ['高', '低'], // 文本,默认为数值文本
calculable: true,
seriesIndex: [0],
inRange: {
color: ['#00467F', '#A5CC82'] // 蓝绿
}
},
geo: {
map: mapName,
roam: true,
select: false,
// zoom: 1.6,
// layoutCenter: ['45%', '70%'],
// layoutSize: 750,
// 图形上的文本标签,可用于说明图形的一些数据信息,比如值,名称等。
selectedMode: 'single',
label: {
show: true
},
emphasis: {
itemStyle: {
areaColor: '#389BB7',
borderColor: '#389BB7',
borderWidth: 0
},
label: {
fontSize: 14,
},
}
},
series: [
// 数据
{
type: 'map',
map: mapName,
roam: true,
geoIndex: 0,
select: false,
data: mapData
},
{
name: '散点',
type: 'scatter',
coordinateSystem: 'geo',
data: mapData,
itemStyle: {
color: '#05C3F9'
}
},
{
name: '点',
type: 'scatter',
coordinateSystem: 'geo',
symbol: 'pin', //气泡
symbolSize: function (val: any) {
if (val) {
return val[2] / 4 + 20;
}
},
label: {
show: true,
formatter: function (params: any) {
return params.data.data || 0;
},
color: '#fff',
fontSize: 9,
},
itemStyle: {
color: '#F62157', //标志颜色
},
zlevel: 6,
data: mapData,
},
{
name: 'Top 5',
type: 'effectScatter',
coordinateSystem: 'geo',
data: mapData.map((item: { data: number }) => {
if (item.data > 60) return item
}),
symbolSize: 15,
showEffectOn: 'render',
rippleEffect: {
brushType: 'stroke'
},
label: {
formatter: '{b}',
position: 'right',
show: true
},
itemStyle: {
color: 'yellow',
shadowBlur: 10,
shadowColor: 'yellow'
},
zlevel: 1
},
]
}
}
const renderMapEcharts = async (mapName: string) => {
const mapJson = await getMapJson(mapName)
registerMap(mapName, mapJson);
const mapdata = mapJson.features.map((item: { properties: any }) => {
const data = (Math.random() * 80 + 20).toFixed(0) // 20-80随机数
const tempValue = item.properties.center ? [...item.properties.center, data] : item.properties.center
return {
name: item.properties.name,
value: tempValue, // 中心点经纬度
adcode: item.properties.adcode, // 区域编码
level: item.properties.level, // 层级
data // 模拟数据
}
});
mapOption.value = setOptions(mapName, mapdata)
}
renderMapEcharts('100000_full') // 初始化绘制中国地图
// 点击下砖
const handleClick = (param: any) => {
// 只有点击地图才触发
if (param.seriesType !== 'map') return
const { adcode, level } = param.data
const mapName = level === 'district' ? adcode : adcode + '_full'
// 防止最后一个层级被重复点击,返回上一级出错
if (mapList.value[mapList.value.length - 1] === mapName) {
return notification.warning({ content: '已经是最下层了', duration: 1000 })
}
mapList.value.push(mapName)
renderMapEcharts(mapName)
}
// 点击返回上一级地图
const goBack = () => {
const mapName = mapList.value[mapList.value.length - 2] || '100000_full'
mapList.value.pop()
renderMapEcharts(mapName)
}
</script>
博客主要记录一些学习的文章,如有不足,望大家指出,谢谢。