☰
ECharts中国地图隐藏南海诸岛:三种方案实现与原理详解
2026/10/1 4:00:06 网站建设 项目流程

做数据可视化大屏或者行政地图展示时,不少人都遇到过这个问题: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设置透明后,虽然视觉上好像没了,但地图整体比例还是和原来一样——因为它的几何空间还占着位置,尺寸计算没有变化。而直接删除feature,ECharts重新计算bounding box,大陆的比例就会明显变大,这往往是隐藏需求的额外惊喜。

搞懂了这两个底层逻辑,后面几个方案哪个适用什么场景,就很好判断了。

2. 方案一:过滤GeoJSON源数据(我的首选方案)

2.1 拿到中国地图原始GeoJSON

这一步要做的是在echarts.registerMap之前,先拿到中国地图的原始GeoJSON数据。以最常用的CDN方式为例:

<script src="https://cdn.jsdelivr.net/npm/echarts@5/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/echarts@5/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操作,所以这个思路要想成立,得先把渲染器切换成SVG:

const 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/echarts@5/map/json/china.json') .then(res => res.json()) .then(geoJson => ({ ...geoJson, features: geoJson.features.filter( feature => feature.properties.name !== '南海诸岛' ) })); }

这样无论后续有多少图表需要地图,只要调这个函数拿数据注册,全项目的行为就统一了。我实际用下来,这个10行不到的辅助函数省掉了后续很多地图相关的改动。

最后再分享一个小细节:如果你的中国地图后续要做省份下钻,比如点击广东省跳到广东省地图,建议把这份过滤后的GeoJSON里所有省份的adcode或者cp(中心点坐标)字段保留好——之前我图省事,用解构把某些字段丢了,结果下钻动画定位全乱了。地图数据这块,宁可多留字段,不要急着精简结构。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询