gis-view-webgl2 0.1.0 → 0.1.2

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 (87) hide show
  1. package/README.md +40 -8
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +1036 -249
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +194 -0
  7. package/docs/GisView.md +9 -8
  8. package/docs/GisView/345/256/236/347/216/260/345/216/237/347/220/206.md +2 -2
  9. package/docs/ai/06-/344/274/232/350/257/235/347/264/242/345/274/225/344/270/216/345/244/215/346/237/245/346/226/271/346/263/225.md +172 -0
  10. package/docs/api/index.html +545 -59
  11. package/docs/demos/BackendSyncDemo.vue +10 -8
  12. package/docs/demos/BasicDemo.vue +101 -101
  13. package/docs/demos/BatchAddDemo.vue +5 -5
  14. package/docs/demos/ChannelDemo.vue +617 -10
  15. package/docs/demos/ConnectDemo.vue +392 -0
  16. package/docs/demos/CopyMergeDemo.vue +980 -929
  17. package/docs/demos/DecorationDraftDemo.vue +1300 -0
  18. package/docs/demos/DemoShell.vue +569 -240
  19. package/docs/demos/DragDropDemo.vue +11 -11
  20. package/docs/demos/EffectsDemo.vue +6 -6
  21. package/docs/demos/EventDemo.vue +327 -327
  22. package/docs/demos/ExtensionDemo.vue +458 -0
  23. package/docs/demos/GisViewDemo.vue +61 -48
  24. package/docs/demos/GraphicDemo.vue +338 -0
  25. package/docs/demos/LabelDemo.vue +553 -497
  26. package/docs/demos/MinimapDemo.vue +358 -0
  27. package/docs/demos/PerfDemo.vue +29 -0
  28. package/docs/demos/SearchFilterDemo.vue +412 -0
  29. package/docs/demos/StyleDemo.vue +121 -121
  30. package/docs/demos/api-index.js +98 -77
  31. package/docs/demos/demos.js +159 -135
  32. package/docs/demos/shared/demoData.js +107 -107
  33. package/docs/demos/shared/demoStyle.css +249 -249
  34. package/docs/demos/shared/fakeBackend.js +93 -6
  35. package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +28 -8
  36. package/docs/format/04-/351/200/232/351/201/223/344/270/216/346/240/207/347/255/276.md +5 -4
  37. package/docs/format/06-/345/257/274/345/205/245/345/257/274/345/207/272/344/270/216/346/240/207/345/207/206/346/226/207/346/241/243.md +8 -6
  38. package/docs/format/07-/344/272/213/344/273/266/350/275/275/350/215/267/344/270/216/347/244/272/344/276/213/346/225/260/346/215/256.md +6 -6
  39. package/docs/guide/sample-document.html +4 -4
  40. package/docs/index.html +180 -9
  41. package/docs/internals/11-/350/246/206/347/233/226/345/261/202/346/240/207/347/255/276/344/270/216/346/217/220/347/244/272.md +1 -1
  42. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +3 -2
  43. package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +16 -0
  44. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +11 -0
  45. package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
  46. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +18 -4
  47. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +110 -25
  48. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +13 -8
  49. package/docs/manual/06-/346/225/260/346/215/256/344/270/216/345/234/272/346/231/257/346/233/264/346/226/260.md +111 -10
  50. package/docs/manual/07-/346/214/207/351/222/210/344/270/216/350/246/201/347/264/240/347/274/226/350/276/221.md +47 -9
  51. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +17 -15
  52. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +61 -11
  53. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +74 -7
  54. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +3 -2
  55. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +260 -3
  56. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +6 -5
  57. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +27 -14
  58. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +142 -49
  59. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +31 -29
  60. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +55 -39
  61. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +189 -64
  62. package/docs/manual/19-/350/257/273/346/225/260/344/270/216/346/213/226/346/213/275/351/242/204/350/247/210.md +4 -4
  63. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +42 -2
  64. package/docs/manual/21-/350/257/264/346/230/216/344/270/216/345/270/270/350/247/201/351/227/256/351/242/230.md +22 -6
  65. package/docs/playground/index.html +5 -5
  66. package/docs/steps/README.md +14 -10
  67. package/docs/steps/check-doc-truth.mjs +1 -1
  68. package/docs/steps/selftest-actions.html +10 -10
  69. package/docs/steps/selftest-data.html +96 -14
  70. package/docs/steps/selftest-decoration.html +577 -0
  71. package/docs/steps/selftest-demo.html +6 -5
  72. package/docs/steps/selftest-demos.html +2201 -1612
  73. package/docs/steps/selftest-docs.html +89 -16
  74. package/docs/steps/selftest-effects.html +5 -5
  75. package/docs/steps/selftest-features.html +757 -86
  76. package/docs/steps/selftest-full.html +583 -583
  77. package/docs/steps/selftest-labels.html +1034 -652
  78. package/docs/steps/selftest-perf.html +1899 -1844
  79. package/docs/steps/selftest-scale.html +117 -7
  80. package/docs/steps/selftest-select.html +24 -17
  81. package/docs/steps/step-10-full.js +1 -1
  82. package/docs//346/216/245/345/217/243/345/215/217/350/256/256.md +167 -17
  83. package/docs//346/225/260/346/215/256/346/240/274/345/274/217.md +1 -1
  84. package/package.json +43 -43
  85. package/docs/demos/HistoryDemo.vue +0 -230
  86. package/docs/demos/InterfaceDemo.vue +0 -309
  87. package/docs/demos/SampleDemo.vue +0 -283
@@ -0,0 +1,358 @@
1
+ <script setup>
2
+ /**
3
+ * 鹰眼图配置:把尺寸 / 位置 / 范围 / 规则 / 交互 / 高清倍率与自定义绘制层放在一页里。
4
+ *
5
+ * 这一页只改 `minimap`,主图数据保持不动 —— 每次控件变化都走
6
+ * `view.updateOptions({ minimap })`,可以看到小图运行时重挂、重铺与视口框联动。
7
+ * 两个自定义层分别画网格和指北针,演示 `layers` 的拆分方式与叠放顺序。
8
+ */
9
+ import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
10
+ import { createGisView } from '../../src/index.js'
11
+ import { buildDemoData } from './shared/demoData.js'
12
+ import './shared/demoStyle.css'
13
+
14
+ const emit = defineEmits(['notice'])
15
+ const container = ref(null)
16
+ const enabled = ref(true)
17
+ const size = ref('medium')
18
+ const position = ref('bottom-right')
19
+ const extent = ref('auto')
20
+ const ruleMode = ref('voltage')
21
+ const levelMode = ref('all')
22
+ const linkedSizes = ref(true)
23
+ const channelCircuits = ref(true)
24
+ const zoomable = ref(true)
25
+ const dragAction = ref('pan')
26
+ const maxPixelRatio = ref('2')
27
+ const gridPlacement = ref('below-data')
28
+ const customLayers = ref(true)
29
+ const readout = ref('正在创建视图…')
30
+ const zoomReadout = ref('—')
31
+ const layerReadout = ref('—')
32
+ const eventText = ref('—')
33
+ const interactionReadout = ref('还没有手势')
34
+
35
+ const SIZES = {
36
+ small: [148, 104],
37
+ medium: [192, 136],
38
+ large: [248, 176]
39
+ }
40
+
41
+ const dataset = buildDemoData({ count: 64, channels: 3 })
42
+ let view = null
43
+ let layerPaints = { grid: 0, compass: 0, gridStage: '—' }
44
+
45
+ const drawGrid = ({ context, width, height, stage }) => {
46
+ layerPaints.grid += 1
47
+ layerPaints.gridStage = stage
48
+ context.save()
49
+ context.strokeStyle = 'rgba(54, 120, 191, 0.18)'
50
+ context.lineWidth = 1
51
+ context.setLineDash([3, 5])
52
+ for (let x = 24; x < width; x += 24) {
53
+ context.beginPath()
54
+ context.moveTo(x + 0.5, 0)
55
+ context.lineTo(x + 0.5, height)
56
+ context.stroke()
57
+ }
58
+ for (let y = 24; y < height; y += 24) {
59
+ context.beginPath()
60
+ context.moveTo(0, y + 0.5)
61
+ context.lineTo(width, y + 0.5)
62
+ context.stroke()
63
+ }
64
+ context.restore()
65
+ }
66
+
67
+ const drawCompass = ({ context, width }) => {
68
+ layerPaints.compass += 1
69
+ const x = width - 18
70
+ const y = 18
71
+ context.save()
72
+ context.fillStyle = 'rgba(255, 255, 255, 0.82)'
73
+ context.strokeStyle = 'rgba(54, 120, 191, 0.55)'
74
+ context.lineWidth = 1
75
+ context.beginPath()
76
+ context.arc(x, y, 11, 0, Math.PI * 2)
77
+ context.fill()
78
+ context.stroke()
79
+ context.fillStyle = '#3678bf'
80
+ context.beginPath()
81
+ context.moveTo(x, y - 8)
82
+ context.lineTo(x - 4, y + 4)
83
+ context.lineTo(x, y + 1)
84
+ context.lineTo(x + 4, y + 4)
85
+ context.closePath()
86
+ context.fill()
87
+ context.font = '600 7px sans-serif'
88
+ context.textAlign = 'center'
89
+ context.fillStyle = '#526b8d'
90
+ context.fillText('N', x, y + 17)
91
+ context.restore()
92
+ }
93
+
94
+ const minimapLayers = () => (customLayers.value
95
+ ? [
96
+ { id: 'grid', order: 10, placement: gridPlacement.value, draw: drawGrid },
97
+ { id: 'compass', order: 20, draw: drawCompass }
98
+ ]
99
+ : [])
100
+
101
+ let interactionCount = 0
102
+ const handleMinimapInteraction = (info) => {
103
+ interactionCount += 1
104
+ interactionReadout.value = `${info.type} #${interactionCount} · ${info.world[0].toFixed(1)}, ${info.world[1].toFixed(1)} · 小图 ${info.zoom.toFixed(2)} · ${info.camera ? '相机已同步' : '相机未就绪'}`
105
+ }
106
+
107
+ const voltagePointRules = () => [
108
+ { when: { vn_kv: { gte: 525 } }, style: { color: '#ff4fa3', size: 2.1, shape: 'square', zIndex: 2 } },
109
+ { when: { vn_kv: { lt: 525 } }, style: { color: '#3fa7ff', size: 1.2, zIndex: 1 } }
110
+ ]
111
+
112
+ const voltageLineRules = () => [
113
+ { when: { vn_kv: { gte: 525 } }, style: { color: '#d47a52', width: 1.3, opacity: 0.86, zIndex: 2 } },
114
+ { when: { vn_kv: { lt: 525 } }, style: { color: '#9fb4ca', width: 0.8, opacity: 0.54, zIndex: 1 } }
115
+ ]
116
+
117
+ const minimapOptions = () => {
118
+ const [width, height] = SIZES[size.value] ?? SIZES.medium
119
+ const ownRules = ruleMode.value === 'voltage'
120
+ return {
121
+ enabled: enabled.value,
122
+ width,
123
+ height,
124
+ position: position.value,
125
+ margin: 12,
126
+ padding: 0.05,
127
+ extent: extent.value,
128
+ background: 'inherit',
129
+ backgroundStyle: 'rgba(255, 255, 255, 0.94)',
130
+ borderColor: '#c7d8eb',
131
+ borderWidth: 1,
132
+ radius: 7,
133
+ className: 'demo-minimap-surface',
134
+ style: { boxShadow: '0 6px 20px rgba(31, 62, 105, 0.18)' },
135
+ pointStyle: { color: '#3f8ee8', size: 1.35 },
136
+ lineStyle: { color: '#8fb2d8', width: 0.9, opacity: 0.55 },
137
+ pointRules: ownRules ? voltagePointRules() : undefined,
138
+ lineRules: ownRules ? voltageLineRules() : undefined,
139
+ inheritRules: ruleMode.value === 'inherit',
140
+ levels: levelMode.value === 'top' ? [2] : undefined,
141
+ cascade: 'both',
142
+ layered: true,
143
+ pointSizeScale: 1,
144
+ lineWidthScale: 1,
145
+ pointMapZoomScale: linkedSizes.value ? 0.5 : 0,
146
+ lineMapZoomScale: linkedSizes.value ? 0.5 : 0,
147
+ boxStyle: { fill: '#3678bf', fillOpacity: 0.16, stroke: '#3678bf', strokeWidth: 1, strokeStyle: 'dashed', radius: 3 },
148
+ selectStyle: { fill: '#f0a43c', stroke: '#f0a43c' },
149
+ interactive: true,
150
+ onInteraction: handleMinimapInteraction,
151
+ longPressDelay: 180,
152
+ zoomable: zoomable.value,
153
+ minZoom: 0.7,
154
+ maxZoom: 6,
155
+ zoomStep: 1.35,
156
+ dragAction: dragAction.value,
157
+ channelCircuits: channelCircuits.value,
158
+ maxPixelRatio: Number(maxPixelRatio.value),
159
+ layers: minimapLayers()
160
+ }
161
+ }
162
+
163
+ const refreshReadout = () => {
164
+ const canvas = container.value?.querySelector('.gis-view__minimap-canvas')
165
+ const root = container.value?.querySelector('.gis-view__minimap')
166
+ const options = minimapOptions()
167
+ const ratio = canvas && options.width ? canvas.width / options.width : 0
168
+ const sizeText = root ? `${Math.round(root.getBoundingClientRect().width)}×${Math.round(root.getBoundingClientRect().height)}` : '未挂载'
169
+ readout.value = `${sizeText} · 备份 ${canvas?.width ?? 0}×${canvas?.height ?? 0} · 像素比 ${ratio ? ratio.toFixed(2) : '—'} · 范围 ${extent.value} · 自定义层 ${options.layers.length}`
170
+ if (view) {
171
+ zoomReadout.value = `小图倍数 ${view.getMinimapZoom().toFixed(2)} · ${zoomable.value ? '滚轮缩放小图' : '滚轮缩放主图'}`
172
+ }
173
+ }
174
+
175
+ const applyMinimap = () => {
176
+ if (!view) {
177
+ return
178
+ }
179
+ view.updateOptions({ minimap: minimapOptions() })
180
+ eventText.value = `updateOptions:${enabled.value ? '已更新鹰眼图配置' : '已卸载鹰眼图'}`
181
+ requestAnimationFrame(refreshReadout)
182
+ }
183
+
184
+ const resetZoom = () => {
185
+ view?.setMinimapZoom(1)
186
+ refreshZoom()
187
+ }
188
+
189
+ const zoomMinimap = (factor) => {
190
+ view?.zoomMinimapBy(factor)
191
+ refreshZoom()
192
+ }
193
+
194
+ const refreshZoom = () => {
195
+ if (view) {
196
+ zoomReadout.value = `小图倍数 ${view.getMinimapZoom().toFixed(2)} · ${zoomable.value ? '滚轮缩放小图' : '滚轮缩放主图'}`
197
+ }
198
+ }
199
+
200
+ const refreshLayerReadout = () => {
201
+ layerReadout.value = `网格层 ${layerPaints.grid} 次(${layerPaints.gridStage}) · 指北针层 ${layerPaints.compass} 次`
202
+ refreshReadout()
203
+ }
204
+
205
+ const fit = () => view?.fitView(44)
206
+ const zoomBy = factor => view?.zoomBy(factor)
207
+
208
+ onMounted(() => {
209
+ view = createGisView(container.value, {
210
+ points: dataset.points,
211
+ lines: dataset.lines,
212
+ channels: dataset.channels,
213
+ channelWidth: 2.4,
214
+ channelInset: 1.8,
215
+ pointStyle: { size: 6, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1.5 },
216
+ lineStyle: { width: 1.35, color: 'rgba(120, 168, 226, 0.52)' },
217
+ showGrid: true,
218
+ gridColor: 'rgba(126, 168, 214, 0.2)',
219
+ fitPadding: 44,
220
+ features: 'view',
221
+ label: { enabled: true, content: '{name}', position: 'top', gap: 7, maxCount: 18, style: { color: '#27405f', fontSize: '11px' } },
222
+ minimap: minimapOptions(),
223
+ onReady: () => {
224
+ emit('notice', '鹰眼图配置页:改控件会即时 updateOptions,右侧读数同步备份尺寸与像素比')
225
+ eventText.value = 'ready:鹰眼图已挂载'
226
+ requestAnimationFrame(refreshReadout)
227
+ },
228
+ onWarning: text => {
229
+ eventText.value = `warning:${text}`
230
+ }
231
+ })
232
+ })
233
+
234
+ onBeforeUnmount(() => {
235
+ view?.destroy()
236
+ view = null
237
+ })
238
+
239
+ const sizeSummary = computed(() => `${SIZES[size.value]?.[0] ?? 0}×${SIZES[size.value]?.[1] ?? 0}`)
240
+ </script>
241
+
242
+ <template>
243
+ <section class="dv-demo">
244
+ <div class="dv-stage">
245
+ <div ref="container" class="dv-canvas"></div>
246
+ <div class="dv-toolbar">
247
+ <button type="button" @click="fit">适应全部</button>
248
+ <button type="button" @click="zoomBy(1.4)">放大</button>
249
+ <button type="button" @click="zoomBy(1 / 1.4)">缩小</button>
250
+ </div>
251
+ </div>
252
+ <aside class="dv-panel">
253
+ <div class="dv-group">
254
+ <h3>布局与范围 <span>{{ sizeSummary }}</span></h3>
255
+ <div class="dv-row">
256
+ <label><input v-model="enabled" type="checkbox" data-demo="minimap-enabled" @change="applyMinimap" /> 显示鹰眼图</label>
257
+ </div>
258
+ <div class="dv-row">
259
+ <label>尺寸
260
+ <select v-model="size" data-demo="minimap-size" @change="applyMinimap">
261
+ <option value="small">小</option>
262
+ <option value="medium">中</option>
263
+ <option value="large">大</option>
264
+ </select>
265
+ </label>
266
+ <label>位置
267
+ <select v-model="position" data-demo="minimap-position" @change="applyMinimap">
268
+ <option value="top-left">左上</option>
269
+ <option value="top-right">右上</option>
270
+ <option value="bottom-left">左下</option>
271
+ <option value="bottom-right">右下</option>
272
+ </select>
273
+ </label>
274
+ </div>
275
+ <div class="dv-row">
276
+ <label>范围
277
+ <select v-model="extent" data-demo="minimap-extent" @change="applyMinimap">
278
+ <option value="auto">数据 ∪ 底图</option>
279
+ <option value="data">只看数据</option>
280
+ <option value="background">只看底图</option>
281
+ </select>
282
+ </label>
283
+ <label>备份倍率
284
+ <select v-model="maxPixelRatio" data-demo="minimap-ratio" @change="applyMinimap">
285
+ <option value="1">1×</option>
286
+ <option value="2">2×</option>
287
+ <option value="3">3×</option>
288
+ <option value="4">4×</option>
289
+ </select>
290
+ </label>
291
+ </div>
292
+ <p class="dv-hint"><code>maxPixelRatio</code> 越大越清晰,也越吃填充率;高分屏按机器档位调到 2 通常就够。</p>
293
+ </div>
294
+ <div class="dv-group">
295
+ <h3>规则与交互</h3>
296
+ <div class="dv-row">
297
+ <label>规则
298
+ <select v-model="ruleMode" data-demo="minimap-rules" @change="applyMinimap">
299
+ <option value="voltage">按电压单独配</option>
300
+ <option value="inherit">沿用主图</option>
301
+ <option value="plain">只用基础样式</option>
302
+ </select>
303
+ </label>
304
+ <label>层级
305
+ <select v-model="levelMode" data-demo="minimap-levels" @change="applyMinimap">
306
+ <option value="all">全部</option>
307
+ <option value="top">只看高层</option>
308
+ </select>
309
+ </label>
310
+ </div>
311
+ <div class="dv-row">
312
+ <label><input v-model="linkedSizes" type="checkbox" data-demo="minimap-linked" @change="applyMinimap" /> 尺寸跟随主图缩放</label>
313
+ <label><input v-model="channelCircuits" type="checkbox" data-demo="minimap-circuits" @change="applyMinimap" /> 画全部回线</label>
314
+ </div>
315
+ <div class="dv-row">
316
+ <label><input v-model="zoomable" type="checkbox" data-demo="minimap-zoomable" @change="applyMinimap" /> 小图自身可缩放</label>
317
+ <label>空白拖动
318
+ <select v-model="dragAction" data-demo="minimap-drag-action" @change="applyMinimap">
319
+ <option value="pan">平移小图</option>
320
+ <option value="box">直接框选</option>
321
+ </select>
322
+ </label>
323
+ </div>
324
+ <p class="dv-hint">打开「小图自身可缩放」后,滚轮缩放小图;按住 Ctrl / ⌘ + 滚轮仍然缩放主图。视口框可以拖,空白处长按跟随。</p>
325
+ </div>
326
+ <div class="dv-group">
327
+ <h3>扩展层</h3>
328
+ <div class="dv-row">
329
+ <label><input v-model="customLayers" type="checkbox" data-demo="minimap-layers" @change="applyMinimap" /> 网格 + 指北针</label>
330
+ <label>网格位置
331
+ <select v-model="gridPlacement" data-demo="minimap-layer-placement" @change="applyMinimap">
332
+ <option value="below-data">数据下面</option>
333
+ <option value="above-data">数据上面</option>
334
+ </select>
335
+ </label>
336
+ <button type="button" class="dv-button" data-demo="minimap-layer-read" @click="refreshLayerReadout">读取层计数</button>
337
+ </div>
338
+ <p class="dv-readout" data-demo="minimap-layer-readout">{{ layerReadout }}</p>
339
+ <p class="dv-hint"><code>layers</code> 用 <code>placement</code> 选择画在数据前还是数据后,同段内再按 <code>order</code> 排序;旧的单个 <code>draw</code> 钩子仍然可用,并排在最后。</p>
340
+ </div>
341
+ <div class="dv-group">
342
+ <h3>小图倍数</h3>
343
+ <div class="dv-row">
344
+ <button type="button" class="dv-button" data-demo="minimap-zoom-out" @click="zoomMinimap(1 / 1.35)">-</button>
345
+ <button type="button" class="dv-button dv-button--primary" data-demo="minimap-zoom-reset" @click="resetZoom">1×</button>
346
+ <button type="button" class="dv-button" data-demo="minimap-zoom-in" @click="zoomMinimap(1.35)">+</button>
347
+ </div>
348
+ <p class="dv-readout" data-demo="minimap-zoom-readout">{{ zoomReadout }}</p>
349
+ </div>
350
+ <div class="dv-group">
351
+ <h3>读数</h3>
352
+ <p class="dv-readout" data-demo="minimap-readout">{{ readout }}</p>
353
+ <p class="dv-readout" data-demo="minimap-event" aria-live="polite">{{ eventText }}</p>
354
+ <p class="dv-readout" data-demo="minimap-interaction" aria-live="polite">{{ interactionReadout }}</p>
355
+ </div>
356
+ </aside>
357
+ </section>
358
+ </template>
@@ -27,6 +27,8 @@ const qualityMode = ref('auto')
27
27
  const statsText = ref('—')
28
28
  const filterText = ref('未筛选:全部要素都画')
29
29
  const qualityText = ref('—')
30
+ const denseMode = ref('auto')
31
+ const denseText = ref('—')
30
32
  const busy = ref(false)
31
33
  /** 交互期细线快路径(缺省开):拖动 / 缩放期间线段用 gl.LINES 画,停手后回到四边形展开 */
32
34
  const thinLines = ref(true)
@@ -70,6 +72,8 @@ const buildOptions = () => ({
70
72
  pointStyle: { size: 4, color: '#ffffff', strokeColor: '#4c8dff', strokeWidth: 1 },
71
73
  lineStyle: { width: 1, color: 'rgba(120, 168, 226, 0.45)' },
72
74
  quality: qualityMode.value,
75
+ // 大数据展示模式:auto 按可见节点数 / 屏幕密度 / 总量自动切,面板上也可以强制开或关
76
+ denseMode: denseMode.value,
73
77
  // MSAA 只在建上下文时用一次,细线开关每帧现读
74
78
  antialias: antialias.value,
75
79
  interactionThinLines: thinLines.value,
@@ -103,6 +107,10 @@ const buildOptions = () => ({
103
107
  const speed = next.idle ? '空闲' : `${Math.round(next.fps)} fps`
104
108
  statsText.value = `${speed} · 最近一帧 ${next.frameMs.toFixed(2)} ms · 画 ${next.pointCount.toLocaleString()} 点 / ${next.lineCount.toLocaleString()} 线`
105
109
  deviceText.value = `${next.antialias ? 'MSAA 4×' : '无 MSAA'} · ${next.software ? '软件光栅' : 'GPU 光栅'} · 线段${next.thinLines ? '走细线快路径' : '走四边形展开'} · ${next.noMsaa ? '交互 / 动画帧走非 MSAA 后备缓冲' : '与静止帧同一张画布'}`
110
+ const dense = next.dense
111
+ denseText.value = dense
112
+ ? `密集档:${dense.active ? '开' : '关'} · ${dense.reason} · 可见 ${dense.visiblePoints === null ? '—' : dense.visiblePoints.toLocaleString()} 点(阈值 ${dense.visibleThreshold.toLocaleString()})`
113
+ : '—'
106
114
  },
107
115
  onQualityChange: (next) => {
108
116
  qualityText.value = `画质:${next?.name ?? next?.level ?? '—'}`
@@ -162,6 +170,12 @@ const applyQuality = () => {
162
170
  emit('notice', `画质档:${qualityMode.value}`)
163
171
  }
164
172
 
173
+ /** 密集档:auto 自动判定,on / off 强制开关;切回 auto 时立即按当前视口重判一次 */
174
+ const applyDenseMode = () => {
175
+ view?.updateOptions({ denseMode: denseMode.value })
176
+ emit('notice', `大数据展示模式:${denseMode.value}`)
177
+ }
178
+
165
179
  const applyThinLines = () => {
166
180
  // 表现类配置:只在下一帧现读,不重排场景、不重传实例
167
181
  view?.updateOptions({ interactionThinLines: thinLines.value })
@@ -222,6 +236,21 @@ onBeforeUnmount(() => {
222
236
  </div>
223
237
  <div class="dv-group">
224
238
  <h3>筛选与画质</h3>
239
+ <div class="dv-row">
240
+ <label>密集档
241
+ <select v-model="denseMode" @change="applyDenseMode">
242
+ <option value="auto">auto(按数据量 / 密度)</option>
243
+ <option value="on">on(强制开启)</option>
244
+ <option value="off">off(强制关闭)</option>
245
+ </select>
246
+ </label>
247
+ </div>
248
+ <p class="dv-readout">{{ denseText }}</p>
249
+ <p class="dv-hint">
250
+ 密集档是数据侧的自动降级:可见节点达到 4000、每百万 CSS 像素达到 3000 个,或总数达到 5 万时,
251
+ 标签切到避让 / 截断,线路动点与节点涟漪暂停;条件回落并留 22% 缓冲后自动恢复。
252
+ 档位与判定依据看 getStats().dense,也可以按 updateOptions({ denseMode }) 强制切换。
253
+ </p>
225
254
  <div class="dv-row">
226
255
  <label>电压
227
256
  <select v-model="voltageFilter" @change="applyFilter">