echarts: 作为基础的可视化库,提供了强大的图表渲染和事件系统。echarts-gl: 关键的3D渲染扩展,它基于 WebGL,为 ECharts 提供了3D坐标系、光照、材质渲染等能力。没有它,type: 'map3D' 将无法工作。
ECharts本身不包含地图矢量数据,它需要一个“注册”过程。
1 // 1. 加载全国GeoJSON 2 const res = await fetch("/map/china.json"); 3 const mapData = await res.json(); 4 // 2. 注册到ECharts实例 5 echarts.registerMap("china", mapData); 6 // 3. 下钻时动态加载并注册省级数据 7 const provinceRes = await fetch(`https://geo.datav.aliyun.com/areas_v3/bound/${code}_full.json`); 8 const provinceMapData = await provinceRes.json(); 9 echarts.registerMap("浙江省", provinceMapData);
- GeoJSON: 一种标准的地理空间数据格式,描述了地理要素(如省份、城市)的几何形状(多边形)和属性。
echarts.registerMap(name, geoJson): 这是一个核心API,它将一个GeoJSON对象与一个字符串名称("china")绑定。后续在option中指定map: name时,ECharts就会查找并使用这个已注册的GeoJSON来绘制地图。- 数据源:全国地图可以静态部署,而省市级地图因数据量大且更新频繁,采用动态从第三方CDN(如阿里云DataV)加载是一种高效且常见的做法。
核心实现:initMap 函数剖析
initMap 是整个组件的渲染引擎,它是一个高度动态的函数,根据传入的 mapName 决定一切。
1. 实例管理
1 if (!chartInstance) { 2 chartInstance = echarts.init(mapRef.value); 3 } 4 chartInstance.clear();
- 单例模式:
chartInstance被声明在组件作用域的顶层,确保在整个组件生命周期内只有一个ECharts实例。初始化只在第一次initMap调用时发生。 clear()vsdispose(): 在重新渲染前调用clear()清空当前系列和配置,但保留实例本身,避免了重复创建/销毁实例带来的性能开销。实例销毁只放在onUnmounted中。
2.动态数据获取与格式化
const generateBarData = async () => { // ... const req = { dimension: mapName === "china" ? "province" : "city", parentProvince: mapName === "china" ? "" : currentMapName.value, // ... }; const resp = await getMapData(req); // ...数据处理和格式化 return res; };
这是实现数据与视图同步的关键。generateBarData 函数根据 mapName 动态构建请求参数,确保后端API能够返回当前层级所需的数据。返回的数据被格式化为 ECharts 系列所需的结构 [{name: 'xxx', value: yyy}]。
3.配置对象 的动态生成
option = { series: [{ type: "map3D", // 关键:指定为3D地图 map: mapName, // 动态绑定已注册的地图名称 regionHeight: 4, // 3D拉伸高度 // ...光照、样式等配置 }], // ...tooltip, visualMap等 };
option 对象是声明式的,它描述了“要画什么”,而不是“怎么画”。
type: "map3D": 告诉echarts-gl启用3D渲染管线。map: mapName: 将图表系列与一个已注册的GeoJSON关联。regionHeight: 3D效果的核心参数,定义了地图板块在Z轴上的拉伸高度,营造出立体感。
4. 交互逻辑:点击下钻的实现
下钻是通过事件驱动的状态机实现的。
chartInstance.on("click", async (params) => { if (mapName === "china") { // 状态判断:只在顶层地图响应 // 1. 异步获取下一层数据 const mapData = await fetch(...).json(); // 2. 更新核心状态 currentMapName.value = params.name; // 3. 注册新地图 echarts.registerMap(params.name, mapData); // 4. 递归调用,触发重新渲染 await initMap(params.name); } else { // 在省级地图上的点击,只做事件通知 emits("on-province", currentMapName.value, params.name); } });
这是一个典型的异步状态更新流程:
- 事件触发:用户点击,ECharts
click事件触发,回调函数获取到点击区域的信息params。 - 状态转换:
currentMapName.value的改变,是整个组件状态机的一次状态转换。 - 异步操作:加载新地图数据是I/O密集型操作,使用
async/await保证代码的异步同步化。 - 递归重绘:调用
initMap传入新的地图名,initMap内部会使用新的mapName去构建新的option,最终setOption完成视图的更新。这是一个“数据/状态变更 -> 驱动视图变更”的清晰闭环。
5. 生命周期与资源管理
onMounted(() => { // ...数据初始化 window.addEventListener("resize", handleMapResize); }); onUnmounted(() => { if (chartInstance) { chartInstance.dispose(); // 必须销毁实例,释放WebGL上下文和内存 chartInstance = null; } window.removeEventListener("resize", handleMapResize); });
- 响应式:监听
window.resize事件并调用chartInstance.resize()是数据大屏的必备适配,确保地图在窗口缩放时能正确重绘。 - 内存管理:
echarts-gl底层使用 WebGL,会创建GPU资源和复杂的对象图。在onUnmounted中调用dispose()是至关重要的,它会彻底销毁图表实例,释放所有占用的内存和GPU上下文,防止组件卸载后出现内存泄漏。
浙公网安备 33010602011771号