Bản đồ ký hiệu
Địa lý
Biểu đồ #20: một bản đồ bong bóng khử chồng lấn bằng mô phỏng lực. Mục tiêu di trú cho MapSymbolForce cũ của sdg-trade - một bản đồ bong bóng chỉ-có-chấm, trong đó tọa độ của mỗi mục được chiếu lên mặt phẳng, sau đó một mô phỏng lực một lần đẩy các vòng tròn chồng lấn ra vừa đủ để dễ đọc, mà không di chuyển chúng xa khỏi vị trí thực. Khác với Bản đồ phân cấp màu, địa lý ở đây là tùy chọn: bỏ qua nó để có diện mạo chỉ-có-chấm của biểu đồ cũ (mặc định), hoặc cung cấp nó để vẽ một nền địa hình mờ nhạt phía sau các ký hiệu.
Một vòng thứ hai đồng tâm (valueSecond) được đọc như một chỉ số con của vòng tròn ngoài - ví dụ "trong đó" một đối tác hoặc kênh cụ thể:
Vị trí chính xác hay tách chồng lấn
positionMode quyết định ý nghĩa vị trí của mỗi symbol:
"force"(mặc định, giữ tương thích legacy): mô phỏng một lần đẩy các vòng tròn chồng nhau tách ra. Dễ đọc khi nhiều symbol dồn về một chỗ, nhưng symbol có thể lệch khỏi tọa độ thật, và trên biểu đồ nhỏ độ lệch có thể rất lớn."precise": mỗi symbol nằm đúng tạilng/latđã chiếu; chấp nhận chồng lấn.
Symbol vẽ nhầm sang nước khác là lỗi chính xác bản đồ, với nhiều đối tượng người xem còn là vấn đề nhạy cảm chính trị. Nên dùng "precise" khi có nền geography: thấy đất liền là người xem sẽ đọc vị trí theo nghĩa đen.
Chuyển sang force để thấy các bong bóng lệch khỏi tọa độ thật nhằm tránh va chạm. Khi có nền bản đồ, precise thường là lựa chọn trung thực hơn.
Bản đồ chỉ để minh họa - và biên giới là vấn đề chính trị
Bản đồ thế giới trên trang này là một tệp GeoJSON đơn giản hóa thuộc phạm vi công cộng, đi kèm ví dụ tài liệu chỉ để minh họa. Nó có thể sai: biên giới, tên gọi và hình dạng KHÔNG có giá trị chính thức, và một số đang bị tranh chấp. Biên giới và tên gọi nào là "đúng" khác nhau theo đối tượng, khu vực pháp lý và tổ chức, và một bản đồ sai ở điểm này là vấn đề chính trị, không phải thẩm mỹ. Không có gì được vẽ ở đây là sự công nhận đối với bất kỳ đường biên giới nào. Trước khi dùng trong sản phẩm, hãy đối chiếu biên giới và tên gọi trong tệp geography bạn truyền vào với chính sách bản đồ của tổ chức: thư viện vẽ tệp nguyên trạng và không sửa gì.
Biểu đồ ở trên là cùng một engine trong mọi framework - chỉ mã tích hợp bên dưới là khác.
Ô lục giác trên lưới tổ ong
Đôi khi mọi địa điểm cần có cùng trọng số và giá trị nằm ở màu sắc: một bản đồ ô. Đặt shape: "hexagon" và positionMode: "honeycomb", mỗi ký hiệu trở thành một lục giác cùng kích thước bám vào lưới lục giác: các ô lát kín mặt phẳng, không bao giờ chồng lấn, và ô của một quốc gia ở gần trọng tâm của nó nhất có thể trong lưới. colorScale (miền/dải scaleThreshold, cùng hợp đồng với Bản đồ choropleth) mang giá trị; các ô được tô ở độ mờ đầy đủ vì màu sắc chính là mã hóa.
Cảnh báo về bản đồ minh họa ở trên cũng áp dụng ở đây: các ô nằm trên một bản đồ minh họa, và vị trí của mỗi ô cũng như quốc gia mà nó đại diện chỉ đúng khi
geographyvà tọa độ bạn cung cấp đúng.
honeycomb-{ radius, gap, orientation, placement, reserve }: bán kính ngoại tiếp của ô tính bằng px (mặc định 11), khoảng cách giữa các cạnh ô (mặc định 2), và đỉnh"flat"(mặc định, cạnh phẳng ở trên) hoặc"pointy". Hướng chi phối cả đường viền lục giác lẫn lưới, để các ô khớp nhau.placementvàreserveđược mô tả bên dưới.- Va chạm (mặc định,
honeycomb.placement: "first") được giải quyết theo thứ tựdataSet: ô đến sau mà cell đã bị chiếm sẽ đi ra từng vòng đến cell trống đầu tiên, một cách tất định. Nếu không có cell trống trong sáu vòng, ô sẽ chồng lấn và mộtonDataWarningloạilayout-overflownêu tên nó. honeycomb.placement: "geographic"giữ các ô lân cận đúng thứ tự thực tế. Ô gần tâm của một ô lưới nhất sẽ giữ ô đó, ô bị đẩy ra nhận ô trống gần vị trí thật của nó nhất, rồi một lượt sửa sẽ hoán đổi hoặc dịch các ô lân cận đang bị đọc ngược đông thành tây hay bắc thành nam. Kết quả không phụ thuộc thứ tự trongdataSet. Nó tốn nhiều công hơn"first"(một lượt sửa sau bước chiếm ô lưới), khoảng 25 ms cho 200 ô. Các cặp lân cận vẫn bị sai thứ tự được đếm trong context (stats.outOfOrderCount, được đo với mọiplacementnên có thể so sánh"first"; với"geographic"đó là phần còn lại sau lượt sửa) và chỉ với"geographic"mới được nêu tên trong mộtonDataWarningloạilayout-out-of-order;stats.displacedCountđếm các ô không nằm ở ô lưới gần nhất của mình.honeycomb.reserve: "all"cho cả các ô không có giá trị cũng giữ một ô lưới, nên bố cục không đổi khi giá trị thay đổi (một bản đồ chuyển qua lại giữa các sản phẩm hay các năm). Các ô bị loại bởidisabledItemshoặcradiusVisibleMinvẫn không tham gia bố cục. Mặc định"valued"giữ các ô không có dữ liệu tránh sang một bên.fit: "width"tính bố cục một lần ở kích thướcwidthxheightrồi co giãn hình vẽ theo vùng chứa (không bao giờ lớn hơnwidth), nên các ô không di chuyển khi cửa sổ đổi kích thước. Mặc định"none"vẽ đúng kích thước pixel.- Không có giá trị? Bỏ qua
value. Mục được tônoDataColor(mặc định#d2d7dd, truyền"transparent"để ẩn), bị loại khỏi thang màu, và, với mặc địnhhoneycomb.reserve: "valued", không bao giờ chiếm cell: ô "không có dữ liệu" không thể đẩy dữ liệu thật đi (honeycomb.reserve: "all"cho chúng cũng giữ một ô lưới, xem bên trên). offsettrên một mục dịch chuyển nó theo px sau phép chiếu và trước khi bám lưới: lối thoát cho một nước nhỏ bị nước lớn bên cạnh che khuất.markersvẽ các ghim trang trí phía trên các ô tại một lng/lat (ví dụ trụ sở chính): ghim bản đồ tích hợp, hoặcpathcủa riêng bạn trong hộppathSize, được co giãn tớisizepx và neo ở đầu nhọn. Đặttilecho một marker (idhoặclabelcủa một ô) để ghim nằm trên ô đó, dù bố cục đã đặt ô ở đâu;lng/latvẫn là phương án dự phòng.markerstrong context giữ nguyênlng/latđã khai báo; với ghim đặt trên một ô, hãy lấyx/ycủa ô đó từsymbols. Ghim không được bố trí, không nhận hover và không có tooltip; tạo kiểu qua.symbol-map-marker. Ngữ cảnh liệt kêx/ycủa mọi ký hiệu đã đặt để bạn tự vẽ lớp phủ và chú giải.
shape: "hexagon" cũng dùng được một mình với cách đặt "force" hoặc "precise", và colorScale dùng được với hình tròn: các mảnh ghép kết hợp với nhau.
Phần thuộc về bạn
Thư viện vẽ ô, ghim và màu. Chữ chú giải, nội dung tooltip, định dạng số, nước nào là "nhà" và nhận bảng màu nào thuộc về bên sử dụng: truyền chúng qua colorScale, markers và tooltipFormatter.
Mang theo tọa độ của riêng bạn
Khác với biểu đồ cũ (vốn đóng gói một CSV tĩnh khoảng 200 dòng tọa độ quốc gia), @michi-vz/core không đóng gói bảng tọa độ nào. Mỗi mục dataSet tự cung cấp lng/lat riêng:
import { SymbolMapChart } from "@michi-vz/react";
<SymbolMapChart
dataSet={[
{ id: "usa", label: "United States", lng: -95.7, lat: 38.9, value: 100 },
{ id: "deu", label: "Germany", lng: 13.4, lat: 52.5, value: 60, valueSecond: 30 },
// ...
]}
/>Vì phạm vi (extent) dùng để khớp bố cục chỉ-có-chấm với vùng vẽ được suy ra từ tọa độ của chính tập dữ liệu của bạn (không phải từ một bảng thế giới luôn đầy đủ được đóng gói sẵn), "thế giới" mà biểu đồ vẽ ra chỉ trải rộng đến mức các điểm bạn truyền vào - xem Ghi chú hành vi bên dưới.
Đặt ký hiệu từ dữ liệu địa lý
Với nền geography, bạn có thể bỏ lng/lat: mục sẽ được đặt tại trọng tâm của đối tượng có id trùng với geoId của nó (mặc định: chính id của mục). Các dòng khóa theo mã ISO ghép với tệp bản đồ thế giới khóa cùng cách mà không cần bảng tọa độ nào; mục không khớp với đối tượng nào sẽ bị loại với cảnh báo invalid-geometry nêu rõ id đã tìm.
<SymbolMapChart
geography={world}
dataSet={[
{ id: "DEU", label: "Germany", value: 60, valueSecond: 30 },
{ id: "row-9", geoId: "FRA", label: "France", value: 40 },
]}
labelFormatter={(d) => `${d.label} ${Math.round((d.valueSecond! / d.value!) * 100)}%`}
onSymbolClick={(d) => go(d.id)}
/>labelFormatter đặt văn bản vẽ bên trong một ký hiệu đủ lớn (mặc định: nhãn), để một tỷ lệ hoặc giá trị có thể đứng cạnh tên; văn bản vẫn được vừa khít với hình tròn. onSymbolClick(item, event) được gọi khi nhấp vào một ký hiệu, trong svg cũng như qua kiểm tra va chạm trên canvas; nhấp chuột vẫn ghim tooltip.
Xem dữ liệu chạy theo năm
Gắn date cho từng ký hiệu rồi bật timeline: bản đồ thành câu chuyện theo từng năm với nút play và thanh tua riêng, mỗi lần đặt các ký hiệu của một giai đoạn. Mặc định tắt - không bật thì biểu đồ giữ nguyên.
Bấm nút play dưới biểu đồ: dữ liệu chạy qua từng năm, mỗi lần một snapshot. Kéo thanh tua để nhảy đến năm bất kỳ.
const ref = useRef<SymbolMapChartHandle>(null);
<SymbolMapChart ref={ref} {...props} timeline={{ speedMs: 1000, loop: true }} />;
// ref.current?.timeline() -> play() / pause() / seek(year) / seekIndex(i) / stepForward()<SymbolMapChart :options="{ ...props, timeline: { speedMs: 1000, loop: true } }" /><div use:symbolMapChart={{ ...props, timeline: { speedMs: 1000, loop: true } }}></div>applySymbolMapChartProps(this.c.nativeElement, { ...props, timeline: { speedMs: 1000, loop: true } });<michi-vz-symbol-map-chart id="c"></michi-vz-symbol-map-chart>
<script>
const el = document.getElementById("c");
el.timeline = { speedMs: 1000, loop: true };
// el.getTimeline() -> play() / pause() / seek(year) / seekIndex(i)
</script>speedMschỉnh nhịp chạy,loopquay vòng,autoplay: truetự chạy khi mount,showControl: falseẩn thanh điều khiển có sẵn.- Giá trị trượt mượt giữa các giai đoạn theo mặc định (
interpolate); chỉnh chuyển động bằngtweenMsvàeasing, hoặc đặtinterpolate: falseđể cắt thẳng. Khi bật reduced motion, biểu đồ luôn cắt thẳng. - Controller headless luôn sẵn sàng:
chart.timeline()choplay() / pause() / toggle() / seek(period) / seekIndex(i) / stepForward() / stepBack(), kèmonStepvàformatPeriodtrong config khi cần tự dựng UI. seek(period)tìm kỳ trước, so sánh dạng chữ, nênseek(2021)vàseek("2021")đều tới 2021 dù ngày trong dữ liệu là số hay chuỗi. Số chỉ được hiểu là vị trí (0 = kỳ đầu) khi không có kỳ nào khớp, còn chuỗi không khớp kỳ nào thì không làm gì.seekIndex(i)luôn đi theo vị trí, giống thanh tua có sẵn.- Ký hiệu không có
datehiển thị ở mọi giai đoạn.
Cách dùng
import { SymbolMapChart } from "@michi-vz/react";
export default () => <SymbolMapChart {...props} />; // props = các tùy chọn biểu đồ<script setup>
import { SymbolMapChart } from "@michi-vz/vue";
</script>
<template>
<SymbolMapChart :options="props" />
</template><script>
import { symbolMapChart } from "@michi-vz/svelte";
</script>
<div use:symbolMapChart={props}></div>// main.ts - đăng ký các phần tử một lần
import "@michi-vz/angular";
import { applySymbolMapChartProps } from "@michi-vz/angular";
// component (sử dụng CUSTOM_ELEMENTS_SCHEMA)
// template: <michi-vz-symbol-map-chart #c></michi-vz-symbol-map-chart>
applySymbolMapChartProps(this.c.nativeElement, props);<script type="module" src="https://cdn.jsdelivr.net/npm/@michi-vz/wc/dist/michi-vz-wc.bundle.js"></script>
<michi-vz-symbol-map-chart id="c"></michi-vz-symbol-map-chart>
<script>
Object.assign(document.getElementById("c"), props); // dataSet, title, …
</script>import { mountSymbolMapChart } from "@michi-vz/core";
const chart = mountSymbolMapChart(el, props);
chart.update(next);
chart.getContext(); // độc lập với renderer, sẵn sàng cho LLM
chart.destroy();API
Các thuộc tính được định kiểu là SymbolMapChartProps trong @michi-vz/core. Dùng chung cho mọi biểu đồ: width, height, margin, colors / colorsMapping, renderer ("svg", "canvas", hoặc thử nghiệm "webgpu"), highlightItems, disabledItems, và các callback on*. onChartDataProcessed / getContext() trả về ChartContext độc lập với renderer.
Ghi chú hành vi
Khử chồng lấn bằng lực một lần
lng/lat của mỗi mục được chiếu tới một vị trí pixel "thực"; forceX/forceY ghim mô phỏng vào đúng vị trí đó làm mục tiêu, forceManyBody() thêm một chút tách biệt nhẹ, và forceCollide (bán kính + đệm 2px, 3 lần lặp) mới là thứ thực sự đẩy các vòng tròn chồng lấn ra xa nhau. Mô phỏng ổn định đồng bộ tới CÙNG ngưỡng alpha mà biểu đồ cũ đã dùng (0.0011) - có tính xác định: đầu vào giống nhau luôn ổn định về cùng một bố cục, vì vậy hai lần dựng cùng dữ liệu cho ra vị trí giống hệt nhau đến từng pixel. Hai mục có tọa độ hoàn toàn giống nhau bắt đầu chồng lên nhau và tách ra gọn gàng khi mô phỏng ổn định. positionMode: "precise" bỏ qua hoàn toàn mô phỏng: mỗi symbol giữ nguyên vị trí chiếu chính xác của nó và chấp nhận chồng lấn (xem demo chuyển chế độ ở trên).
Chiếu chỉ-có-chấm so với nền
Khi không có geography, projection được chọn (mặc định "geoMercator", giống biểu đồ cũ) được dùng ở dạng chưa tinh chỉnh - một factory chiếu trần trụi không có bất kỳ tinh chỉnh translate/scale/rotate/center nào như Bản đồ phân cấp màu - và phạm vi các điểm đã chiếu sau đó được co giãn lại để lấp đầy vùng vẽ, phản ánh đúng công thức riêng của biểu đồ cũ. Khi cung cấp geography, CÙNG cơ chế điều phối đã tinh chỉnh mà Bản đồ phân cấp màu dùng sẽ thay thế, để nền địa hình và tọa độ ký hiệu chia sẻ một khung địa lý nhất quán (không co giãn phạm vi).
radiusVisibleMin - lọc trên giá trị thô
radiusVisibleMin ẩn các mục có value thô bằng hoặc dưới ngưỡng (và, khi valueSecond được đặt, có valueSecond cũng dưới ngưỡng đó) trước khi bố cục lực chạy - được chuyển thể từ bộ lọc riêng của biểu đồ cũ, vốn so sánh giá trị thô, chưa bao giờ so sánh bán kính đã co giãn. Miền của thang bán kính/độ mờ vẫn được xây dựng từ mọi mục có tọa độ hợp lệ bất kể bộ lọc này, để bán kính của một ký hiệu vẫn có thể so sánh được giữa các lần dựng ngay cả khi ngưỡng thay đổi.
Vòng thứ hai đồng tâm
valueSecond vẽ một vòng tròn thứ hai, cùng màu với vòng ngoài, ở opacity - 0.3 (giới hạn không âm), vẽ ĐÈ LÊN vòng tròn chính - chuyển thể chính xác từ cách phân lớp riêng của ForceNode cũ. Khi vòng tròn thứ hai nhỏ hơn vòng ngoài, nó đọc như một lõi mờ hơn bên trong một vòng sáng hơn (ví dụ "trong đó"); khi lớn hơn, nó che phủ hoàn toàn vòng tròn ngoài.
Nền địa lý tùy chọn
geography (một FeatureCollection GeoJSON hoặc một GeoFeatureItem[] đã chuẩn hóa sẵn, cùng hợp đồng như geography của Bản đồ phân cấp màu) là một khả năng mới - biểu đồ cũ chưa bao giờ vẽ địa hình. Bỏ qua nó để có diện mạo chỉ-có-chấm của bản cũ; cung cấp nó để có một nền mờ nhạt, không tương tác (geographyColor, mặc định #eef1f5) phía sau các ký hiệu.
Kết xuất: SVG, canvas và một WebGPU ủy quyền
renderer="svg" (mặc định) vẽ một <circle class="symbol"> cho mỗi mục hiển thị (cộng với một <circle class="symbol-second"> tùy chọn). renderer="canvas" vẽ cùng các ký hiệu đó lên canvas 2D, phân giải màu tô qua cùng một đầu dò CSS phía người dùng mà mọi biểu đồ ký hiệu đơn dùng. renderer="webgpu" ủy quyền cho renderer canvas-2D, cùng lý do như Bản đồ phân cấp màu: nền tùy chọn là GeoJSON tùy ý (có thể lõm, nhiều vòng), nên việc tam giác hóa GPU chính xác là phạm vi không tương xứng ở đây.
Đang tải / không có dữ liệu
isLoading và isNodata điều khiển lớp phủ (React: isLoadingComponent / isNodataComponent), giống hệt mọi biểu đồ khác trong hệ thống.
Thẩm quyền màu phía người dùng: ngữ cảnh mang theo
legendData({ label, color, dataLabelSafe }) để một hệ thống màu tiêm CSS có thể khóa các quy tắc theo từng nhãn;onChartDataProcessedchỉ được phát ra khi ngữ cảnh thay đổi.
