Implementing Map Drill‑Down with Echarts in Vue 3
This tutorial demonstrates how to create an interactive Echarts map that supports drill‑down to county level and back navigation using Vue 3, vue‑echarts, and Vite, covering data acquisition, option configuration, event handling, and full component code.
Echarts is a popular charting library often used for visual dashboards; this article explains how to implement map drill‑down that can descend to county level and return to the previous level.
Preparation : The map JSON data is obtained from the DataV.GeoAtlas API (with a note on handling possible 403 errors). The technical stack includes Vue 3.3.7, vue‑echarts 6.6.1 (a component wrapper for Echarts), and Vite 4.5.0.
Map effect : A preview image is shown and links to the project and source code are provided.
Template (minimal markup):
<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>Fetching map JSON (online API version shown; a local‑import version is mentioned but has Vite packaging issues):
// Using online 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;
};
// Using local resource (may not be bundled by Vite)
const getMapJson = async (mapName: string) => {
const url = `@/assets/mapJson/${mapName}.json`;
const mapJson = await import(/* @vite-ignore */ url);
return mapJson;
};Setting Echarts options (tooltip, visualMap, geo, and series configuration):
const setOptions = (mapName: string, mapData: any) => {
return {
tooltip: {
show: true,
formatter: function (params: any) {
if (params && params.data) {
const { adcode, name, data } = params.data;
return `adcode: ${adcode}
name: ${name}
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,
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 }
]
};
};Rendering the map (registering the map, generating random data, and assigning options):
const renderMapEcharts = async (mapName: string) => {
const mapJson = await getMapJson(mapName);
registerMap(mapName, mapJson); // register map
const mapdata = mapJson.features.map((item: { properties: any }) => {
const data = (Math.random() * 80 + 20).toFixed(0); // 20‑80 random number
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);
};Drill‑down click handler (determines the next map based on adcode and level, prevents duplicate clicks, records navigation history):
const mapList = ref
([]); // record map history
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);
};Back‑to‑previous level (pops the history stack and renders the prior map):
const goBack = () => {
const mapName = mapList.value[mapList.value.length - 2] || '100000_full';
mapList.value.pop();
renderMapEcharts(mapName);
};Full component code (combines the template, script setup, imports, and all functions above) is provided in the original source.
Rare Earth Juejin Tech Community
Juejin, a tech community that helps developers grow.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.