@easy-editor/materials-dashboard-geo-map 0.0.3 → 0.0.5

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.
package/src/component.tsx CHANGED
@@ -1,325 +1,323 @@
1
- /**
2
- * Geo Map Component
3
- * 地理地图组件 - 支持数据源绑定和事件交互
4
- */
5
-
6
- import { useEffect, useRef, useMemo, type CSSProperties } from 'react'
7
- import * as echarts from 'echarts/core'
8
- import { MapChart, EffectScatterChart } from 'echarts/charts'
9
- import { GeoComponent, TooltipComponent, VisualMapComponent } from 'echarts/components'
10
- import { CanvasRenderer } from 'echarts/renderers'
11
- import { type MaterialComponet, useDataSource } from '@easy-editor/materials-shared'
12
- import {
13
- DEFAULT_COLORS,
14
- DEFAULT_REGION_DATA,
15
- DEFAULT_SCATTER_DATA,
16
- type MapDataPoint,
17
- type ScatterPoint,
18
- type MapType,
19
- } from './constants'
20
- import chinaGeoJson from './assets/geo/china.json'
21
- import worldGeoJson from './assets/geo/world.json'
22
- import styles from './component.module.css'
23
-
24
- // 按需注册 ECharts 组件
25
- echarts.use([MapChart, EffectScatterChart, GeoComponent, TooltipComponent, VisualMapComponent, CanvasRenderer])
26
-
27
- // 内置地图数据
28
- const BUILTIN_MAP_JSON: Record<MapType, object> = {
29
- china: chinaGeoJson as object,
30
- world: worldGeoJson as object,
31
- }
32
-
33
- // 已注册的地图
34
- const registeredMaps = new Set<string>()
35
-
36
- interface GeoMapProps extends MaterialComponet {
37
- /** 地图类型 */
38
- mapType?: MapType
39
- /** 地图 GeoJSON 数据 */
40
- mapJson?: object
41
- /** 区域数据(兼容旧版) */
42
- regionData?: MapDataPoint[]
43
- /** 散点数据(兼容旧版) */
44
- scatterData?: ScatterPoint[]
45
- /** 颜色列表 */
46
- colors?: string[]
47
- /** 显示图例 */
48
- showVisualMap?: boolean
49
- /** 显示提示 */
50
- showTooltip?: boolean
51
- /** 显示散点 */
52
- showScatter?: boolean
53
- /** 散点大小 */
54
- scatterSymbolSize?: number
55
- /** 发光效果 */
56
- glowEffect?: boolean
57
- /** 允许缩放 */
58
- roam?: boolean
59
- /** 点击事件 */
60
- onClick?: (e: React.MouseEvent) => void
61
- /** 双击事件 */
62
- onDoubleClick?: (e: React.MouseEvent) => void
63
- /** 鼠标进入 */
64
- onMouseEnter?: (e: React.MouseEvent) => void
65
- /** 鼠标离开 */
66
- onMouseLeave?: (e: React.MouseEvent) => void
67
- }
68
-
69
- export const GeoMap = (props: GeoMapProps) => {
70
- const {
71
- ref,
72
- $data,
73
- __dataSource,
74
- mapType = 'china',
75
- mapJson,
76
- regionData: staticRegionData,
77
- scatterData: staticScatterData,
78
- colors,
79
- showVisualMap = true,
80
- showTooltip = true,
81
- showScatter = true,
82
- scatterSymbolSize = 12,
83
- glowEffect = true,
84
- roam = true,
85
- rotation = 0,
86
- opacity = 100,
87
- background = 'transparent',
88
- style: externalStyle,
89
- onClick,
90
- onDoubleClick,
91
- onMouseEnter,
92
- onMouseLeave,
93
- } = props
94
-
95
- // 解析数据源
96
- const dataSource = useDataSource($data, __dataSource)
97
- const regionData = useMemo<MapDataPoint[]>(() => {
98
- if (dataSource.length > 0) {
99
- return dataSource as MapDataPoint[]
100
- }
101
- return staticRegionData ?? DEFAULT_REGION_DATA
102
- }, [dataSource, staticRegionData])
103
- const scatterData = useMemo<ScatterPoint[]>(() => {
104
- return staticScatterData ?? DEFAULT_SCATTER_DATA
105
- }, [staticScatterData])
106
- const chartColors = useMemo<string[]>(() => {
107
- return colors ?? DEFAULT_COLORS
108
- }, [colors])
109
-
110
- const chartRef = useRef<HTMLDivElement>(null)
111
- const chartInstance = useRef<echarts.ECharts | null>(null)
112
-
113
- // 计算数据范围
114
- const values = regionData.map(d => d.value)
115
- const minValue = values.length > 0 ? Math.min(...values) : 0
116
- const maxValue = values.length > 0 ? Math.max(...values) : 100
117
-
118
- useEffect(() => {
119
- if (!chartRef.current) {
120
- return
121
- }
122
-
123
- // 获取地图数据:优先使用用户提供的 mapJson,否则使用内置地图
124
- const geoJson = mapJson || BUILTIN_MAP_JSON[mapType]
125
-
126
- // 注册地图(避免重复注册)
127
- const mapKey = mapJson ? `custom_${mapType}` : mapType
128
- if (!registeredMaps.has(mapKey) && geoJson) {
129
- echarts.registerMap(mapType, geoJson as Parameters<typeof echarts.registerMap>[1])
130
- registeredMaps.add(mapKey)
131
- }
132
-
133
- chartInstance.current = echarts.init(chartRef.current)
134
-
135
- const option = {
136
- backgroundColor: 'transparent',
137
- tooltip: showTooltip
138
- ? {
139
- trigger: 'item',
140
- backgroundColor: 'rgba(0, 15, 35, 0.95)',
141
- borderColor: 'rgba(0, 242, 254, 0.6)',
142
- borderWidth: 1,
143
- padding: [10, 15],
144
- textStyle: {
145
- color: '#fff',
146
- fontSize: 13,
147
- },
148
- formatter: (params: { name: string; value?: number; seriesType: string }) => {
149
- if (params.seriesType === 'effectScatter') {
150
- return `<div style="font-weight:500">${params.name}</div>`
151
- }
152
- return `<div style="font-weight:500">${params.name}</div><div style="color:#00f2fe;margin-top:4px">${params.value?.toLocaleString() ?? '-'}</div>`
153
- },
154
- }
155
- : { show: false },
156
- visualMap: showVisualMap
157
- ? {
158
- min: minValue,
159
- max: maxValue,
160
- left: 20,
161
- bottom: 20,
162
- itemWidth: 12,
163
- itemHeight: 100,
164
- text: ['高', '低'],
165
- textStyle: {
166
- color: 'rgba(255, 255, 255, 0.7)',
167
- fontSize: 11,
168
- },
169
- inRange: {
170
- color: ['#0a2e4e', '#0d4a6e', '#1a6a8e', '#2a8aae', '#4abadd', '#00f2fe'],
171
- },
172
- calculable: true,
173
- }
174
- : undefined,
175
- geo: {
176
- map: mapType,
177
- roam,
178
- zoom: 1.2,
179
- label: {
180
- show: false,
181
- },
182
- emphasis: {
183
- label: {
184
- show: true,
185
- color: '#fff',
186
- fontSize: 12,
187
- fontWeight: 500,
188
- },
189
- itemStyle: {
190
- areaColor: {
191
- type: 'linear',
192
- x: 0,
193
- y: 0,
194
- x2: 0,
195
- y2: 1,
196
- colorStops: [
197
- { offset: 0, color: '#00f2fe' },
198
- { offset: 1, color: '#4facfe' },
199
- ],
200
- },
201
- shadowColor: glowEffect ? 'rgba(0, 242, 254, 0.8)' : 'transparent',
202
- shadowBlur: glowEffect ? 25 : 0,
203
- borderColor: '#00f2fe',
204
- borderWidth: 2,
205
- },
206
- },
207
- itemStyle: {
208
- areaColor: {
209
- type: 'linear',
210
- x: 0,
211
- y: 0,
212
- x2: 0,
213
- y2: 1,
214
- colorStops: [
215
- { offset: 0, color: '#0d2b4a' },
216
- { offset: 1, color: '#051428' },
217
- ],
218
- },
219
- borderColor: 'rgba(0, 242, 254, 0.3)',
220
- borderWidth: 1,
221
- shadowColor: glowEffect ? 'rgba(0, 242, 254, 0.15)' : 'transparent',
222
- shadowBlur: glowEffect ? 8 : 0,
223
- shadowOffsetY: glowEffect ? 2 : 0,
224
- },
225
- },
226
- series: [
227
- {
228
- name: '区域数据',
229
- type: 'map',
230
- map: mapType,
231
- geoIndex: 0,
232
- data: regionData,
233
- },
234
- ...(showScatter
235
- ? [
236
- {
237
- name: '散点',
238
- type: 'effectScatter' as const,
239
- coordinateSystem: 'geo' as const,
240
- data: scatterData.map(item => ({
241
- name: item.name,
242
- value: item.value,
243
- })),
244
- symbolSize: (val: number[]) => {
245
- const size = (val[2] / maxValue) * scatterSymbolSize + scatterSymbolSize / 2
246
- return Math.max(size, 8)
247
- },
248
- showEffectOn: 'render' as const,
249
- rippleEffect: {
250
- brushType: 'stroke' as const,
251
- scale: 4,
252
- period: 4,
253
- },
254
- itemStyle: {
255
- color: {
256
- type: 'radial',
257
- x: 0.5,
258
- y: 0.5,
259
- r: 0.5,
260
- colorStops: [
261
- { offset: 0, color: '#fff' },
262
- { offset: 0.3, color: chartColors[0] },
263
- { offset: 1, color: chartColors[0] },
264
- ],
265
- },
266
- shadowColor: glowEffect ? chartColors[0] : 'transparent',
267
- shadowBlur: glowEffect ? 15 : 0,
268
- },
269
- zlevel: 1,
270
- },
271
- ]
272
- : []),
273
- ],
274
- }
275
-
276
- chartInstance.current.setOption(option)
277
-
278
- const resizeObserver = new ResizeObserver(() => {
279
- chartInstance.current?.resize()
280
- })
281
- resizeObserver.observe(chartRef.current)
282
-
283
- return () => {
284
- resizeObserver.disconnect()
285
- chartInstance.current?.dispose()
286
- }
287
- }, [
288
- mapType,
289
- mapJson,
290
- regionData,
291
- scatterData,
292
- chartColors,
293
- showVisualMap,
294
- showTooltip,
295
- showScatter,
296
- scatterSymbolSize,
297
- glowEffect,
298
- roam,
299
- minValue,
300
- maxValue,
301
- ])
302
-
303
- const containerStyle: CSSProperties = {
304
- width: '100%',
305
- height: '100%',
306
- transform: rotation !== 0 ? `rotate(${rotation}deg)` : undefined,
307
- opacity: opacity / 100,
308
- backgroundColor: background,
309
- ...externalStyle,
310
- }
311
-
312
- return (
313
- <div
314
- className={styles.container}
315
- onClick={onClick}
316
- onDoubleClick={onDoubleClick}
317
- onMouseEnter={onMouseEnter}
318
- onMouseLeave={onMouseLeave}
319
- ref={ref}
320
- style={containerStyle}
321
- >
322
- <div className={styles.chart} ref={chartRef} />
323
- </div>
324
- )
325
- }
1
+ /**
2
+ * Geo Map Component
3
+ * 地理地图组件 - 支持数据源绑定和事件交互
4
+ */
5
+
6
+ import { useEffect, useRef, useMemo, type CSSProperties } from 'react'
7
+ import * as echarts from 'echarts/core'
8
+ import { MapChart, EffectScatterChart } from 'echarts/charts'
9
+ import { GeoComponent, TooltipComponent, VisualMapComponent } from 'echarts/components'
10
+ import { CanvasRenderer } from 'echarts/renderers'
11
+ import { type MaterialComponet, useDataSource } from '@easy-editor/materials-shared'
12
+ import {
13
+ DEFAULT_COLORS,
14
+ DEFAULT_REGION_DATA,
15
+ DEFAULT_SCATTER_DATA,
16
+ type MapDataPoint,
17
+ type ScatterPoint,
18
+ type MapType,
19
+ } from './constants'
20
+ import chinaGeoJson from './assets/geo/china.json'
21
+ import worldGeoJson from './assets/geo/world.json'
22
+ import styles from './component.module.css'
23
+
24
+ // 按需注册 ECharts 组件
25
+ echarts.use([MapChart, EffectScatterChart, GeoComponent, TooltipComponent, VisualMapComponent, CanvasRenderer])
26
+
27
+ // 内置地图数据
28
+ const BUILTIN_MAP_JSON: Record<MapType, object> = {
29
+ china: chinaGeoJson as object,
30
+ world: worldGeoJson as object,
31
+ }
32
+
33
+ // 已注册的地图
34
+ const registeredMaps = new Set<string>()
35
+
36
+ interface GeoMapProps extends MaterialComponet {
37
+ /** 地图类型 */
38
+ mapType?: MapType
39
+ /** 地图 GeoJSON 数据 */
40
+ mapJson?: object
41
+ /** 区域数据(兼容旧版) */
42
+ regionData?: MapDataPoint[]
43
+ /** 散点数据(兼容旧版) */
44
+ scatterData?: ScatterPoint[]
45
+ /** 颜色列表 */
46
+ colors?: string[]
47
+ /** 显示图例 */
48
+ showVisualMap?: boolean
49
+ /** 显示提示 */
50
+ showTooltip?: boolean
51
+ /** 显示散点 */
52
+ showScatter?: boolean
53
+ /** 散点大小 */
54
+ scatterSymbolSize?: number
55
+ /** 发光效果 */
56
+ glowEffect?: boolean
57
+ /** 允许缩放 */
58
+ roam?: boolean
59
+ /** 点击事件 */
60
+ onClick?: (e: React.MouseEvent) => void
61
+ /** 双击事件 */
62
+ onDoubleClick?: (e: React.MouseEvent) => void
63
+ /** 鼠标进入 */
64
+ onMouseEnter?: (e: React.MouseEvent) => void
65
+ /** 鼠标离开 */
66
+ onMouseLeave?: (e: React.MouseEvent) => void
67
+ }
68
+
69
+ export const GeoMap = (props: GeoMapProps) => {
70
+ const {
71
+ ref,
72
+ $data,
73
+ __dataSource,
74
+ mapType = 'china',
75
+ mapJson,
76
+ regionData: staticRegionData,
77
+ scatterData: staticScatterData,
78
+ colors,
79
+ showVisualMap = true,
80
+ showTooltip = true,
81
+ showScatter = true,
82
+ scatterSymbolSize = 12,
83
+ glowEffect = true,
84
+ roam = true,
85
+ rotation = 0,
86
+ opacity = 100,
87
+ background = 'transparent',
88
+ style: externalStyle,
89
+ onClick,
90
+ onDoubleClick,
91
+ onMouseEnter,
92
+ onMouseLeave,
93
+ } = props
94
+
95
+ // 解析数据源
96
+ const dataSource = useDataSource($data, __dataSource)
97
+ const regionData = useMemo<MapDataPoint[]>(() => {
98
+ if (dataSource.length > 0) {
99
+ return dataSource as MapDataPoint[]
100
+ }
101
+ return staticRegionData ?? DEFAULT_REGION_DATA
102
+ }, [dataSource, staticRegionData])
103
+ const scatterData = useMemo<ScatterPoint[]>(() => staticScatterData ?? DEFAULT_SCATTER_DATA, [staticScatterData])
104
+ const chartColors = useMemo<string[]>(() => colors ?? DEFAULT_COLORS, [colors])
105
+
106
+ const chartRef = useRef<HTMLDivElement>(null)
107
+ const chartInstance = useRef<echarts.ECharts | null>(null)
108
+
109
+ // 计算数据范围
110
+ const values = regionData.map(d => d.value)
111
+ const minValue = values.length > 0 ? Math.min(...values) : 0
112
+ const maxValue = values.length > 0 ? Math.max(...values) : 100
113
+
114
+ useEffect(() => {
115
+ if (!chartRef.current) {
116
+ return
117
+ }
118
+
119
+ // 获取地图数据:优先使用用户提供的 mapJson,否则使用内置地图
120
+ const geoJson = mapJson || BUILTIN_MAP_JSON[mapType]
121
+
122
+ // 注册地图(避免重复注册)
123
+ const mapKey = mapJson ? `custom_${mapType}` : mapType
124
+ if (!registeredMaps.has(mapKey) && geoJson) {
125
+ echarts.registerMap(mapType, geoJson as Parameters<typeof echarts.registerMap>[1])
126
+ registeredMaps.add(mapKey)
127
+ }
128
+
129
+ chartInstance.current = echarts.init(chartRef.current)
130
+
131
+ const option = {
132
+ backgroundColor: 'transparent',
133
+ tooltip: showTooltip
134
+ ? {
135
+ trigger: 'item',
136
+ backgroundColor: 'rgba(0, 15, 35, 0.95)',
137
+ borderColor: 'rgba(0, 242, 254, 0.6)',
138
+ borderWidth: 1,
139
+ padding: [10, 15],
140
+ textStyle: {
141
+ color: '#fff',
142
+ fontSize: 13,
143
+ },
144
+ formatter: (params: { name: string; value?: number; seriesType: string }) => {
145
+ if (params.seriesType === 'effectScatter') {
146
+ return `<div style="font-weight:500">${params.name}</div>`
147
+ }
148
+ return `<div style="font-weight:500">${params.name}</div><div style="color:#00f2fe;margin-top:4px">${params.value?.toLocaleString() ?? '-'}</div>`
149
+ },
150
+ }
151
+ : { show: false },
152
+ visualMap: showVisualMap
153
+ ? {
154
+ min: minValue,
155
+ max: maxValue,
156
+ left: 20,
157
+ bottom: 20,
158
+ itemWidth: 12,
159
+ itemHeight: 100,
160
+ text: ['高', '低'],
161
+ textStyle: {
162
+ color: 'rgba(255, 255, 255, 0.7)',
163
+ fontSize: 11,
164
+ },
165
+ inRange: {
166
+ color: ['#0a2e4e', '#0d4a6e', '#1a6a8e', '#2a8aae', '#4abadd', '#00f2fe'],
167
+ },
168
+ calculable: true,
169
+ }
170
+ : undefined,
171
+ geo: {
172
+ map: mapType,
173
+ roam,
174
+ zoom: 1.2,
175
+ label: {
176
+ show: false,
177
+ },
178
+ emphasis: {
179
+ label: {
180
+ show: true,
181
+ color: '#fff',
182
+ fontSize: 12,
183
+ fontWeight: 500,
184
+ },
185
+ itemStyle: {
186
+ areaColor: {
187
+ type: 'linear',
188
+ x: 0,
189
+ y: 0,
190
+ x2: 0,
191
+ y2: 1,
192
+ colorStops: [
193
+ { offset: 0, color: '#00f2fe' },
194
+ { offset: 1, color: '#4facfe' },
195
+ ],
196
+ },
197
+ shadowColor: glowEffect ? 'rgba(0, 242, 254, 0.8)' : 'transparent',
198
+ shadowBlur: glowEffect ? 25 : 0,
199
+ borderColor: '#00f2fe',
200
+ borderWidth: 2,
201
+ },
202
+ },
203
+ itemStyle: {
204
+ areaColor: {
205
+ type: 'linear',
206
+ x: 0,
207
+ y: 0,
208
+ x2: 0,
209
+ y2: 1,
210
+ colorStops: [
211
+ { offset: 0, color: '#0d2b4a' },
212
+ { offset: 1, color: '#051428' },
213
+ ],
214
+ },
215
+ borderColor: 'rgba(0, 242, 254, 0.3)',
216
+ borderWidth: 1,
217
+ shadowColor: glowEffect ? 'rgba(0, 242, 254, 0.15)' : 'transparent',
218
+ shadowBlur: glowEffect ? 8 : 0,
219
+ shadowOffsetY: glowEffect ? 2 : 0,
220
+ },
221
+ },
222
+ series: [
223
+ {
224
+ name: '区域数据',
225
+ type: 'map',
226
+ map: mapType,
227
+ geoIndex: 0,
228
+ data: regionData,
229
+ },
230
+ ...(showScatter
231
+ ? [
232
+ {
233
+ name: '散点',
234
+ type: 'effectScatter' as const,
235
+ coordinateSystem: 'geo' as const,
236
+ data: scatterData.map(item => ({
237
+ name: item.name,
238
+ value: item.value,
239
+ })),
240
+ symbolSize: (val: number[]) => {
241
+ const size = (val[2] / maxValue) * scatterSymbolSize + scatterSymbolSize / 2
242
+ return Math.max(size, 8)
243
+ },
244
+ showEffectOn: 'render' as const,
245
+ rippleEffect: {
246
+ brushType: 'stroke' as const,
247
+ scale: 4,
248
+ period: 4,
249
+ },
250
+ itemStyle: {
251
+ color: {
252
+ type: 'radial',
253
+ x: 0.5,
254
+ y: 0.5,
255
+ r: 0.5,
256
+ colorStops: [
257
+ { offset: 0, color: '#fff' },
258
+ { offset: 0.3, color: chartColors[0] },
259
+ { offset: 1, color: chartColors[0] },
260
+ ],
261
+ },
262
+ shadowColor: glowEffect ? chartColors[0] : 'transparent',
263
+ shadowBlur: glowEffect ? 15 : 0,
264
+ },
265
+ zlevel: 1,
266
+ },
267
+ ]
268
+ : []),
269
+ ],
270
+ }
271
+
272
+ chartInstance.current.setOption(option)
273
+
274
+ const resizeObserver = new ResizeObserver(() => {
275
+ chartInstance.current?.resize()
276
+ })
277
+ resizeObserver.observe(chartRef.current)
278
+
279
+ return () => {
280
+ resizeObserver.disconnect()
281
+ chartInstance.current?.dispose()
282
+ }
283
+ }, [
284
+ mapType,
285
+ mapJson,
286
+ regionData,
287
+ scatterData,
288
+ chartColors,
289
+ showVisualMap,
290
+ showTooltip,
291
+ showScatter,
292
+ scatterSymbolSize,
293
+ glowEffect,
294
+ roam,
295
+ minValue,
296
+ maxValue,
297
+ ])
298
+
299
+ const containerStyle: CSSProperties = {
300
+ width: '100%',
301
+ height: '100%',
302
+ transform: rotation !== 0 ? `rotate(${rotation}deg)` : undefined,
303
+ opacity: opacity / 100,
304
+ backgroundColor: background,
305
+ ...externalStyle,
306
+ }
307
+
308
+ return (
309
+ <div
310
+ className={styles.container}
311
+ onClick={onClick}
312
+ onDoubleClick={onDoubleClick}
313
+ onMouseEnter={onMouseEnter}
314
+ onMouseLeave={onMouseLeave}
315
+ ref={ref}
316
+ style={containerStyle}
317
+ >
318
+ <div className={styles.chart} ref={chartRef} />
319
+ </div>
320
+ )
321
+ }
322
+
323
+ export default GeoMap