據(jù)對齊排查)
簡介「echarts 世界地圖js」是一套面向前端開發(fā)者與數(shù)據(jù)可視化學習者的世界地圖示例資源重點解決在網(wǎng)頁中快速搭建可交互全球數(shù)據(jù)地圖的問題適合展示人口、GDP等跨國指標或做區(qū)域數(shù)據(jù)對比的場景。壓縮包共77個文件以39個js腳本和38個json地圖/配置數(shù)據(jù)為主整體約3.42MB便于查看地圖初始化、配置項組織與數(shù)據(jù)接入方式。已有1639人學習下載說明其在實際項目與入門練習中具有一定參考價值。讀者可從中獲得世界地圖渲染的完整素材與代碼線索包括地圖數(shù)據(jù)、主題樣式、ECharts實例配置及交互效果參考能幫助理解tooltip、visualMap、縮放平移等常見能力的落地寫法資源中的腳本與數(shù)據(jù)文件可配合復現(xiàn)地圖加載、數(shù)值映射和視覺編碼流程并據(jù)此改造成自己的可視化頁面。1. 先別急著 setOptionecharts 世界地圖js 到底替你做了哪一步這份 echarts 世界地圖js 資源最常見的形態(tài)不是一張圖片而是一段把 GeoJSON 注冊進 ECharts 的腳本它提供區(qū)域邊界、區(qū)域名稱以及一個能被series.map引用的底圖。很多人以為拿到 js 就能直接出圖真正卡住的地方往往是注冊名、腳本順序和名稱匹配。它適合做離線大屏、內部報表和區(qū)域指標分布圖不適合街道級細節(jié)或實時軌跡。先把它當成一份“地圖字典”再談樣式和數(shù)據(jù)。2. 把 world.js 接進頁面三種加載路徑與一份最小可運行配置地圖資源接不進去后面調色、加 tooltip、做鉆取都是空談。我一般先把問題拆成兩層world.js到底是“已經幫你注冊好的腳本”還是“只暴露了一份 GeoJSON 的數(shù)據(jù)文件”。這兩種東西文件名可能都叫world.js但接法完全不同。先判斷結構再選加載方式能少走很多彎路。2.1 先判斷 world.js 是“注冊腳本”還是“純數(shù)據(jù)”拿到文件后不要急著改代碼先用文本搜索看幾個關鍵詞registerMap、features、export default、window.。如果文件里出現(xiàn)echarts.registerMap(world, ...)說明它是注冊腳本頁面里只要保證echarts先加載、它后加載即可。如果只看到features數(shù)組和properties.name那它更像純 GeoJSON 包裝需要你自己調registerMap。如果是export default說明它走模塊導出script標簽直接引入不一定能用。# 在資源目錄里快速看結構不要全文件打印 grep -n registerMap\|features\|export default\|window\. world.js | head -20這段命令只做一件事把可能影響接入方式的關鍵詞找出來。registerMap出現(xiàn)優(yōu)先按注冊腳本處理features出現(xiàn)但沒有registerMap按純數(shù)據(jù)處理export default出現(xiàn)優(yōu)先走打包器。注意壓縮后的文件可能只有一行grep的輸出會很長看到關鍵詞即可不要直接拿壓縮文件手改。注意world.js里的注冊名不一定是world以文件里registerMap的第一個參數(shù)為準。后面所有map: world都要和它一致。2.2 瀏覽器直連script 順序與最小初始化最常見的落地方式是靜態(tài)頁面直接引。順序必須是 ECharts 在前、world.js在后否則world.js內部調echarts.registerMap時會直接報錯。容器必須有明確高度地圖不是文本父級高度為 0 時它不會自己撐開。下面這份最小示例只畫底圖不接業(yè)務數(shù)據(jù)用來確認資源本身可用。div idworldMap stylewidth: 100%; height: 600px;/div script src./echarts.min.js/script script src./vendor/world.js/script script // 1. 先確認 world.js 是否已經完成注冊 if (!echarts.getMap(world)) { const geo window.world || window.WORLD_GEOJSON || window.worldGeoJson; if (!geo) { throw new Error(world.js 未注冊地圖也未暴露 GeoJSON 全局變量); } echarts.registerMap(world, geo); } // 2. 容器必須有明確高度否則地圖高度為 0 const dom document.getElementById(worldMap); const chart echarts.init(dom); // 3. 最小配置先只畫底圖確認資源可用 chart.setOption({ series: [{ type: map, map: world, // 必須和 registerMap 的第一個參數(shù)一致 roam: true }] }); // 4. 容器尺寸變化時重算避免大屏縮放后留白 window.addEventListener(resize, () chart.resize()); /script這段代碼的關鍵參數(shù)只有三個map是注冊名不是文件名roam控制拖拽和縮放先設true方便檢查地圖是否完整容器高度必須落在 CSS 上height: 600px只是示例實際按大屏格子給。echarts.getMap(world)用來判斷是否已經注冊能避免重復注冊也能在控制臺快速確認資源有沒有被加載。如果頁面用file://直接打開而world.js內部又用fetch拉 JSON就會被瀏覽器的本地文件限制攔住。常見做法是起一個靜態(tài)服務# 在項目根目錄啟動靜態(tài)服務避免 fetch/JSON 被 file:// 限制 python3 -m http.server 8080 # 瀏覽器訪問 http://localhost:8080/index.html這不是地圖資源本身的要求而是前端加載本地 JSON 的通用習慣。world.js如果是純腳本注冊直接雙擊 HTML 有時也能出圖但只要涉及fetch、模塊導入或跨文件路徑靜態(tài)服務更穩(wěn)。2.3 打包器接入副作用導入與全局 shim項目走構建工具時最容易翻車的不是地圖本身而是world.js里的執(zhí)行時機。很多世界地圖腳本默認頁面里有一個全局echarts內部直接寫echarts.registerMap(...)。打包后模塊作用域變了全局變量不一定存在地圖就會靜默消失。我的處理方式是先掛全局再副作用導入讓腳本按它原本的假設執(zhí)行。import * as echarts from echarts; // 有些 world.js 內部直接調用全局 echarts.registerMap需要先掛到 window window.echarts echarts; // 副作用導入只執(zhí)行 world.js不接收返回值 await import(./vendor/world.js); // 如果 world.js 是純數(shù)據(jù)模塊走這一條 // import worldGeo from ./vendor/world.js; // echarts.registerMap(world, worldGeo); const chart echarts.init(document.getElementById(worldMap)); chart.setOption({ series: [{ type: map, map: world, roam: true }] });window.echarts echarts是兼容層不是必須長期保留的寫法等確認world.js是純數(shù)據(jù)模塊后應該改成顯式registerMap減少全局污染。await import()適合在模塊頂層或異步初始化函數(shù)里用注意它要求運行環(huán)境支持頂層 await如果不支持就把導入放到async function boot()里。另一個坑是構建工具的 tree-shaking只在代碼里import ./vendor/world.js但不使用返回值時某些配置會把它當成無副作用模塊搖掉。常見做法是在構建配置里把該文件標記為有副作用或者改成顯式導入 GeoJSON 后手動注冊。{ sideEffects: [ ./src/vendor/world.js ] }這段配置的意思是不要對這個文件做副作用裁剪。路徑按項目實際結構調整不要照抄文件名。做完這一步再用echarts.getMap(world)在控制臺確認一次能返回對象就說明注冊成功返回undefined繼續(xù)回到腳本順序和導入方式上查。3. 讓顏色說話visualMap、tooltip 與 nameMap 的聯(lián)動參數(shù)底圖能出來之后下一步才是業(yè)務表達。世界地圖的區(qū)域多、名稱長顏色映射和懸浮提示如果不克制很容易變成“能看但讀不懂”。我一般先把visualMap的范圍從數(shù)據(jù)里算出來再用tooltip補原始名稱和數(shù)值最后才調roam和布局。順序反過來調半天也不知道是數(shù)據(jù)沒對上還是樣式覆蓋了。3.1 visualMapmin/max 不是拍腦袋分段與連續(xù)怎么選visualMap最容易犯的錯是手寫min: 0, max: 100。業(yè)務數(shù)據(jù)可能集中在 20 到 40 之間也可能有一個極端值把色帶拉滿手寫范圍會讓大部分區(qū)域顏色幾乎一樣。連續(xù)型適合看整體高低分布分段型適合匯報時明確分檔。區(qū)域數(shù)量多時色帶控制在 3 到 5 段不要做彩虹色。參數(shù)作用常見設置type連續(xù)或分段指標跨度大用continuous匯報分檔用piecewisemin/max色彩映射范圍從數(shù)據(jù)算不要固定寫 0 到 100inRange.color區(qū)間內顏色3 到 5 個顏色足夠outOfRange.color無數(shù)據(jù)或越界用淺灰避免和最低值同色seriesIndex綁定哪個系列多系列時必填單系列也建議寫calculable可拖拽手柄連續(xù)型可開分段型一般不開const rawData [ { name: Region-A, value: 12 }, { name: Region-B, value: 87 }, { name: Region-C, value: null } ]; const nums rawData .map(d Number(d.value)) .filter(Number.isFinite); let min Math.min(...nums); let max Math.max(...nums); // 所有值相同時給一個最小跨度避免色帶退化 if (min max) max min 1; chart.setOption({ tooltip: { trigger: item }, visualMap: { type: continuous, min: Math.floor(min), max: Math.ceil(max), calculable: true, seriesIndex: 0, inRange: { color: [#dbeafe, #60a5fa, #1e3a8a] }, outOfRange: { color: #e5e7eb }, text: [高, 低], left: 16, bottom: 16 }, series: [{ type: map, map: world, data: rawData.map(d ({ name: d.name, value: Number.isFinite(Number(d.value)) ? Number(d.value) : null })) }] });這段代碼先過濾出有效數(shù)值再向下取整、向上取整避免色帶邊界出現(xiàn)小數(shù)。seriesIndex: 0把visualMap明確綁到第一個系列后面加折線圖或散點圖時不會誤傷。outOfRange給無數(shù)據(jù)區(qū)域一個淺灰和最低值顏色拉開如果兩者太接近讀者會誤以為“沒有數(shù)據(jù)”也是低值。null值不要直接轉成0否則缺數(shù)據(jù)區(qū)域會被染成最低檔。3.2 tooltip 與 nameMap顯示名和原始名分離世界地圖的區(qū)域名通常是英文或拼音直接懸浮出來對業(yè)務方不友好。nameMap可以做顯示名映射但它不是數(shù)據(jù)清洗工具它不會自動幫你把業(yè)務表里的簡稱、別名、大小寫差異全部對齊。穩(wěn)妥做法是數(shù)據(jù)側保留一套標準名顯示側用formatter或nameMap轉成可讀名稱兩邊不要混著改。const displayName new Map([ [Region-A, 區(qū)域 A], [Region-B, 區(qū)域 B] ]); chart.setOption({ tooltip: { trigger: item, confine: true, formatter: (p) { const label displayName.get(p.name) || p.name || 未知區(qū)域; const value p.value null || Number.isNaN(Number(p.value)) ? 無數(shù)據(jù) : Number(p.value).toFixed(1); return ${label}br/指標值${value}; } }, series: [{ type: map, map: world, nameMap: { Region-A: 區(qū)域 A, Region-B: 區(qū)域 B }, data: rawData }] });confine: true讓提示框限制在圖表容器內移動端和大屏邊緣區(qū)域不容易溢出。formatter里先取p.name再用displayName轉一次轉不到就回退原名稱p.value可能是null、空字符串或字符串數(shù)字所以用Number.isNaN(Number(p.value))兜底。nameMap只影響顯示層數(shù)據(jù)到底用原始名還是映射名建議先拿一個區(qū)域做單點驗證數(shù)據(jù)里只放一條記錄能點亮再批量。不要一半用原始名、一半用顯示名最后排查起來非常費勁。3.3 roam、layoutCenter 與 layoutSize視口和自適應世界地圖的長寬比和容器不一定匹配默認鋪滿時容易出現(xiàn)上下留白或左右裁切。layoutCenter和layoutSize控制地圖在容器里的布局中心與大小roam控制用戶交互zoom控制初始縮放。大屏項目里我一般把layoutSize設成95%左右留一點邊給圖例和visualMap不要頂滿。chart.setOption({ series: [{ type: map, map: world, roam: true, zoom: 1.15, center: [0, 0], layoutCenter: [50%, 50%], layoutSize: 95%, aspectScale: 0.85, scaleLimit: { min: 1, max: 8 }, emphasis: { label: { show: true } } }] });roam: true是拖拽和縮放的快捷寫法需要限制縮放范圍時改成對象或者單獨加scaleLimit。layoutCenter用百分比時相對于容器layoutSize可以寫百分比或像素aspectScale會改變地圖的縱向拉伸比例不要為了“填滿”把區(qū)域拉變形。center: [0, 0]只是初始中心點示例實際按業(yè)務關注區(qū)域調整。容器尺寸變化后必須調chart.resize()否則layoutSize還是按舊容器算的。提示先確認echarts.getMap(world)有返回值再調樣式底圖沒注冊時調色和 tooltip 都不會生效。4. 數(shù)據(jù)落到區(qū)域上從名稱對賬到 series.data 對齊地圖資源接好、樣式調完真正決定圖表能不能用的是series.data里的name能不能和 GeoJSON 里的區(qū)域名對上。世界地圖尤其容易在這里翻車業(yè)務表用簡稱地圖數(shù)據(jù)用全稱業(yè)務表有空格地圖數(shù)據(jù)有連字符業(yè)務表是字符串數(shù)字地圖側按數(shù)值排序。我的習慣是先導出一份“標準名稱清單”再做別名映射最后統(tǒng)計未覆蓋區(qū)域和多余數(shù)據(jù)。4.1 從注冊數(shù)據(jù)里導出名稱清單不要靠猜。地圖注冊成功后直接通過echarts.getMap(world)拿到 GeoJSON把features里的properties.name全部列出來。這份清單就是后續(xù)數(shù)據(jù)對齊的基準。不同資源的屬性名可能不叫name所以先打印一條樣本看看。const mapInfo echarts.getMap(world); if (!mapInfo) throw new Error(地圖未注冊); const geo mapInfo.geoJSON || mapInfo.geoJson; const firstFeature geo.features[0]; console.log(第一條屬性樣本:, firstFeature.properties); const regionNames geo.features.map(f f.properties.name); console.log(區(qū)域數(shù)量:, regionNames.length); console.table(regionNames.slice(0, 20));geoJSON和geoJson做了一次兼容避免不同構建產物屬性名不一致。features是區(qū)域數(shù)組properties.name是常見名稱字段如果樣本里沒有name而是NAME、name_en之類的字段就要在map里換字段名不要繼續(xù)硬套。regionNames既是排查工具也是后面生成對照表的源數(shù)據(jù)。4.2 數(shù)據(jù)清洗別名、空值、字符串數(shù)值業(yè)務數(shù)據(jù)進來后先做一輪標準化去空格、別名映射、數(shù)值轉換、重復名稱處理。別名表不要寫得太寬寧可少映射也不要讓兩個不同區(qū)域誤合并。重復名稱要看業(yè)務含義如果是同一區(qū)域的多條記錄應該先聚合如果只是重復導入保留一條并打日志。function buildCanonicalSet(names) { return new Set(names.map(n String(n).trim())); } function normalizeRows(rows, canonicalSet) { const alias new Map([ [Region A, Region-A], [Region_B, Region-B] ]); const seen new Set(); const clean []; for (const row of rows) { let name String(row.name ?? ).trim(); name alias.get(name) || name; // 不在地圖標準名稱里的記錄先丟棄后面單獨統(tǒng)計 if (!canonicalSet.has(name)) continue; // 同名記錄只保留第一條真實業(yè)務里應先聚合再進來 if (seen.has(name)) continue; const value Number(row.value); if (!Number.isFinite(value)) continue; seen.add(name); clean.push({ name, value }); } return clean; }buildCanonicalSet把地圖名稱做成集合查詢成本低。alias只處理明確的別名比如空格、下劃線、縮寫不要用模糊匹配否則容易把相似名稱合并錯。row.name ?? 兼容null和undefinedNumber(row.value)把字符串數(shù)字轉成數(shù)值Number.isFinite過濾掉空值和非法字符。重復名稱這里簡單保留第一條真實業(yè)務里更常見的是按名稱求和或取最新值策略要寫在數(shù)據(jù)清洗層不要留到圖表配置里。4.3 series.data 對齊與缺失值排查清洗完的數(shù)據(jù)不要直接塞進series.data先做一次對賬哪些區(qū)域有數(shù)據(jù)、哪些區(qū)域缺失、哪些數(shù)據(jù)多余。缺失不一定是錯誤可能業(yè)務上就沒有多余數(shù)據(jù)則通常意味著別名表漏了或者名稱格式不一致。把這兩個數(shù)字打出來排查效率會高很多。const canonicalSet buildCanonicalSet(regionNames); const cleanData normalizeRows(rawRows, canonicalSet); const cleanSet new Set(cleanData.map(d d.name)); const missing regionNames.filter(n !cleanSet.has(n)); const extra cleanData.filter(d !canonicalSet.has(d.name)); console.log(未覆蓋區(qū)域數(shù):, missing.length); console.log(多余數(shù)據(jù)數(shù):, extra.length); console.table(extra.slice(0, 10)); chart.setOption({ series: [{ type: map, map: world, data: cleanData, emphasis: { label: { show: true } } }] });missing用來決定無數(shù)據(jù)區(qū)域怎么顯示通常交給visualMap.outOfRange的淺灰extra用來反查別名表如果extra不為空但canonicalSet里沒有說明這些名稱根本不在底圖里。cleanData里只保留能匹配上的記錄避免無效數(shù)據(jù)干擾 tooltip。最后把cleanData傳給series.data不要再在setOption里做二次名稱轉換轉換邏輯越靠前越容易復用。5. 空白、不上色、縮放漂移五個高頻翻車點的排查順序地圖類問題最怕“看起來沒報錯”??刂婆_干凈頁面空白底圖出來了區(qū)域全灰拖拽兩下地圖跑偏。下面這五類是我在項目里反復遇到的按“先資源、再數(shù)據(jù)、后交互”的順序排查基本能覆蓋大多數(shù)場景。每條都按現(xiàn)象、原因、解決寫方便對照。5.1 地圖整塊空白先查腳本順序再查容器高度現(xiàn)象頁面有容器控制臺沒有明顯報錯地圖不出現(xiàn)。原因通常有三個world.js在echarts.min.js之前加載內部調registerMap時echarts還不存在容器高度是 0地圖畫布沒有尺寸注冊名和series.map不一致。解決順序是先看echarts.getMap(world)有沒有返回值再看容器getBoundingClientRect()的高度最后核對注冊名。console.log(地圖注冊信息:, echarts.getMap(world)); console.log(容器尺寸:, document.getElementById(worldMap).getBoundingClientRect());如果第一行是undefined問題在資源加載或注冊如果第一行有對象、第二行高度是 0問題在 CSS。父級用了height: 100%但祖先沒有高度也會導致容器高度為 0。大屏項目里常見的是格子用了絕對定位內部圖表容器沒有顯式高度初始化時還是 0。5.2 只有底圖沒有顏色名稱和數(shù)值兩頭對不上現(xiàn)象地圖輪廓正常所有區(qū)域都是默認色visualMap也顯示了但顏色不生效。原因多半是series.data的name和 GeoJSON 的區(qū)域名不一致或者value是字符串、空值、重復名稱導致匹配失敗。另一個常見原因是visualMap的min/max被手寫死數(shù)據(jù)全落在范圍外。解決方式是先打印第一條匹配記錄再打印未匹配數(shù)量。console.log(第一條數(shù)據(jù):, chart.getOption().series[0].data[0]); console.log(未匹配區(qū)域數(shù):, missing.length); console.log(數(shù)據(jù)值樣本:, cleanData.slice(0, 5));chart.getOption()拿到的是合并后的配置能確認數(shù)據(jù)有沒有真正進入系列。missing.length很大先查別名和大小寫missing.length很小但顏色仍不對查visualMap.min/max和outOfRange。如果所有值都是0min和max相等色帶會退化需要給一個最小跨度。5.3 縮放后漂移或重復渲染init 被調用兩次現(xiàn)象拖拽或縮放后地圖跳到左上角或者每次切換頁面回來地圖多一層、提示框重復出現(xiàn)。原因通常是同一個 DOM 上重復echarts.init或者resize監(jiān)聽器重復綁定。單頁應用里組件本文還有配套的精品資源點擊獲取