
做数据可视化大屏或者行政地图展示时不少人都遇到过这个问题ECharts渲染出来的中国地图右下角永远挂着一块“南海诸岛”的缩略区域。第一次注意到它的时候我在调试一个省级业务分布图屏幕上地图主体挤在左侧右边一块小图怎么看怎么别扭——不是为了别的纯粹是版式和比例问题。客户那边也反馈打印会议材料时右下角这块小图和旁边的图例打架。这篇文章就围绕这个需求展开把ECharts中国地图右下角的“南海诸岛”隐藏掉。如果你正在写中国地图相关的数据可视化项目无论用ECharts 4还是5这里都有能落地的方案我也会把每个方案的原理和副作用讲清楚。我自己试过三种做法最后留下了最好维护的那个下面一个个说。1. 藏在右下角的小图是怎么来的先搞清楚原理1.1 GeoJSON里的“特殊分子”在动手之前我先做了一件事把ECharts官方那份china.json拉下来仔细看了一眼结构。整个GeoJSON就是一个标准的FeatureCollection里面大多数feature都是省份它们的properties.name分别是“北京市”“广东省”这样。但翻到底部会看到一个name为“南海诸岛”的feature。这个feature的geometry并不复杂就是一组多边形对应南海海域的岛礁轮廓和那条断续线。关键在于它的坐标范围是落在整个地图右下角那块小框里的而不是严格投影在真实海域的经纬度位置。也就是说官方地图在制作GeoJSON时就已经把南海诸岛当成一个“插图”放在大陆主体右下方了。提示如果你用的是第三方提供的中国地图GeoJSON建议先用控制台打印features数组确认“南海诸岛”这个feature是否存在、属性字段叫什么。不同来源的地图数据字段名五花八门这一步能省掉后面很多排查时间。1.2 插图的排版逻辑读源码时可以发现ECharts的地图组件在解析GeoJSON、计算整体边界框bounding box的时候会把所有feature的坐标纳入计算范围。既然“南海诸岛”这个feature本身就位于右下角插图区那么它天然参与了整体布局计算。当它存在时大陆主体的边界框和右侧、下方的留白都会受它影响。这解释了一个常见现象很多人把南海诸岛区域用regions设置透明后虽然视觉上好像没了但地图整体比例还是和原来一样——因为它的几何空间还占着位置尺寸计算没有变化。而直接删除featureECharts重新计算bounding box大陆的比例就会明显变大这往往是隐藏需求的额外惊喜。搞懂了这两个底层逻辑后面几个方案哪个适用什么场景就很好判断了。2. 方案一过滤GeoJSON源数据我的首选方案2.1 拿到中国地图原始GeoJSON这一步要做的是在echarts.registerMap之前先拿到中国地图的原始GeoJSON数据。以最常用的CDN方式为例script srchttps://cdn.jsdelivr.net/npm/echarts5/dist/echarts.min.js/script注意单纯引入echarts.min.js并不会自动注册中国地图。ECharts 5之后地图GeoJSON要么自己准备要么从echarts/map/json/china.json这个npm路径取要么用fetch请求一个同名的JSON文件。我用过的比较稳的方式是直接用fetch加载fetch(https://cdn.jsdelivr.net/npm/echarts5/map/json/china.json) .then(response response.json()) .then(geoJson { const filtered { ...geoJson, features: geoJson.features.filter( feature feature.properties.name ! 南海诸岛 ) }; echarts.registerMap(chinaNoNansha, filtered); initChart(); });如果你的项目走的是本地构建更简洁import chinaJson from echarts/map/json/china.json; const filteredGeoJson { ...chinaJson, features: chinaJson.features.filter( feature feature.properties.name ! 南海诸岛 ) }; echarts.registerMap(chinaNoNansha, filteredGeoJson);2.2 注册过滤后的地图并初始化注册完成之后后面就是正常的setOption流程。这里我用geo组件示范function initChart() { const chart echarts.init(document.getElementById(mapContainer)); chart.setOption({ geo: { map: chinaNoNansha, roam: true, itemStyle: { areaColor: #e8f0fe, borderColor: #4e6ef2 } }, series: [{ type: map, map: chinaNoNansha, geoIndex: 0, data: [ { name: 广东省, value: 128 }, { name: 浙江省, value: 87 } ] }] }); }这里有个容易忽略的点过滤后的地图map名称我故意取成chinaNoNansha而不是覆盖原来的china。这样做的直接好处是万一你在同一个页面上还有其他图表需要完整版地图带南海诸岛两个版本可以共存互不影响。2.3 为什么这个方法最干净这个方案是我个人最推荐的理由主要有几点第一它是在数据源头处理不存在“视觉上隐藏但交互还在”的麻烦。比如regions透明化方案南海诸岛那块区域虽然看不见了但鼠标还能触发tooltip地图缩放范围也仍然被它的边界影响着而删除feature后ECharts根本不认识这个区域后续级联问题不会出现。第二代码量少且逻辑直观后面维护的人一眼能看懂。“过滤掉name为南海诸岛的feature”这个语义非常明确不需要解释一大堆配置项交互的坑。第三和ECharts版本解耦。即使ECharts升级换版本只要china.json结构还是FeatureCollection这段过滤代码基本不用改。唯一要留意的就是使用时不要再引用ECharts内部默认注册的china地图而是统一用自定义的注册名称否则容易弄混出现“怎么改半天没变化”的诡异问题。3. 方案二regions透明化只改配置不碰数据3.1 最短代码实现如果你不想管GeoJSON只想快速在现有配置里加几行代码那regions透明化是上手最快的方案geo: { map: china, regions: [{ name: 南海诸岛, itemStyle: { areaColor: transparent, borderColor: transparent }, label: { show: false }, emphasis: { itemStyle: { areaColor: transparent }, label: { show: false }, disabled: true }, select: { disabled: true } }] }这段代码能实现的效果是南海诸岛的填充色和边框都变成透明label也关闭悬停时的高亮颜色同样透明。肉眼看上去右下角就像没有了这块区域。如果你的地图用的是map系列而不是geo组件写法也类似把regions写在series层级下面即可。3.2 这个方案的局限与适用场景但我要说实话这个方案更适合临时场景。比如快速出图给领导看个效果或者你用的是截图输出、不需要交互的地图。它有几个明显的局限透明化不等于移除南海诸岛的几何空间仍然留在整个地图的bounding box内。这意味着地图的整体缩放比例并没有变化大陆部分不会因此变大。如果你就是嫌右下角占空间、想让主体更饱满这个方案解决不了核心诉求。交互事件仍然存在。透明区域还是能响应鼠标悬停、点击tooltip照样会弹出来。虽然设置emphasis.disabled能在一定程度上降低干扰但如果你在series里配置了统一的tooltip触发器鼠标划过右下角时可能还是会触发数据提示。透明区域在部分截图工具或打印导出场景下可能出现浅灰色底边或边框残留在某些背景色下肉眼很容易看出来。所以我一般把这个方案当成备用或者作为不用碰数据时的临时workaround。4. 方案三SVG渲染器配合DOM操作4.1 切换到SVG渲染器ECharts默认使用Canvas渲染。Canvas渲染出来的地图是一整张位图没办法针对单个区域做DOM操作所以这个思路要想成立得先把渲染器切换成SVGconst chart echarts.init( document.getElementById(mapContainer), null, { renderer: svg } );SVG渲染器下ECharts会把每个地理区域渲染成一个独立的path元素并且会带上对应的name这样就有了操作空间。4.2 通过选择器隐藏对应的path图表渲染完成之后可以直接在容器里找到这块区域并隐藏它chart.setOption({ ... }); // 渲染地图 setTimeout(() { const svgRoot document.getElementById(mapContainer).querySelector(svg); const nanshaPath svgRoot.querySelector([name南海诸岛]); if (nanshaPath) { nanshaPath.style.display none; } }, 0);为什么不直接马上执行因为ECharts的SVG节点是在setOption之后才创建并挂载的所以要么放在setTimeout里要么监听chart.on(finished)事件chart.on(finished, () { const svgRoot document.getElementById(mapContainer).querySelector(svg); const nanshaPath svgRoot.querySelector([name南海诸岛]); if (nanshaPath) { nanshaPath.setAttribute(style, display: none); } });注意不同版本ECharts在SVG节点上写的属性名可能不一样有的版本是name有的版本是>geo: { map: chinaNoNansha, center: [104.0, 36.0], // 根据自己的版面微调 zoom: 1.2 }center的默认值通常是[0, 0]或者null计算出的中心点取决于当前地图的bounding rect。如果你发现地图整体偏左或者偏上可以往右往下平移center坐标如果发现地图太小就逐步加大zoom。这个调整没有固定公式和容器尺寸、地图数据都有关系最好在调试面板里实时改参数看效果。5.3 与多层地图联动时的特殊修正如果你在一个页面上同时挂了geo地图、map系列还有散点图或飞线图删除南海诸岛后要注意散点坐标是否仍用原来的经纬度数组。在官方china.json这种“已经摆好坐标位置”的数据里散点如果用的是真实经纬度会和地图上的省界对不上。这不是隐藏南海诸岛导致的而是官方china.json本来就不是严格经纬度投影——这一点在联动调试时容易被误判提前提一下免得你排查半天怀疑是过滤逻辑写错了。6. 实战中容易踩的坑和一点个人建议6.1 属性名大小写问题网上很多地图GeoJSON来源不一有的是properties.name有的是properties.NAME还有的是properties.NAME_CHN。如果过滤后地图上省份名字全乱了或过滤后的地图直接变成空白先别怀疑ECharts去打印一下features数组看看属性结构。我在一个老项目里就遇到过这种情况同事从某个GIS平台导出了GeoJSON属性字段是NAME我按properties.name去过滤结果“南海诸岛”根本没过滤掉白排查了一下午。6.2 边界线残留有些地图GeoJSON除了“南海诸岛”这个feature还有单独的一条线名字可能叫“九段线”“南海断续线”等。如果你过滤完发现右下角还有一条虚线残影说明地图数据里还有第二个相关feature。处理方式一样把它们一并列入排除名单const excludeNames [南海诸岛, 南海诸岛界线]; const filtered { ...geoJson, features: geoJson.features.filter( feature !excludeNames.includes(feature.properties.name) ) };6.3 我的经验总结最后聊聊我的选择习惯。如果是一个新项目我会直接用方案一在加载地图数据后做一次过滤再注册成本和心智负担都最小。如果是老项目不想动原来已经调好的地图比例只想让右下角那块视觉上消失方案二够用但记得给关键区域挂上emphasis.disabled避免透明区域误触发放大缩小。SVG那套方案只在想完全不动别人的封装代码时采用。从维护角度讲把过滤逻辑抽成一个独立的工具函数是最划算的export function getChinaMapGeoJson() { return fetch(https://cdn.jsdelivr.net/npm/echarts5/map/json/china.json) .then(res res.json()) .then(geoJson ({ ...geoJson, features: geoJson.features.filter( feature feature.properties.name ! 南海诸岛 ) })); }这样无论后续有多少图表需要地图只要调这个函数拿数据注册全项目的行为就统一了。我实际用下来这个10行不到的辅助函数省掉了后续很多地图相关的改动。最后再分享一个小细节如果你的中国地图后续要做省份下钻比如点击广东省跳到广东省地图建议把这份过滤后的GeoJSON里所有省份的adcode或者cp中心点坐标字段保留好——之前我图省事用解构把某些字段丢了结果下钻动画定位全乱了。地图数据这块宁可多留字段不要急着精简结构。