@zerodep-dev/vue-tsx-pro-ui 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. package/dist/charts/AreaChart.d.ts +1 -1
  2. package/dist/charts/AreaChart.js +6 -48
  3. package/dist/charts/AreaChart.js.map +1 -1
  4. package/dist/charts/BarChart.js +6 -49
  5. package/dist/charts/BarChart.js.map +1 -1
  6. package/dist/charts/ChordChart.d.ts +7 -2
  7. package/dist/charts/ChordChart.js +151 -60
  8. package/dist/charts/ChordChart.js.map +1 -1
  9. package/dist/charts/ComboChart.d.ts +2 -1
  10. package/dist/charts/ComboChart.js +17 -93
  11. package/dist/charts/ComboChart.js.map +1 -1
  12. package/dist/charts/DonutChart.d.ts +6 -1
  13. package/dist/charts/DonutChart.js +134 -51
  14. package/dist/charts/DonutChart.js.map +1 -1
  15. package/dist/charts/FunnelChart.js.map +1 -1
  16. package/dist/charts/GraphChart.d.ts +22 -10
  17. package/dist/charts/GraphChart.js +415 -175
  18. package/dist/charts/GraphChart.js.map +1 -1
  19. package/dist/charts/LineChart.d.ts +1 -29
  20. package/dist/charts/LineChart.js +6 -54
  21. package/dist/charts/LineChart.js.map +1 -1
  22. package/dist/charts/MapChart.d.ts +21 -48
  23. package/dist/charts/MapChart.js +426 -353
  24. package/dist/charts/MapChart.js.map +1 -1
  25. package/dist/charts/RadarChart.js.map +1 -1
  26. package/dist/charts/SankeyChart.d.ts +7 -2
  27. package/dist/charts/SankeyChart.js +164 -76
  28. package/dist/charts/SankeyChart.js.map +1 -1
  29. package/dist/charts/ScatterChart.d.ts +7 -1
  30. package/dist/charts/ScatterChart.js +45 -30
  31. package/dist/charts/ScatterChart.js.map +1 -1
  32. package/dist/charts/TimelineChart.d.ts +8 -1
  33. package/dist/charts/TimelineChart.js +141 -89
  34. package/dist/charts/TimelineChart.js.map +1 -1
  35. package/dist/charts/TreemapChart.d.ts +6 -0
  36. package/dist/charts/TreemapChart.js +174 -58
  37. package/dist/charts/TreemapChart.js.map +1 -1
  38. package/dist/charts/WaterfallChart.d.ts +6 -7
  39. package/dist/charts/WaterfallChart.js +46 -51
  40. package/dist/charts/WaterfallChart.js.map +1 -1
  41. package/dist/charts/XYChart.d.ts +44 -35
  42. package/dist/charts/XYChart.js +493 -281
  43. package/dist/charts/XYChart.js.map +1 -1
  44. package/dist/charts/axis-ticks.d.ts +1 -1
  45. package/dist/charts/axis-ticks.js +4 -5
  46. package/dist/charts/axis-ticks.js.map +1 -1
  47. package/dist/charts/flow.d.ts +4 -11
  48. package/dist/charts/flow.js +4 -4
  49. package/dist/charts/flow.js.map +1 -1
  50. package/dist/charts/frame.d.ts +6 -30
  51. package/dist/charts/frame.js +6 -99
  52. package/dist/charts/frame.js.map +1 -1
  53. package/dist/charts/geo/projection.d.ts +45 -0
  54. package/dist/charts/geo/projection.js +201 -0
  55. package/dist/charts/geo/projection.js.map +1 -0
  56. package/dist/charts/geo/topojson.d.ts +50 -0
  57. package/dist/charts/geo/topojson.js +119 -0
  58. package/dist/charts/geo/topojson.js.map +1 -0
  59. package/dist/charts/index.d.ts +21 -9
  60. package/dist/charts/index.js +19 -8
  61. package/dist/charts/label-layout.js.map +1 -1
  62. package/dist/charts/layout/chord.d.ts +44 -0
  63. package/dist/charts/layout/chord.js +45 -0
  64. package/dist/charts/layout/chord.js.map +1 -0
  65. package/dist/charts/layout/graph.d.ts +49 -0
  66. package/dist/charts/layout/graph.js +268 -0
  67. package/dist/charts/layout/graph.js.map +1 -0
  68. package/dist/charts/layout/sankey.d.ts +79 -0
  69. package/dist/charts/layout/sankey.js +200 -0
  70. package/dist/charts/layout/sankey.js.map +1 -0
  71. package/dist/charts/layout/treemap.d.ts +46 -0
  72. package/dist/charts/layout/treemap.js +152 -0
  73. package/dist/charts/layout/treemap.js.map +1 -0
  74. package/dist/charts/marks.d.ts +79 -0
  75. package/dist/charts/marks.js +231 -0
  76. package/dist/charts/marks.js.map +1 -0
  77. package/dist/charts/plot.d.ts +107 -0
  78. package/dist/charts/plot.js +231 -0
  79. package/dist/charts/plot.js.map +1 -0
  80. package/dist/charts/scale.d.ts +29 -0
  81. package/dist/charts/scale.js +244 -0
  82. package/dist/charts/scale.js.map +1 -0
  83. package/dist/charts/shape.d.ts +46 -0
  84. package/dist/charts/shape.js +131 -0
  85. package/dist/charts/shape.js.map +1 -0
  86. package/dist/charts/stack.d.ts +17 -0
  87. package/dist/charts/stack.js +35 -0
  88. package/dist/charts/stack.js.map +1 -0
  89. package/dist/charts/text-size.d.ts +12 -0
  90. package/dist/charts/text-size.js +32 -0
  91. package/dist/charts/text-size.js.map +1 -0
  92. package/dist/config/createUi.d.ts +21 -2
  93. package/dist/config/createUi.js +18 -1
  94. package/dist/config/createUi.js.map +1 -1
  95. package/dist/config/theme/preference.js +2 -1
  96. package/dist/config/theme/preference.js.map +1 -1
  97. package/dist/index.d.ts +2 -2
  98. package/dist/maps/china-provinces.js +35680 -0
  99. package/dist/maps/china-provinces.js.map +1 -0
  100. package/dist/maps/china.d.ts +13 -0
  101. package/dist/maps/china.js +8 -0
  102. package/dist/maps/china.js.map +1 -0
  103. package/dist/maps/index.d.ts +9 -0
  104. package/dist/maps/index.js +3 -0
  105. package/dist/maps/world-countries.js +22273 -0
  106. package/dist/maps/world-countries.js.map +1 -0
  107. package/dist/maps/world.d.ts +13 -0
  108. package/dist/maps/world.js +8 -0
  109. package/dist/maps/world.js.map +1 -0
  110. package/dist/overlay/detached.js.map +1 -1
  111. package/dist/utils/request/use-request.d.ts +25 -1
  112. package/dist/utils/request/use-request.js +35 -3
  113. package/dist/utils/request/use-request.js.map +1 -1
  114. package/package.json +7 -8
  115. package/dist/charts/single.d.ts +0 -41
  116. package/dist/charts/single.js +0 -66
  117. package/dist/charts/single.js.map +0 -1
@@ -1,29 +1,32 @@
1
+ import { svgNumber } from "../utils/data/number.js";
1
2
  import { toNumber } from "../utils/data/numeric.js";
2
- import { colors } from "../config/theme/tokens.js";
3
+ import { colors, t } from "../config/theme/tokens.js";
3
4
  import { useLang } from "../config/context.js";
4
5
  import { IconButton } from "../base/IconButton.js";
5
- import { useEventListener } from "../utils/dom/event-listener.js";
6
+ import { useReducedMotion } from "../utils/dom/media-query.js";
6
7
  import { useFormat } from "../config/lang/format.js";
7
8
  import { Skeleton } from "../feedback/Skeleton.js";
9
+ import { usePanZoom } from "../utils/dom/pan-zoom.js";
10
+ import { ChartTooltipBox, chartSvgStyles, pointerIn, usePlotArea } from "./plot.js";
8
11
  import { ChartFrame, chartTooltip } from "./frame.js";
9
12
  import { resolveColor } from "./series.js";
10
- import { useSingleChart } from "./single.js";
13
+ import { estimateTextWidth } from "./text-size.js";
14
+ import { createProjection } from "./geo/projection.js";
15
+ import { simplifyTopology, topologyBounds, topologyFeatures } from "./geo/topojson.js";
11
16
  import { placeLabels, rectsOverlap } from "./label-layout.js";
12
17
  import { computed, createTextVNode, createVNode, ref, shallowRef } from "vue";
13
18
  import { _component, effect, mergeJsxProps, readonlyProp, restProps } from "@zerodep-dev/vue-tsx-pro-tsx";
14
19
  import { css } from "@zerodep-dev/vue-tsx-pro-tsx/styles";
15
20
  import { Maximize, Minus, Plus } from "@lucide/icons";
16
- import { MapProjection, TopoJSONMap } from "@unovis/ts";
17
- import { presimplify, simplify } from "topojson-simplify";
18
21
  //#region src/charts/MapChart.tsx
19
22
  /**
20
23
  * 地图:按区域着色(分省的销售额、各国的采购额),标出地点(工厂、供应商)与它们之间的流向。
21
- * 地理数据是 TopoJSON:Unovis 自带中国省份、世界、美国等(`@unovis/ts/maps`),也可以用自己的。
24
+ * 地理数据是 TopoJSON:`@zerodep-dev/vue-tsx-pro-ui/maps` 带着中国省份与世界各国,也可以用自己的。
22
25
  *
23
- * import { ChinaTopoJSON } from '@unovis/ts/maps';
26
+ * import { chinaProvinces } from '@zerodep-dev/vue-tsx-pro-ui/maps';
24
27
  * <MapChart
25
28
  * aria-label="各省销售额"
26
- * topology={ChinaTopoJSON}
29
+ * topology={chinaProvinces}
27
30
  * feature="provinces"
28
31
  * areas={[{ id: 'CN-JS', label: '江苏', value: 623 }, …]} // id 是地图要素的 id
29
32
  * points={[{ id: 'sh', label: '上海组装', longitude: 121.47, latitude: 31.23, value: 860 }]}
@@ -32,49 +35,77 @@ import { presimplify, simplify } from "topojson-simplify";
32
35
  * areaLabels
33
36
  * />
34
37
  *
35
- * 投影:默认按地理数据的范围选——一个国家、一个地区用以它为中心的等积圆锥投影(中国地图常用的 Albers,正着、不歪),
36
- * 跨了大半个地球时用 Natural Earth;也可以指定 mercator、equalEarth、orthographic(地球仪)或自己的 d3 投影。
38
+ * 投影(见 createProjection):默认按地理数据的范围选——一个国家、一个地区用以它为中心的等积圆锥投影
39
+ * (中国地图常用的 Albers,正着、不歪),跨了大半个地球时用 Natural Earth;也可以指定 mercator、equalEarth、
40
+ * equirectangular、orthographic(地球仪)或自己的投影函数(d3-geo 的投影可以直接传)。
41
+ * 地理数据先按绘图区的像素简化(看不出的点去掉),再投影、按绘图区缩放居中。
37
42
  *
38
43
  * 着色:sequential(默认)按数值深浅;diverging 以 midpoint 为界两头两种颜色(同比的涨跌);
39
44
  * bins 分成几档(分级着色,图例是几个色块),不写时连续着色(图例是色带);区域写了 color 时按它(分类着色)。
40
45
  * 没有数据的区域是底色。地点:value 决定大小(面积按比例),color、shape 决定颜色与形状;名字自动避让
41
46
  * (换方位、挤时往外退并画引线,数值大的先放,放不下的悬停看;压住名字、地点的区域名藏起来);
42
- * links 是地点之间的流向,粗细按 value,flow 时有流动的粒子。
43
- * zoomable 时可以拖动、按住 Ctrl 滚轮缩放,右上角有放大、缩小、复位的按钮。悬停看名称与数值;读屏的数据表列出各区域、地点与流向。
47
+ * links 是地点之间的流向,粗细按 value,flow 时有流动的粒子(“减少动态效果”时不播)。
48
+ * zoomable 时可以拖动、按住 Ctrl 滚轮缩放,右上角有放大、缩小、复位的按钮;缩放时区域放大,地点与文字保持原来的大小。
49
+ * 悬停看名称与数值;读屏的数据表列出各区域、地点与流向。
44
50
  */
45
51
  /** 一个区域的数据 */
46
52
  /** 一个地点 */
47
53
  /** 两个地点之间的流向 */
48
54
  /** 投影的预设 */
49
- /** d3 的投影(TopoJSONMap 的 projection) */
50
55
  /** 未声明的属性落到外层 div(role="figure")上;aria-label 是图表的名称 */
51
56
  /** MapChart 的样式 */
52
57
  var mapChartStyles = css.create({
53
- root: {
54
- "--vis-map-feature-color": colors.fill,
55
- "--vis-map-boundary-color": colors.surface,
56
- "--vis-map-point-label-text-color-dark": colors.text,
57
- "--vis-map-point-label-text-color-light": colors.text,
58
- "--vis-map-area-label-text-color": colors.text,
59
- "--vis-map-area-label-font-size": "10px",
60
- "--vis-map-area-label-font-weight": "400",
61
- "--vis-map-point-default-stroke-color": colors.surface,
62
- "--vis-map-point-default-stroke-width": "1.5px"
63
- },
64
58
  skeleton: {
65
59
  display: "block",
66
60
  width: "100%",
67
61
  height: "100%"
68
62
  },
69
- pointLabels: {
70
- fontWeight: 600,
71
- fill: colors.text,
72
- stroke: colors.surface,
73
- paintOrder: "stroke",
74
- strokeLinejoin: "round",
63
+ viewport: {
64
+ position: "absolute",
65
+ inset: 0
66
+ },
67
+ feature: {
68
+ stroke: "_surface",
69
+ strokeWidth: .8,
70
+ fillRule: "evenodd",
71
+ transition: `fill ${t.durations.md} ${t.easings.standard}`
72
+ },
73
+ featureHover: {
74
+ fill: "none",
75
+ stroke: "_text",
76
+ strokeWidth: 1.5,
75
77
  pointerEvents: "none"
76
78
  },
77
- leader: { stroke: colors.textMuted },
79
+ sphere: {
80
+ fill: "_surfaceMuted",
81
+ stroke: "_border",
82
+ strokeWidth: 1
83
+ },
84
+ link: {
85
+ fill: "none",
86
+ strokeOpacity: .7,
87
+ strokeLinecap: "round",
88
+ pointerEvents: "none"
89
+ },
90
+ particle: { pointerEvents: "none" },
91
+ point: {
92
+ stroke: "_surface",
93
+ strokeWidth: 1.5
94
+ },
95
+ clickable: { cursor: "pointer" },
96
+ pointLabel: {
97
+ fill: "_text",
98
+ fontSize: "11px",
99
+ fontWeight: "_semibold"
100
+ },
101
+ areaLabel: {
102
+ fill: "_text",
103
+ fontSize: "10px"
104
+ },
105
+ leader: {
106
+ stroke: "_textMuted",
107
+ strokeWidth: 1
108
+ },
78
109
  zoom: {
79
110
  display: "inline-flex",
80
111
  alignItems: "center",
@@ -116,81 +147,30 @@ var mapChartStyles = css.create({
116
147
  },
117
148
  swatchColor: (color) => ({ backgroundColor: color })
118
149
  });
119
- /** 地点的标签四周留的空(屏幕像素) */
120
- var labelPadding = 3;
121
- /** 新建一个 SVG 元素 */
122
- var svgElement = (name) => document.createElementNS("http://www.w3.org/2000/svg", name);
150
+ /** 地点名字的字号、区域名的字号(px) */
151
+ var POINT_FONT = 11;
152
+ var AREA_FONT = 10;
123
153
  /** 混色:基色占 percent,其余透明(落在底色上) */
124
154
  var tint = (color, percent) => `color-mix(in srgb, ${color} ${Math.round(percent)}%, transparent)`;
125
- /** 算好了各点权重的地理数据:同一份 TopoJSON 只算一次 */
126
- var presimplified = /* @__PURE__ */ new WeakMap();
127
- /**
128
- * 简化过的地理数据放进这个类的实例:Unovis 的 setConfig 深拷贝配置,但跳过类的实例,
129
- * 每次更新就不必把整份 TopoJSON 再拷一遍
130
- */
131
- var SharedTopology = class {};
132
- /** 简化过的地理数据:TopoJSON → 档位 → 结果 */
133
- var simplified = /* @__PURE__ */ new WeakMap();
134
- /**
135
- * 简化地理数据:去掉与前后两点围成的三角形面积不到 minArea(坐标单位的平方,经纬度是平方度)的点
136
- * (Visvalingam,topojson-simplify)。相邻区域共用的边界一起简化,不会出现缝隙;各段弧的两端总保留。
137
- * minArea 按 2 的整数次幂分档缓存:绘图区大小变一点不重算。
138
- */
139
- function simplifyTopology(topology, minArea) {
140
- if (!(minArea > 0) || !Number.isFinite(minArea)) return topology;
141
- const level = 2 ** Math.floor(Math.log2(minArea));
142
- let byLevel = simplified.get(topology);
143
- if (!byLevel) simplified.set(topology, byLevel = /* @__PURE__ */ new Map());
144
- let result = byLevel.get(level);
145
- if (!result) {
146
- let weighted = presimplified.get(topology);
147
- if (!weighted) presimplified.set(topology, weighted = presimplify(topology));
148
- result = Object.assign(new SharedTopology(), simplify(weighted, level));
149
- byLevel.set(level, result);
150
- }
151
- return result;
155
+ /** 一组环写成路径;坐标保留一位小数(像素) */
156
+ function ringsPath(rings) {
157
+ const round = (value) => Math.round(value * 10) / 10;
158
+ return rings.map((ring) => `M${ring.map(([x, y]) => `${round(x)},${round(y)}`).join("L")}Z`).join("");
152
159
  }
153
- /** TopoJSON 的经纬度范围 [西, 南, 东, 北]:有 bbox 用它,否则解出各段弧的坐标(量化的弧是逐点的差值) */
154
- function topologyBounds(topology) {
155
- if (topology.bbox) {
156
- const [west, south, east, north] = topology.bbox;
157
- return [
158
- west,
159
- south,
160
- east,
161
- north
162
- ];
160
+ /** 地点的标记:中心在 (x, y)、半径 r 的路径 */
161
+ function markerPath(shape, x, y, r) {
162
+ const [cx, cy] = [svgNumber(x), svgNumber(y)];
163
+ if (shape === "square") {
164
+ const half = svgNumber(r * .9);
165
+ return `M${cx - half},${cy - half}h${half * 2}v${half * 2}h${-half * 2}Z`;
163
166
  }
164
- const quantized = topology.transform;
165
- const [scaleX, scaleY] = quantized?.scale ?? [1, 1];
166
- const [translateX, translateY] = quantized?.translate ?? [0, 0];
167
- let [west, south, east, north] = [
168
- Infinity,
169
- Infinity,
170
- -Infinity,
171
- -Infinity
172
- ];
173
- for (const arc of topology.arcs) {
174
- let x = 0;
175
- let y = 0;
176
- for (const position of arc) {
177
- x = quantized ? x + position[0] : position[0];
178
- y = quantized ? y + position[1] : position[1];
179
- const longitude = x * scaleX + translateX;
180
- const latitude = y * scaleY + translateY;
181
- west = Math.min(west, longitude);
182
- east = Math.max(east, longitude);
183
- south = Math.min(south, latitude);
184
- north = Math.max(north, latitude);
185
- }
167
+ if (shape === "triangle") {
168
+ const h = svgNumber(r * 1.15);
169
+ return `M${cx},${svgNumber(cy - h)}L${svgNumber(cx + h)},${svgNumber(cy + h * .75)}L${svgNumber(cx - h)},${svgNumber(cy + h * .75)}Z`;
186
170
  }
187
- return [
188
- west,
189
- south,
190
- east,
191
- north
192
- ];
171
+ return `M${svgNumber(cx - r)},${cy}A${r},${r},0,1,1,${svgNumber(cx + r)},${cy}A${r},${r},0,1,1,${svgNumber(cx - r)},${cy}Z`;
193
172
  }
173
+ /** 悬停的区域或地点 */
194
174
  /** 地图 */
195
175
  var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
196
176
  const _cache = [];
@@ -217,12 +197,15 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
217
197
  const tooltip = readonlyProp(_props, "tooltip", true);
218
198
  const onAreaClick = readonlyProp(_props, "onAreaClick");
219
199
  const onPointClick = readonlyProp(_props, "onPointClick");
220
- const sx = readonlyProp(_props, "sx");
221
200
  const rest = restProps(_props, _ctx, true);
222
201
  const lang = useLang();
223
202
  const format = useFormat();
203
+ const area = usePlotArea();
204
+ const reducedMotion = useReducedMotion();
205
+ const viewport = shallowRef(null);
206
+ let hovered = shallowRef(null);
224
207
  const bounds = computed(() => topologyBounds(topology.value));
225
- const numbers = computed(() => areas.value.flatMap((area) => area.value === void 0 ? [] : [toNumber(area.value)]));
208
+ const numbers = computed(() => areas.value.flatMap((item) => item.value === void 0 ? [] : [toNumber(item.value)]));
226
209
  const extent = computed(() => numbers.value.length ? [Math.min(...numbers.value), Math.max(...numbers.value)] : null);
227
210
  const largest = (values) => Math.max(0, ...values.map((item) => item.value === void 0 ? 0 : toNumber(item.value)));
228
211
  const valueText = (value) => value === void 0 ? lang.emptyValue : format.number(value, unit.value ? {
@@ -230,6 +213,8 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
230
213
  suffix: unit.value
231
214
  } : valueFormat.value);
232
215
  const numberText = (value) => format.number(value, valueFormat.value);
216
+ const areaById = computed(() => new Map(areas.value.map((item) => [item.id, item])));
217
+ const pointById = computed(() => new Map(points.value.map((point) => [point.id, point])));
233
218
  /** 数值 → 颜色:sequential 从 12% 到 100%;diverging 两头各自从分界往外加深;bins 时按档取 */
234
219
  function colorOf(value) {
235
220
  const [low, high] = extent.value ?? [0, 0];
@@ -244,129 +229,155 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
244
229
  if (bins.value) ratio = Math.min(Math.floor(ratio * bins.value), bins.value - 1) / Math.max(bins.value - 1, 1);
245
230
  return tint(resolveColor(color.value, 0), 12 + ratio * 88);
246
231
  }
247
- const areaColor = (area) => area.color ? resolveColor(area.color, 0) : area.value === void 0 ? void 0 : colorOf(toNumber(area.value));
248
- /** 投影:预设按中心旋转;不写时按范围选 */
249
- function createProjection() {
250
- if (projection.value && typeof projection.value !== "string") return projection.value;
251
- const [west, south, east, north] = bounds.value;
252
- const [longitude, latitude] = center.value ?? [(west + east) / 2, (south + north) / 2];
253
- const kind = projection.value ?? (east - west > 180 ? "naturalEarth" : "conic");
254
- if (kind === "conic") return MapProjection.ConicEqualArea().rotate([-longitude, 0]).parallels([south + (north - south) / 6, north - (north - south) / 6]);
255
- if (kind === "orthographic") return MapProjection.Orthographic().rotate([-longitude, -latitude]);
256
- return (kind === "mercator" ? MapProjection.Mercator : kind === "equalEarth" ? MapProjection.EqualEarth : MapProjection.NaturalEarth1)().rotate([-longitude, 0]);
257
- }
258
- let mapKey = [];
259
- let generation = 0;
260
- let mapProjection;
261
- function updateProjection() {
262
- const key = [
263
- projection.value,
264
- center.value?.[0],
265
- center.value?.[1],
266
- ...bounds.value
267
- ];
268
- if (key.some((item, index) => item !== mapKey[index])) {
269
- if (mapKey.length) {
270
- map?.destroy();
271
- generation += 1;
232
+ const areaColor = (item) => !item ? void 0 : item.color ? resolveColor(item.color, 0) : item.value === void 0 ? void 0 : colorOf(toNumber(item.value));
233
+ const pointMax = computed(() => largest(points.value));
234
+ const radiusOf = (point) => point.value === void 0 || pointMax.value === 0 ? 4 : 3 + Math.sqrt(toNumber(point.value) / pointMax.value) * 5;
235
+ let wheelHint = ref(false);
236
+ let hintTimer;
237
+ effect(() => () => clearTimeout(hintTimer));
238
+ const view = usePanZoom({
239
+ viewport: () => viewport.value,
240
+ min: 1,
241
+ max: 12,
242
+ step: 1.5,
243
+ disabled: () => !zoomable.value,
244
+ filter: (event) => {
245
+ if (event instanceof WheelEvent && !event.ctrlKey && !event.metaKey) {
246
+ wheelHint.value = true;
247
+ clearTimeout(hintTimer);
248
+ hintTimer = setTimeout(() => wheelHint.value = false, 1500);
249
+ return false;
272
250
  }
273
- mapKey = key;
274
- mapProjection = createProjection();
251
+ return true;
275
252
  }
276
- }
277
- function drawnTopology() {
278
- const width = plot.value?.clientWidth ?? 0;
279
- const height = plot.value?.clientHeight ?? 0;
280
- if (!width || !height) return topology.value;
253
+ });
254
+ /** 投影:预设按中心旋转;不写时按范围选 */
255
+ const projector = computed(() => {
281
256
  const [west, south, east, north] = bounds.value;
282
- const degreesPerPixel = Math.max((east - west) / width, (north - south) / height) / (zoomable.value ? 2 : 1);
283
- return simplifyTopology(topology.value, degreesPerPixel ** 2);
284
- }
285
- const pointOf = (id) => points.value.find((point) => point.id === id);
286
- /** Unovis 画连线时给的是 { source, target }(克隆过的地点),不是原来的流向:按两端的 id 找回 */
287
- const linkOf = (drawn) => {
288
- const { source, target } = drawn;
289
- return links.value.find((link) => link.from === source.id && link.to === target.id);
290
- };
291
- let map;
292
- let plot = shallowRef(null);
293
- const pointMax = computed(() => largest(points.value));
294
- const radiusOf = (point) => point.value === void 0 || pointMax.value === 0 ? 4 : 3 + Math.sqrt(toNumber(point.value) / pointMax.value) * 5;
295
- let labelFrame = 0;
296
- /** 画完、缩放之后,下一帧重排地点的标签 */
297
- function scheduleLabels() {
298
- cancelAnimationFrame(labelFrame);
299
- labelFrame = requestAnimationFrame(layoutLabels);
300
- }
301
- effect(() => () => cancelAnimationFrame(labelFrame));
257
+ const middle = center.value ?? [(west + east) / 2, (south + north) / 2];
258
+ const name = projection.value ?? (east - west > 180 ? "naturalEarth" : "conic");
259
+ return createProjection(name, {
260
+ center: middle,
261
+ bounds: bounds.value
262
+ });
263
+ });
302
264
  /**
303
- * 地点的标签:Unovis 的标签相撞时只会藏起来,这里用 placeLabels 挪到放得下的方位(挤时画引线),
304
- * 画在地点那一组里(跟着缩放、拖动);压在标签、地点上的区域名藏起来。
305
- * 按屏幕像素算:地点那一组的坐标乘缩放级别
265
+ * 区域的路径:按绘图区里一像素对应的经纬度简化(三角形不到一平方像素的点去掉;能缩放时留到放大一倍也看不出折角),
266
+ * 投影后按绘图区缩放、居中。地点、连线用同一个换算(toScreen)
306
267
  */
307
- function layoutLabels() {
308
- const root = map?.g.node();
309
- const group = root?.querySelector(`.${TopoJSONMap.selectors.points}`);
310
- const svg = group?.ownerSVGElement;
311
- if (!root || !group || !svg || !mapProjection) return;
312
- const projectionAt = mapProjection;
313
- const zoom = Number(svg.style.getPropertyValue("--vis-map-current-zoom-level")) || 1;
314
- const layer = group.querySelector(":scope > [data-point-labels]") ?? svgElement("g");
315
- layer.setAttribute("data-point-labels", "");
316
- layer.setAttribute("class", css.attrs(mapChartStyles.pointLabels).class ?? "");
317
- layer.style.fontSize = `${11 / zoom}px`;
318
- layer.style.strokeWidth = `${3 / zoom}px`;
319
- group.append(layer);
320
- const items = points.value.flatMap((point) => {
321
- const position = projectionAt([point.longitude, point.latitude]);
322
- if (!position || !point.label) return [];
323
- const text = svgElement("text");
324
- text.textContent = point.label;
325
- text.setAttribute("text-anchor", "middle");
326
- return [{
327
- point,
328
- text,
329
- x: position[0] * zoom,
330
- y: position[1] * zoom
331
- }];
332
- });
333
- layer.replaceChildren(...items.map((item) => item.text));
334
- const glyphs = items.map((item) => item.text.getBBox());
335
- const anchors = items.map((item, index) => {
336
- const box = glyphs[index];
337
- return {
338
- x: item.x,
339
- y: item.y,
340
- radius: radiusOf(item.point),
341
- width: box.width * zoom + 6,
342
- height: box.height * zoom + 6,
343
- priority: item.point.value === void 0 ? 0 : toNumber(item.point.value)
344
- };
345
- });
346
- const areaTexts = [...root.querySelectorAll(`text.${TopoJSONMap.selectors.areaLabel}`)].filter((text) => text.getAttribute("visibility") !== "hidden");
347
- const areaBoxes = areaTexts.map((text) => {
348
- text.style.display = "";
349
- const { e, f } = text.transform.baseVal.consolidate()?.matrix ?? {
350
- e: 0,
351
- f: 0
268
+ const geometry = computed(() => {
269
+ const { width, height: plotHeight } = area;
270
+ const [west, south, east, north] = bounds.value;
271
+ const degreesPerPixel = width && plotHeight ? Math.max((east - west) / width, (north - south) / plotHeight) / (zoomable.value ? 2 : 1) : 0;
272
+ const projected = topologyFeatures(simplifyTopology(topology.value, degreesPerPixel ** 2), feature.value).map((item) => ({
273
+ feature: item,
274
+ rings: item.polygons.flatMap((polygon) => projector.value.polygon(polygon)).filter((ring) => ring.length > 2)
275
+ }));
276
+ let [x0, y0, x1, y1] = [
277
+ Infinity,
278
+ Infinity,
279
+ -Infinity,
280
+ -Infinity
281
+ ];
282
+ for (const item of projected) for (const ring of item.rings) for (const [x, y] of ring) {
283
+ x0 = Math.min(x0, x);
284
+ x1 = Math.max(x1, x);
285
+ y0 = Math.min(y0, y);
286
+ y1 = Math.max(y1, y);
287
+ }
288
+ const fits = Number.isFinite(x0) && x1 > x0 && y1 > y0;
289
+ const k = fits ? Math.min((width - 16) / (x1 - x0), (plotHeight - 16) / (y1 - y0)) : 1;
290
+ const tx = fits ? (width - (x1 - x0) * k) / 2 - x0 * k : 0;
291
+ const ty = fits ? (plotHeight - (y1 - y0) * k) / 2 - y0 * k : 0;
292
+ const toScreen = ([x, y]) => [x * k + tx, y * k + ty];
293
+ return {
294
+ toScreen,
295
+ sphere: projector.value.sphere && {
296
+ ...projector.value.sphere,
297
+ cx: tx,
298
+ cy: ty,
299
+ r: projector.value.sphere.r * k
300
+ },
301
+ shapes: projected.map((item) => {
302
+ const screen = item.rings.map((ring) => ring.map(toScreen));
303
+ let best;
304
+ for (const ring of screen) {
305
+ const xs = ring.map(([x]) => x);
306
+ const ys = ring.map(([, y]) => y);
307
+ const box = {
308
+ x: Math.min(...xs),
309
+ y: Math.min(...ys),
310
+ width: Math.max(...xs) - Math.min(...xs),
311
+ height: Math.max(...ys) - Math.min(...ys)
312
+ };
313
+ if (!best || box.width * box.height > best.size) best = {
314
+ box,
315
+ size: box.width * box.height
316
+ };
317
+ }
318
+ return {
319
+ id: item.feature.id,
320
+ d: ringsPath(screen),
321
+ box: best?.box
322
+ };
323
+ })
324
+ };
325
+ });
326
+ /** 缩放后屏幕上的位置:地点、连线、文字不随缩放变大,只跟着移动 */
327
+ const zoomed = ([x, y]) => [x * view.scale + view.x, y * view.scale + view.y];
328
+ /** 地点在屏幕上的位置;看不见(地球背面)的不画 */
329
+ const placedPoints = computed(() => points.value.flatMap((point, index) => {
330
+ const projected = projector.value.point([point.longitude, point.latitude]);
331
+ if (!projected) return [];
332
+ const [x, y] = zoomed(geometry.value.toScreen(projected));
333
+ return [{
334
+ point,
335
+ index,
336
+ x,
337
+ y,
338
+ r: radiusOf(point)
339
+ }];
340
+ }));
341
+ /** 地点的名字:placeLabels 挪到放得下的方位(挤时画引线);压住名字、地点的区域名藏起来 */
342
+ const labels = computed(() => {
343
+ const anchors = placedPoints.value.map((item) => ({
344
+ x: item.x,
345
+ y: item.y,
346
+ radius: item.r,
347
+ width: estimateTextWidth(item.point.label, POINT_FONT) + 6,
348
+ height: 19.75,
349
+ priority: item.point.value === void 0 ? 0 : toNumber(item.point.value)
350
+ }));
351
+ const areaBoxes = areaLabels.value ? geometry.value.shapes.flatMap((shape) => {
352
+ const info = areaById.value.get(shape.id);
353
+ if (!info || !shape.box) return [];
354
+ const width = estimateTextWidth(info.label, AREA_FONT);
355
+ const box = {
356
+ x: shape.box.x * view.scale + view.x,
357
+ y: shape.box.y * view.scale + view.y,
358
+ width: shape.box.width * view.scale,
359
+ height: shape.box.height * view.scale
352
360
  };
353
- const box = text.getBBox();
354
- return {
355
- x: (e + box.x) * zoom,
356
- y: (f + box.y) * zoom,
357
- width: box.width * zoom,
358
- height: box.height * zoom
361
+ if (box.width < width + 4 || box.height < 14) return [];
362
+ const rect = {
363
+ x: box.x + box.width / 2 - width / 2,
364
+ y: box.y + box.height / 2 - AREA_FONT / 2,
365
+ width,
366
+ height: AREA_FONT
359
367
  };
360
- });
361
- const bounds = zoom === 1 ? {
362
- x: 0,
363
- y: 0,
364
- width: svg.clientWidth,
365
- height: svg.clientHeight
366
- } : void 0;
368
+ return [{
369
+ label: info.label,
370
+ rect
371
+ }];
372
+ }) : [];
367
373
  const placements = placeLabels(anchors, {
368
- bounds,
369
- soft: areaBoxes
374
+ bounds: view.scale === 1 ? {
375
+ x: 0,
376
+ y: 0,
377
+ width: area.width,
378
+ height: area.height
379
+ } : void 0,
380
+ soft: areaBoxes.map((item) => item.rect)
370
381
  });
371
382
  const taken = anchors.map((anchor) => ({
372
383
  x: anchor.x - anchor.radius,
@@ -374,128 +385,191 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
374
385
  width: anchor.radius * 2,
375
386
  height: anchor.radius * 2
376
387
  }));
377
- placements.forEach((placement, index) => {
378
- const { text } = items[index];
379
- if (!placement) {
380
- text.remove();
381
- return;
382
- }
383
- const { box, leader } = placement;
384
- taken.push(box);
385
- text.setAttribute("x", String((box.x + box.width / 2) / zoom));
386
- text.setAttribute("y", String((box.y + labelPadding) / zoom - glyphs[index].y));
387
- if (leader) layer.insertBefore(leaderLine(anchors[index], box, zoom), text);
388
- });
389
- areaTexts.forEach((text, index) => {
390
- if (taken.some((rect) => rectsOverlap(rect, areaBoxes[index]))) text.style.display = "none";
388
+ for (const placement of placements) if (placement) taken.push(placement.box);
389
+ return {
390
+ points: placements.map((placement, index) => ({
391
+ placement,
392
+ anchor: anchors[index],
393
+ item: placedPoints.value[index]
394
+ })),
395
+ areas: areaBoxes.filter((item) => !taken.some((rect) => rectsOverlap(rect, item.rect)))
396
+ };
397
+ });
398
+ /** 连线:两个地点之间往上拱起的弧,粗细按数值 */
399
+ const linkShapes = computed(() => {
400
+ const linkMax = largest(links.value);
401
+ const byId = new Map(placedPoints.value.map((item) => [item.point.id, item]));
402
+ return links.value.flatMap((link, index) => {
403
+ const from = byId.get(link.from);
404
+ const to = byId.get(link.to);
405
+ if (!from || !to) return [];
406
+ const dx = to.x - from.x;
407
+ const dy = to.y - from.y;
408
+ const length = Math.hypot(dx, dy) || 1;
409
+ const [nx, ny] = dx >= 0 ? [dy / length, -dx / length] : [-dy / length, dx / length];
410
+ const bend = length * .2;
411
+ const cx = (from.x + to.x) / 2 + nx * bend;
412
+ const cy = (from.y + to.y) / 2 + ny * bend;
413
+ return [{
414
+ link,
415
+ index,
416
+ length,
417
+ d: `M${svgNumber(from.x)},${svgNumber(from.y)}Q${svgNumber(cx)},${svgNumber(cy)},${svgNumber(to.x)},${svgNumber(to.y)}`,
418
+ width: link.value === void 0 || linkMax === 0 ? 1.5 : 1 + toNumber(link.value) / linkMax * 4
419
+ }];
391
420
  });
421
+ });
422
+ function onPointermove(event) {
423
+ const target = event.target.closest?.("[data-feature],[data-point]");
424
+ if (!target) {
425
+ hovered.value = null;
426
+ return;
427
+ }
428
+ const { x, y } = pointerIn(event, viewport.value ?? event.currentTarget);
429
+ const point = target.getAttribute("data-point");
430
+ hovered.value = point === null ? {
431
+ kind: "feature",
432
+ index: Number(target.getAttribute("data-feature")),
433
+ x,
434
+ y
435
+ } : {
436
+ kind: "point",
437
+ index: Number(point),
438
+ x,
439
+ y
440
+ };
392
441
  }
393
- /** 引线:从标记的边到标签矩形上离标记最近的点 */
394
- function leaderLine(anchor, box, zoom) {
395
- const toX = Math.min(Math.max(anchor.x, box.x), box.x + box.width);
396
- const toY = Math.min(Math.max(anchor.y, box.y), box.y + box.height);
397
- const length = Math.hypot(toX - anchor.x, toY - anchor.y) || 1;
398
- const line = svgElement("line");
399
- line.setAttribute("class", css.attrs(mapChartStyles.leader).class ?? "");
400
- line.setAttribute("x1", String((anchor.x + (toX - anchor.x) / length * anchor.radius) / zoom));
401
- line.setAttribute("y1", String((anchor.y + (toY - anchor.y) / length * anchor.radius) / zoom));
402
- line.setAttribute("x2", String(toX / zoom));
403
- line.setAttribute("y2", String(toY / zoom));
404
- line.style.strokeWidth = `${1 / zoom}px`;
405
- return line;
442
+ function onClick(event) {
443
+ const target = event.target.closest?.("[data-feature],[data-point]");
444
+ if (!target) return;
445
+ const point = target.getAttribute("data-point");
446
+ if (point !== null) {
447
+ onPointClick.value?.(points.value[Number(point)]);
448
+ return;
449
+ }
450
+ const shape = geometry.value.shapes[Number(target.getAttribute("data-feature"))];
451
+ const info = shape && areaById.value.get(shape.id);
452
+ if (info) onAreaClick.value?.(info);
406
453
  }
407
- const chart = useSingleChart(() => ({
408
- element: plot.value,
409
- ready: !loading.value,
410
- tooltip: tooltip.value,
411
- build: (instance) => {
412
- const linkMax = largest(links.value);
413
- updateProjection();
414
- map = instance(`map-${generation}`, () => {
415
- const created = new TopoJSONMap();
416
- created.g.select("filter#heatmapFilter").remove();
417
- const observer = new MutationObserver(scheduleLabels);
418
- queueMicrotask(() => {
419
- const svg = created.g.node()?.parentElement;
420
- if (svg) observer.observe(svg, {
421
- attributes: true,
422
- attributeFilter: ["style"]
423
- });
424
- });
425
- const destroy = created.destroy.bind(created);
426
- created.destroy = () => {
427
- observer.disconnect();
428
- destroy();
429
- };
430
- return created;
431
- });
432
- map.setConfig({
433
- topojson: drawnTopology(),
434
- mapFeatureName: feature.value,
435
- projection: mapProjection,
436
- disableZoom: !zoomable.value,
437
- areaId: (area) => area.id,
438
- areaColor,
439
- areaLabel: areaLabels.value ? (area) => area.label : void 0,
440
- areaCursor: onAreaClick.value ? () => "pointer" : void 0,
441
- pointId: (point) => point.id,
442
- longitude: (point) => point.longitude,
443
- latitude: (point) => point.latitude,
444
- pointColor: (point) => resolveColor(point.color ?? "danger", 0),
445
- pointRadius: radiusOf,
446
- pointShape: (point) => point.shape ?? "circle",
447
- pointCursor: onPointClick.value ? () => "pointer" : void 0,
448
- pointLabel: () => "",
449
- pointBottomLabel: () => "",
450
- linkSource: (link) => link.from,
451
- linkTarget: (link) => link.to,
452
- linkColor: (drawn) => resolveColor(linkOf(drawn)?.color ?? "neutral", 0),
453
- linkWidth: (drawn) => {
454
- const value = linkOf(drawn)?.value;
455
- return value === void 0 || linkMax === 0 ? 1.5 : 1 + toNumber(value) / linkMax * 4;
456
- },
457
- enableFlowAnimation: flow.value,
458
- sourceLongitude: (link) => pointOf(link.from)?.longitude,
459
- sourceLatitude: (link) => pointOf(link.from)?.latitude,
460
- targetLongitude: (link) => pointOf(link.to)?.longitude,
461
- targetLatitude: (link) => pointOf(link.to)?.latitude,
462
- sourcePointRadius: 0,
463
- flowParticleColor: (link) => resolveColor(link.color ?? "accent", 0),
464
- flowParticleRadius: 2,
465
- flowParticleDensity: .15,
466
- events: {
467
- [TopoJSONMap.selectors.feature]: { click: (shape) => {
468
- if (shape.data) onAreaClick.value?.(shape.data);
469
- } },
470
- [TopoJSONMap.selectors.point]: { click: (point) => onPointClick.value?.(point) }
471
- }
472
- });
454
+ function renderPlot() {
455
+ const { width, height: plotHeight } = area;
456
+ const { shapes, sphere } = geometry.value;
457
+ const current = hovered.value;
458
+ const hoveredShape = current?.kind === "feature" ? shapes[current.index] : void 0;
459
+ return createVNode("svg", mergeJsxProps({
460
+ "width": width,
461
+ "height": plotHeight
462
+ }, css.attrs(chartSvgStyles.svg), { "onClick": onClick }), [
463
+ createVNode("g", mergeJsxProps({
464
+ "data-part": "features",
465
+ "transform": `translate(${svgNumber(view.x)} ${svgNumber(view.y)}) scale(${svgNumber(view.scale)})`
466
+ }, css.attrs(chartSvgStyles.fadeIn)), [
467
+ sphere && createVNode("circle", mergeJsxProps({
468
+ "cx": sphere.cx,
469
+ "cy": sphere.cy,
470
+ "r": sphere.r
471
+ }, css.attrs(mapChartStyles.sphere)), null),
472
+ shapes.map((shape, index) => {
473
+ const info = areaById.value.get(shape.id);
474
+ return createVNode("path", mergeJsxProps({
475
+ "key": `${shape.id}:${index}`,
476
+ "data-part": "feature",
477
+ "data-feature": index,
478
+ "d": shape.d,
479
+ "vector-effect": "non-scaling-stroke"
480
+ }, css.attrs(mapChartStyles.feature, chartSvgStyles.fill(areaColor(info) ?? colors.fill), info && onAreaClick.value && mapChartStyles.clickable)), null);
481
+ }),
482
+ hoveredShape && createVNode("path", mergeJsxProps({
483
+ "d": hoveredShape.d,
484
+ "vector-effect": "non-scaling-stroke"
485
+ }, css.attrs(mapChartStyles.featureHover)), null)
486
+ ]),
487
+ createVNode("g", { "data-part": "links" }, [linkShapes.value.map((shape) => {
488
+ const tone = resolveColor(shape.link.color ?? "neutral", 0);
489
+ return createVNode("path", mergeJsxProps({
490
+ "key": shape.index,
491
+ "data-part": "link",
492
+ "d": shape.d,
493
+ "stroke-width": svgNumber(shape.width)
494
+ }, css.attrs(mapChartStyles.link, chartSvgStyles.stroke(tone))), null);
495
+ }), flow.value && !reducedMotion.value && linkShapes.value.flatMap((shape) => {
496
+ const count = Math.max(1, Math.round(shape.length / 120));
497
+ const duration = Math.max(1.5, shape.length / 60);
498
+ const tone = resolveColor(shape.link.color ?? "accent", 0);
499
+ return Array.from({ length: count }, (_, k) => createVNode("circle", mergeJsxProps({
500
+ "key": `${shape.index}:${k}`,
501
+ "r": 2
502
+ }, css.attrs(mapChartStyles.particle, chartSvgStyles.fill(tone))), [createVNode("animateMotion", mergeJsxProps({
503
+ "dur": `${svgNumber(duration)}s`,
504
+ "begin": `${svgNumber(-(duration * k) / count)}s`,
505
+ "repeatCount": "indefinite"
506
+ }, { path: shape.d }), null)]));
507
+ })]),
508
+ createVNode("g", { "data-part": "points" }, [placedPoints.value.map((item) => createVNode("path", mergeJsxProps({
509
+ "key": item.point.id,
510
+ "data-part": "point",
511
+ "data-point": item.index,
512
+ "d": markerPath(item.point.shape, item.x, item.y, item.r)
513
+ }, css.attrs(mapChartStyles.point, chartSvgStyles.fill(resolveColor(item.point.color ?? "danger", 0)), onPointClick.value && mapChartStyles.clickable)), null))]),
514
+ createVNode("g", mergeJsxProps({ "data-part": "area-labels" }, css.attrs(chartSvgStyles.noPointer)), [labels.value.areas.map((item) => createVNode("text", mergeJsxProps({
515
+ "key": item.label,
516
+ "x": svgNumber(item.rect.x + item.rect.width / 2),
517
+ "y": svgNumber(item.rect.y + item.rect.height / 2),
518
+ "text-anchor": "middle",
519
+ "dominant-baseline": "central"
520
+ }, css.attrs(mapChartStyles.areaLabel, chartSvgStyles.halo)), [item.label]))]),
521
+ createVNode("g", mergeJsxProps({ "data-part": "point-labels" }, css.attrs(chartSvgStyles.noPointer)), [labels.value.points.map(({ placement, anchor, item }) => {
522
+ if (!placement) return null;
523
+ const { box, leader } = placement;
524
+ const toX = Math.min(Math.max(anchor.x, box.x), box.x + box.width);
525
+ const toY = Math.min(Math.max(anchor.y, box.y), box.y + box.height);
526
+ const length = Math.hypot(toX - anchor.x, toY - anchor.y) || 1;
527
+ return createVNode("g", { "key": item.point.id }, [leader && createVNode("line", mergeJsxProps({
528
+ "x1": svgNumber(anchor.x + (toX - anchor.x) / length * anchor.radius),
529
+ "y1": svgNumber(anchor.y + (toY - anchor.y) / length * anchor.radius),
530
+ "x2": svgNumber(toX),
531
+ "y2": svgNumber(toY)
532
+ }, css.attrs(mapChartStyles.leader)), null), createVNode("text", mergeJsxProps({
533
+ "x": svgNumber(box.x + box.width / 2),
534
+ "y": svgNumber(box.y + box.height / 2),
535
+ "text-anchor": "middle",
536
+ "dominant-baseline": "central"
537
+ }, css.attrs(mapChartStyles.pointLabel, chartSvgStyles.halo)), [item.point.label])]);
538
+ })])
539
+ ]);
540
+ }
541
+ const tip = () => {
542
+ const current = hovered.value;
543
+ if (!tooltip.value || !current) return null;
544
+ if (current.kind === "point") {
545
+ const point = points.value[current.index];
546
+ if (!point) return null;
473
547
  return {
474
- component: map,
475
- onRender: scheduleLabels,
476
- data: {
477
- areas: [...areas.value],
478
- points: [...points.value],
479
- links: links.value.filter((link) => pointOf(link.from) && pointOf(link.to))
480
- },
481
- triggers: {
482
- [TopoJSONMap.selectors.feature]: (shape) => shape.data ? chart.tooltip(chartTooltip(shape.data.label, [{
483
- label: lang.chartValue,
484
- color: areaColor(shape.data) ?? "transparent",
485
- value: valueText(shape.data.value)
486
- }])) : void 0,
487
- [TopoJSONMap.selectors.point]: (point) => chart.tooltip(chartTooltip(point.label, [{
488
- label: lang.chartValue,
489
- color: resolveColor(point.color ?? "danger", 0),
490
- value: valueText(point.value)
491
- }]))
492
- }
548
+ x: current.x,
549
+ y: current.y,
550
+ content: chartTooltip(point.label, [{
551
+ label: lang.chartValue,
552
+ color: resolveColor(point.color ?? "danger", 0),
553
+ value: valueText(point.value)
554
+ }])
493
555
  };
494
556
  }
495
- }));
557
+ const shape = geometry.value.shapes[current.index];
558
+ const info = shape && areaById.value.get(shape.id);
559
+ if (!info) return null;
560
+ return {
561
+ x: current.x,
562
+ y: current.y,
563
+ content: chartTooltip(info.label, [{
564
+ label: lang.chartValue,
565
+ color: areaColor(info) ?? "transparent",
566
+ value: valueText(info.value)
567
+ }])
568
+ };
569
+ };
496
570
  /** 图例:分档时每档一个色块与范围,连续时一条色带两端写最小最大 */
497
571
  function legend() {
498
- if (!extent.value || areas.value.every((area) => area.color)) return null;
572
+ if (!extent.value || areas.value.every((item) => item.color)) return null;
499
573
  const [low, high] = extent.value;
500
574
  if (bins.value) {
501
575
  const step = (high - low) / bins.value;
@@ -521,32 +595,22 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
521
595
  createVNode("span", null, [numberText(high)])
522
596
  ]);
523
597
  }
524
- let wheelHint = ref(false);
525
- let hintTimer;
526
- useEventListener(() => zoomable.value ? plot.value : null, "wheel", (event) => {
527
- if (event.ctrlKey || event.metaKey) return;
528
- event.stopPropagation();
529
- wheelHint.value = true;
530
- clearTimeout(hintTimer);
531
- hintTimer = setTimeout(() => wheelHint.value = false, 1500);
532
- }, { capture: true });
533
- effect(() => () => clearTimeout(hintTimer));
534
598
  /** 缩放按钮:放大、缩小、复位;前面是滚轮的提示 */
535
599
  const zoomButtons = () => createVNode("span", css.attrs(mapChartStyles.zoom), [wheelHint.value && createVNode("span", mergeJsxProps(css.attrs(mapChartStyles.wheelHint), { "role": "status" }), [lang.chartZoomWheelHint]), [
536
600
  [
537
601
  Plus,
538
602
  lang.chartZoomIn,
539
- () => map?.zoomIn()
603
+ () => view.zoomIn()
540
604
  ],
541
605
  [
542
606
  Minus,
543
607
  lang.chartZoomOut,
544
- () => map?.zoomOut()
608
+ () => view.zoomOut()
545
609
  ],
546
610
  [
547
611
  Maximize,
548
612
  lang.chartZoomFit,
549
- () => map?.fitView()
613
+ () => view.reset()
550
614
  ]
551
615
  ].map(([icon, label, action]) => createVNode(IconButton, {
552
616
  "key": label,
@@ -558,7 +622,6 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
558
622
  "onClick": action
559
623
  }, null))]);
560
624
  return () => createVNode(ChartFrame, mergeJsxProps(rest, {
561
- "sx": [mapChartStyles.root, sx.value],
562
625
  "unit": unit.value,
563
626
  "height": height.value,
564
627
  "legend": _cache[0] || (_cache[0] = []),
@@ -573,16 +636,16 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
573
636
  "table": {
574
637
  columns: [lang.chartCategory, unit.value ? `${lang.chartValue}(${unit.value})` : lang.chartValue],
575
638
  rows: [
576
- ...areas.value.map((area) => ({
577
- header: area.label,
578
- value: area.value
639
+ ...areas.value.map((item) => ({
640
+ header: item.label,
641
+ value: item.value
579
642
  })),
580
643
  ...points.value.map((point) => ({
581
644
  header: point.label,
582
645
  value: point.value
583
646
  })),
584
647
  ...links.value.map((link) => ({
585
- header: lang.chartFlowLabel(pointOf(link.from)?.label ?? link.from, pointOf(link.to)?.label ?? link.to),
648
+ header: lang.chartFlowLabel(pointById.value.get(link.from)?.label ?? link.from, pointById.value.get(link.to)?.label ?? link.to),
586
649
  value: link.value
587
650
  }))
588
651
  ].map((row) => ({
@@ -590,7 +653,18 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
590
653
  cells: [row.value === void 0 ? lang.emptyValue : format.number(row.value, valueFormat.value)]
591
654
  }))
592
655
  },
593
- "plotRef": _cache[3] || (_cache[3] = (element) => plot.value = element)
656
+ "plotRef": area.ref,
657
+ "plot": area.ready ? createVNode("div", mergeJsxProps(css.attrs(mapChartStyles.viewport), zoomable.value ? view.viewportProps : {}, {
658
+ "ref": _cache[3] || (_cache[3] = (element) => viewport.value = element),
659
+ "onPointermove": _cache[4] || (_cache[4] = (event) => {
660
+ if (zoomable.value) view.viewportProps.onPointermove(event);
661
+ onPointermove(event);
662
+ }),
663
+ "onPointerleave": _cache[5] || (_cache[5] = () => hovered.value = null)
664
+ }), [renderPlot(), createVNode(ChartTooltipBox, {
665
+ "tip": tip(),
666
+ "width": area.width
667
+ }, null)]) : null
594
668
  }), null);
595
669
  }, {
596
670
  name: "MapChart",
@@ -617,11 +691,10 @@ var MapChart = /*#__PURE__*/ _component((_props, _ctx) => {
617
691
  "empty",
618
692
  "tooltip",
619
693
  "onAreaClick",
620
- "onPointClick",
621
- "sx"
694
+ "onPointClick"
622
695
  ]
623
696
  });
624
697
  //#endregion
625
- export { MapChart, mapChartStyles, simplifyTopology, topologyBounds };
698
+ export { MapChart, mapChartStyles };
626
699
 
627
700
  //# sourceMappingURL=MapChart.js.map