gis-view-webgl2 0.1.13 → 0.1.15
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/dist/index.cjs +1 -1
- package/dist/index.d.ts +140 -60
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +73 -5
- package/docs/GisView.md +49 -49
- 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 +87 -16
- package/docs/demos/ApiCoverageDemo.vue +1 -5
- package/docs/demos/BackendSyncDemo.vue +1 -2
- package/docs/demos/BackgroundDemo.vue +68 -1
- package/docs/demos/BasicDemo.vue +76 -76
- package/docs/demos/BatchAddDemo.vue +2 -1
- package/docs/demos/ChannelDemo.vue +1 -3
- package/docs/demos/ClusterDemo.vue +48 -1
- package/docs/demos/CopyMergeDemo.vue +899 -904
- package/docs/demos/DemoShell.vue +336 -336
- package/docs/demos/EventDemo.vue +300 -300
- package/docs/demos/ExtensionDemo.vue +200 -200
- package/docs/demos/GisViewDemo.vue +64 -64
- package/docs/demos/LabelDemo.vue +489 -489
- package/docs/demos/StyleDemo.vue +67 -67
- package/docs/demos/api-index.js +24 -29
- package/docs/demos/demos.js +92 -92
- package/docs/demos/gis-view-demo/useGisViewDemo.js +19 -14
- package/docs/demos/shared/demoData.js +107 -107
- package/docs/demos/shared/demoStyle.css +249 -249
- package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +3 -3
- 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 +3 -0
- package/docs/from0/01-/345/205/210/346/212/212/345/234/260/345/233/276/346/203/263/346/270/205/346/245/232.md +8 -7
- package/docs/index.html +3 -3
- package/docs/internals/04-/347/233/270/346/234/272/345/235/220/346/240/207/344/270/216/345/203/217/347/264/240.md +6 -4
- package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +2 -2
- package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +36 -16
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +38 -84
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +36 -19
- 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 +1 -1
- 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 +7 -7
- package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +15 -15
- 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 +1 -1
- package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +6 -0
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +1 -1
- package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -0
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +10 -8
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +11 -7
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +27 -8
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +27 -4
- 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 +3 -3
- package/docs/playground/index.html +1 -1
- package/docs/sample-document/345/255/227/346/256/265/350/257/246/350/247/243.md +2 -2
- package/docs/steps/check-doc-truth.mjs +1 -1
- package/docs/steps/selftest-actions.html +4 -4
- package/docs/steps/selftest-data.html +5 -5
- package/docs/steps/selftest-demos.html +1683 -1619
- package/docs/steps/selftest-docs.html +4 -4
- package/docs/steps/selftest-effects.html +1 -1
- package/docs/steps/selftest-features.html +548 -47
- package/docs/steps/selftest-full.html +566 -566
- package/docs/steps/selftest-labels.html +647 -647
- package/docs/steps/selftest-perf.html +2282 -2237
- package/docs/steps/selftest-select.html +53 -14
- package/docs/steps/step-10-full.js +1 -4
- package/docs/steps/step-12-events.js +1 -4
- package/package.json +43 -43
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* 编辑本身完全由组件的 beginBackgroundEdit / updateBackgroundEdit / endBackgroundEdit 接管;
|
|
5
5
|
* 这个页面只负责导入素材、调背景透明度与内置编辑器的配置。
|
|
6
6
|
*/
|
|
7
|
-
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue'
|
|
7
|
+
import { computed, nextTick, onBeforeUnmount, onMounted, reactive, ref, shallowRef, watch } from 'vue'
|
|
8
8
|
import { createGisView } from '../../src/index.js'
|
|
9
9
|
import { buildDemoData } from './shared/demoData.js'
|
|
10
10
|
import { buildDemoBasemap } from './demoBackground.js'
|
|
@@ -104,6 +104,9 @@ const heightDraft = ref(formatWorld(editHeight.value))
|
|
|
104
104
|
const rotationDraft = ref('0')
|
|
105
105
|
const resolvedReadout = ref('尚未添加背景')
|
|
106
106
|
const notice = ref('先看网架;再添加背景。编辑背景时网架不可点击,滚轮仍可整体缩放。')
|
|
107
|
+
const savedLngLat = shallowRef(null)
|
|
108
|
+
const savedAnchor = shallowRef(null)
|
|
109
|
+
const roundtripReadout = ref('尚未保存经纬度。先切到 GeoJSON 背景,再保存整份网架。')
|
|
107
110
|
let view = null
|
|
108
111
|
let readoutTimer = 0
|
|
109
112
|
|
|
@@ -325,6 +328,59 @@ const rotateBy = (delta) => {
|
|
|
325
328
|
view.updateBackgroundEdit({ angle: normalizeRotation(editRotation.value + delta) })
|
|
326
329
|
syncEditorState(view.getBackgroundEdit())
|
|
327
330
|
}
|
|
331
|
+
const formatLngLat = coordinate => coordinate && Number.isFinite(Number(coordinate[0])) && Number.isFinite(Number(coordinate[1]))
|
|
332
|
+
? `[${Number(coordinate[0]).toFixed(6)}, ${Number(coordinate[1]).toFixed(6)}]`
|
|
333
|
+
: '—'
|
|
334
|
+
const saveNetworkLngLat = () => {
|
|
335
|
+
if (!view || backgroundMode.value !== 'geojson') {
|
|
336
|
+
roundtripReadout.value = '保存经纬度需要 GeoJSON 背景:先用上方按钮添加底图。'
|
|
337
|
+
return
|
|
338
|
+
}
|
|
339
|
+
const data = view.getData()
|
|
340
|
+
const firstPoint = data.points[0] ?? null
|
|
341
|
+
const saved = view.mapDataToLngLat()
|
|
342
|
+
if (!saved) {
|
|
343
|
+
roundtripReadout.value = '当前没有可用的 GeoJSON 投影,未能保存经纬度。'
|
|
344
|
+
return
|
|
345
|
+
}
|
|
346
|
+
savedLngLat.value = saved
|
|
347
|
+
savedAnchor.value = firstPoint ? { id: firstPoint.id, x: firstPoint.x, y: firstPoint.y } : null
|
|
348
|
+
const savedFirstPoint = firstPoint ? saved.points?.find(point => point.id === firstPoint.id) ?? saved.points?.[0] : null
|
|
349
|
+
roundtripReadout.value = firstPoint && savedFirstPoint
|
|
350
|
+
? `已保存 ${saved.points?.length ?? 0} 节点 / ${saved.lines?.length ?? 0} 线路 / ${saved.channels?.length ?? 0} 通道。首节点 ${firstPoint.id}:世界 [${formatWorld(firstPoint.x)}, ${formatWorld(firstPoint.y)}] → 经纬度 ${formatLngLat([savedFirstPoint.x, savedFirstPoint.y])}。`
|
|
351
|
+
: `已保存 ${saved.points?.length ?? 0} 节点 / ${saved.lines?.length ?? 0} 线路 / ${saved.channels?.length ?? 0} 通道;网架里还没有节点。`
|
|
352
|
+
emit('notice', '经纬度存档已生成:现在可以清空网架,再用 mapLngLatToData() 原样回灌。')
|
|
353
|
+
}
|
|
354
|
+
const clearNetwork = () => {
|
|
355
|
+
if (!view) {
|
|
356
|
+
return
|
|
357
|
+
}
|
|
358
|
+
view.updateData({ points: [], lines: [], channels: [] })
|
|
359
|
+
roundtripReadout.value = savedLngLat.value
|
|
360
|
+
? '网架已清空;经纬度存档仍在,点「3. 原样回灌」恢复。'
|
|
361
|
+
: '网架已清空;当前没有经纬度存档。'
|
|
362
|
+
emit('notice', '网架已清空,背景与相机位置保持不变。')
|
|
363
|
+
}
|
|
364
|
+
const restoreNetworkLngLat = () => {
|
|
365
|
+
if (!view || backgroundMode.value !== 'geojson' || !savedLngLat.value) {
|
|
366
|
+
roundtripReadout.value = '回灌需要 GeoJSON 背景和一份已保存的经纬度存档。'
|
|
367
|
+
return
|
|
368
|
+
}
|
|
369
|
+
const restored = view.mapLngLatToData(savedLngLat.value)
|
|
370
|
+
if (!restored) {
|
|
371
|
+
roundtripReadout.value = '当前没有可用的 GeoJSON 投影,未能回灌。'
|
|
372
|
+
return
|
|
373
|
+
}
|
|
374
|
+
view.updateData(restored)
|
|
375
|
+
const anchor = savedAnchor.value
|
|
376
|
+
const restoredAnchor = anchor ? view.getData().points.find(point => point.id === anchor.id) ?? null : null
|
|
377
|
+
const delta = anchor && restoredAnchor ? Math.hypot(restoredAnchor.x - anchor.x, restoredAnchor.y - anchor.y) : null
|
|
378
|
+
const savedFirstPoint = anchor ? savedLngLat.value.points?.find(point => point.id === anchor.id) ?? savedLngLat.value.points?.[0] : null
|
|
379
|
+
roundtripReadout.value = anchor && restoredAnchor && delta !== null && delta < 1e-7
|
|
380
|
+
? `回灌完成:${restored.points?.length ?? 0} 节点 / ${restored.lines?.length ?? 0} 线路 / ${restored.channels?.length ?? 0} 通道。首节点 ${anchor.id} 经纬度 ${formatLngLat(savedFirstPoint ? [savedFirstPoint.x, savedFirstPoint.y] : null)} → 世界 [${formatWorld(restoredAnchor.x)}, ${formatWorld(restoredAnchor.y)}],与保存前一致(误差 ${delta.toExponential(1)})。`
|
|
381
|
+
: `回灌完成,但首节点世界坐标与保存前不一致${delta === null ? '' : `(误差 ${delta.toExponential(1)})`};请确认底图 bounds / rotation 没有在保存后改动。`
|
|
382
|
+
emit('notice', '经纬度存档已按当前底图投影回灌为世界坐标。')
|
|
383
|
+
}
|
|
328
384
|
const toggleNetwork = () => {
|
|
329
385
|
networkVisible.value = !networkVisible.value
|
|
330
386
|
}
|
|
@@ -533,6 +589,17 @@ onBeforeUnmount(() => {
|
|
|
533
589
|
<p class="dv-readout" data-demo="background-resolved">{{ resolvedReadout }}</p>
|
|
534
590
|
</div>
|
|
535
591
|
|
|
592
|
+
<div class="dv-group">
|
|
593
|
+
<h3>经纬度存档回灌<span class="dv-chip">mapDataToLngLat → mapLngLatToData</span></h3>
|
|
594
|
+
<div class="dv-row">
|
|
595
|
+
<button type="button" class="dv-button" data-demo="background-save-lnglat" :disabled="backgroundMode !== 'geojson'" @click="saveNetworkLngLat">1. 保存经纬度</button>
|
|
596
|
+
<button type="button" class="dv-button" data-demo="background-clear-network" :disabled="!savedLngLat" @click="clearNetwork">2. 清空网架</button>
|
|
597
|
+
<button type="button" class="dv-button dv-button--primary" data-demo="background-restore-lnglat" :disabled="!savedLngLat || backgroundMode !== 'geojson'" @click="restoreNetworkLngLat">3. 原样回灌</button>
|
|
598
|
+
</div>
|
|
599
|
+
<p class="dv-readout" data-demo="background-lnglat-readout">{{ roundtripReadout }}</p>
|
|
600
|
+
<p class="dv-hint">保存时把节点、线路、通道分组一起写成经纬度;回灌先由 `mapLngLatToData()` 按当前最终底图投影写回世界坐标,再交给 `updateData()`。底图未改动时世界坐标与屏幕落点原样恢复。</p>
|
|
601
|
+
</div>
|
|
602
|
+
|
|
536
603
|
<div class="dv-group">
|
|
537
604
|
<h3>操作提示</h3>
|
|
538
605
|
<p class="dv-hint" data-demo="background-notice">{{ notice }}</p>
|
package/docs/demos/BasicDemo.vue
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<script setup>
|
|
2
|
-
/**
|
|
1
|
+
<script setup>
|
|
2
|
+
/**
|
|
3
3
|
* 基础视图:一份数据 + 最少的配置,重点看相机、比例、空闲与拾取读数怎么拿。
|
|
4
4
|
*
|
|
5
5
|
* 首屏用顶层 center / scale 落位;面板能读写比例、观察 idleTimeout 的回调与事件,
|
|
@@ -7,16 +7,16 @@
|
|
|
7
7
|
* onCameraChange / onClick / onSelect / onStats / onIdle 的值直接摊开 ——
|
|
8
8
|
* 事件载荷长什么样、什么时候发,一眼能对上。
|
|
9
9
|
*/
|
|
10
|
-
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|
11
|
-
import { createGisView } from '../../src/index.js'
|
|
10
|
+
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
|
11
|
+
import { createGisView } from '../../src/index.js'
|
|
12
12
|
import { buildDemoData, DEMO_CENTER } from './shared/demoData.js'
|
|
13
|
-
import './shared/demoStyle.css'
|
|
14
|
-
|
|
15
|
-
const emit = defineEmits(['notice'])
|
|
16
|
-
const container = ref(null)
|
|
17
|
-
const pointCount = ref(28)
|
|
18
|
-
const gridOn = ref(true)
|
|
19
|
-
const labelOn = ref(true)
|
|
13
|
+
import './shared/demoStyle.css'
|
|
14
|
+
|
|
15
|
+
const emit = defineEmits(['notice'])
|
|
16
|
+
const container = ref(null)
|
|
17
|
+
const pointCount = ref(28)
|
|
18
|
+
const gridOn = ref(true)
|
|
19
|
+
const labelOn = ref(true)
|
|
20
20
|
const channelOn = ref(true)
|
|
21
21
|
const scaleDraft = ref('72')
|
|
22
22
|
const currentScale = ref(72)
|
|
@@ -90,10 +90,10 @@ const buildOptions = () => ({
|
|
|
90
90
|
event: `onIdle:停手 ${Math.round(idleTime)} ms(阈值 ${timeout} ms)`
|
|
91
91
|
}
|
|
92
92
|
},
|
|
93
|
-
onReady: () => {
|
|
94
|
-
emit('notice', `已加载 ${dataset.points.length} 个节点 / ${dataset.lines.length} 条线路`)
|
|
95
|
-
readouts.value = { ...readouts.value, event: 'ready:WebGL2 上下文就绪' }
|
|
96
|
-
},
|
|
93
|
+
onReady: () => {
|
|
94
|
+
emit('notice', `已加载 ${dataset.points.length} 个节点 / ${dataset.lines.length} 条线路`)
|
|
95
|
+
readouts.value = { ...readouts.value, event: 'ready:WebGL2 上下文就绪' }
|
|
96
|
+
},
|
|
97
97
|
onClick: (payload) => {
|
|
98
98
|
readouts.value = { ...readouts.value, pick: describeTarget(payload), event: `click:${describeTarget(payload)}` }
|
|
99
99
|
},
|
|
@@ -107,10 +107,10 @@ const buildOptions = () => ({
|
|
|
107
107
|
event: count ? `select:${mode} 选中 ${count} 个` : 'select:清空选中'
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
|
-
onCameraChange: (camera) => {
|
|
111
|
-
// 相机每帧都在变:只把读数写进面板,不再触发别的重活
|
|
112
|
-
readouts.value = { ...readouts.value, camera: `scale ${camera.scale.toPrecision(4)} px/单位 · 中心 ${camera.center[0].toFixed(3)}, ${camera.center[1].toFixed(3)}` }
|
|
113
|
-
},
|
|
110
|
+
onCameraChange: (camera) => {
|
|
111
|
+
// 相机每帧都在变:只把读数写进面板,不再触发别的重活
|
|
112
|
+
readouts.value = { ...readouts.value, camera: `scale ${camera.scale.toPrecision(4)} px/单位 · 中心 ${camera.center[0].toFixed(3)}, ${camera.center[1].toFixed(3)}` }
|
|
113
|
+
},
|
|
114
114
|
onStats: (next) => {
|
|
115
115
|
// fps 每帧都变,节流后再写响应式数据(十万级数据下每帧重渲染面板本身也是开销)
|
|
116
116
|
const now = performance.now()
|
|
@@ -122,24 +122,24 @@ const buildOptions = () => ({
|
|
|
122
122
|
const speed = next.idle ? '空闲' : `${Math.round(next.fps)} fps`
|
|
123
123
|
readouts.value = { ...readouts.value, stats: `${speed} · 最近一帧 ${next.frameMs.toFixed(2)} ms · ${next.pointCount.toLocaleString()} 点 / ${next.lineCount.toLocaleString()} 线` }
|
|
124
124
|
},
|
|
125
|
-
onWarning: (text) => {
|
|
126
|
-
readouts.value = { ...readouts.value, event: `warning:${text}` }
|
|
127
|
-
}
|
|
128
|
-
})
|
|
129
|
-
|
|
130
|
-
/** 换数据量:数据与通道一起换掉,相机重新适应(不改选项里的样式,所以点位外观不跳) */
|
|
131
|
-
const applyData = () => {
|
|
132
|
-
if (!view) {
|
|
133
|
-
return
|
|
134
|
-
}
|
|
135
|
-
dataset = buildDemoData({ count: pointCount.value })
|
|
136
|
-
view.updateData(dataset.points, channelOn.value ? dataset.lines : dataset.linesWithoutChannel)
|
|
125
|
+
onWarning: (text) => {
|
|
126
|
+
readouts.value = { ...readouts.value, event: `warning:${text}` }
|
|
127
|
+
}
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
/** 换数据量:数据与通道一起换掉,相机重新适应(不改选项里的样式,所以点位外观不跳) */
|
|
131
|
+
const applyData = () => {
|
|
132
|
+
if (!view) {
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
dataset = buildDemoData({ count: pointCount.value })
|
|
136
|
+
view.updateData(dataset.points, channelOn.value ? dataset.lines : dataset.linesWithoutChannel)
|
|
137
137
|
view.updateOptions({ channels: channelOn.value ? dataset.channels : [] })
|
|
138
138
|
view.fitView(40)
|
|
139
139
|
syncScaleFromView()
|
|
140
140
|
emit('notice', `已换成 ${dataset.points.length} 个节点 / ${dataset.lines.length} 条线路`)
|
|
141
|
-
}
|
|
142
|
-
|
|
141
|
+
}
|
|
142
|
+
|
|
143
143
|
/**
|
|
144
144
|
* 开关键:只改与自己相关的那几项 —— 一次「把整份 options 再发一遍」会连数据一起重发,
|
|
145
145
|
* 拖过的节点与标签偏移都会被冲回原样,这里只改网格与标签两项。
|
|
@@ -155,7 +155,7 @@ const applyChannel = () => {
|
|
|
155
155
|
channels: channelOn.value ? dataset.channels : []
|
|
156
156
|
})
|
|
157
157
|
}
|
|
158
|
-
|
|
158
|
+
|
|
159
159
|
const resetCamera = () => {
|
|
160
160
|
view?.fitView(40)
|
|
161
161
|
syncScaleFromView()
|
|
@@ -209,7 +209,7 @@ const toggleIdle = () => {
|
|
|
209
209
|
event: idleEnabled.value ? `idleTimeout 已开启(${IDLE_TIMEOUT_MS} ms)` : 'idleTimeout 已关闭(0)'
|
|
210
210
|
}
|
|
211
211
|
}
|
|
212
|
-
|
|
212
|
+
|
|
213
213
|
onMounted(() => {
|
|
214
214
|
view = createGisView(container.value, buildOptions())
|
|
215
215
|
syncScaleFromView()
|
|
@@ -238,18 +238,18 @@ onBeforeUnmount(() => {
|
|
|
238
238
|
window.clearInterval(idleTicker)
|
|
239
239
|
idleTicker = null
|
|
240
240
|
view?.destroy()
|
|
241
|
-
view = null
|
|
242
|
-
})
|
|
243
|
-
</script><template>
|
|
244
|
-
<section class="dv-demo">
|
|
245
|
-
<div class="dv-stage">
|
|
246
|
-
<div ref="container" class="dv-canvas"></div>
|
|
247
|
-
<div class="dv-toolbar">
|
|
248
|
-
<button type="button" @click="resetCamera">适应全部</button>
|
|
249
|
-
<button type="button" @click="zoomBy(1.4)">放大</button>
|
|
250
|
-
<button type="button" @click="zoomBy(1 / 1.4)">缩小</button>
|
|
251
|
-
</div>
|
|
252
|
-
</div>
|
|
241
|
+
view = null
|
|
242
|
+
})
|
|
243
|
+
</script><template>
|
|
244
|
+
<section class="dv-demo">
|
|
245
|
+
<div class="dv-stage">
|
|
246
|
+
<div ref="container" class="dv-canvas"></div>
|
|
247
|
+
<div class="dv-toolbar">
|
|
248
|
+
<button type="button" @click="resetCamera">适应全部</button>
|
|
249
|
+
<button type="button" @click="zoomBy(1.4)">放大</button>
|
|
250
|
+
<button type="button" @click="zoomBy(1 / 1.4)">缩小</button>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
253
|
<aside class="dv-panel">
|
|
254
254
|
<div class="dv-group">
|
|
255
255
|
<h3>比例读写<span class="dv-chip">center / scale 首屏</span></h3>
|
|
@@ -275,36 +275,36 @@ onBeforeUnmount(() => {
|
|
|
275
275
|
</div>
|
|
276
276
|
<div class="dv-group">
|
|
277
277
|
<h3>数据</h3>
|
|
278
|
-
<div class="dv-row">
|
|
279
|
-
<label>节点数
|
|
280
|
-
<select v-model.number="pointCount" @change="applyData">
|
|
281
|
-
<option :value="12">12</option>
|
|
282
|
-
<option :value="28">28</option>
|
|
283
|
-
<option :value="60">60</option>
|
|
284
|
-
</select>
|
|
285
|
-
</label>
|
|
286
|
-
<button type="button" class="dv-button" @click="applyData">重建数据</button>
|
|
287
|
-
</div>
|
|
288
|
-
<div class="dv-row">
|
|
289
|
-
<label><input v-model="gridOn" type="checkbox" @change="applyOptions" /> 网格</label>
|
|
290
|
-
<label><input v-model="labelOn" type="checkbox" @change="applyOptions" /> 节点标签</label>
|
|
278
|
+
<div class="dv-row">
|
|
279
|
+
<label>节点数
|
|
280
|
+
<select v-model.number="pointCount" @change="applyData">
|
|
281
|
+
<option :value="12">12</option>
|
|
282
|
+
<option :value="28">28</option>
|
|
283
|
+
<option :value="60">60</option>
|
|
284
|
+
</select>
|
|
285
|
+
</label>
|
|
286
|
+
<button type="button" class="dv-button" @click="applyData">重建数据</button>
|
|
287
|
+
</div>
|
|
288
|
+
<div class="dv-row">
|
|
289
|
+
<label><input v-model="gridOn" type="checkbox" @change="applyOptions" /> 网格</label>
|
|
290
|
+
<label><input v-model="labelOn" type="checkbox" @change="applyOptions" /> 节点标签</label>
|
|
291
291
|
<label><input v-model="channelOn" type="checkbox" @change="applyChannel" /> 通道</label>
|
|
292
|
-
</div>
|
|
293
|
-
<p class="dv-hint">打开标签后可以拖着标签走一段:松手后偏移记在状态里,再移动节点标签跟着走。</p>
|
|
294
|
-
</div>
|
|
295
|
-
<div class="dv-group">
|
|
296
|
-
<h3>读数</h3>
|
|
297
|
-
<p class="dv-readout">相机:{{ readouts.camera }}</p>
|
|
298
|
-
<p class="dv-readout">拾取:{{ readouts.pick }}</p>
|
|
299
|
-
<p class="dv-readout">性能:{{ readouts.stats }}</p>
|
|
300
|
-
<p class="dv-readout">事件:{{ readouts.event }}</p>
|
|
301
|
-
</div>
|
|
302
|
-
<div class="dv-group">
|
|
303
|
-
<h3>试一下</h3>
|
|
304
|
-
<p class="dv-hint">单击节点 / 线路 / 通道看「拾取」;空白处拖框看「选中」;滚轮缩放、拖动平移时看相机的 scale 与中心。</p>
|
|
305
|
-
</div>
|
|
306
|
-
</aside>
|
|
307
|
-
</section>
|
|
292
|
+
</div>
|
|
293
|
+
<p class="dv-hint">打开标签后可以拖着标签走一段:松手后偏移记在状态里,再移动节点标签跟着走。</p>
|
|
294
|
+
</div>
|
|
295
|
+
<div class="dv-group">
|
|
296
|
+
<h3>读数</h3>
|
|
297
|
+
<p class="dv-readout">相机:{{ readouts.camera }}</p>
|
|
298
|
+
<p class="dv-readout">拾取:{{ readouts.pick }}</p>
|
|
299
|
+
<p class="dv-readout">性能:{{ readouts.stats }}</p>
|
|
300
|
+
<p class="dv-readout">事件:{{ readouts.event }}</p>
|
|
301
|
+
</div>
|
|
302
|
+
<div class="dv-group">
|
|
303
|
+
<h3>试一下</h3>
|
|
304
|
+
<p class="dv-hint">单击节点 / 线路 / 通道看「拾取」;空白处拖框看「选中」;滚轮缩放、拖动平移时看相机的 scale 与中心。</p>
|
|
305
|
+
</div>
|
|
306
|
+
</aside>
|
|
307
|
+
</section>
|
|
308
308
|
</template>
|
|
309
309
|
|
|
310
310
|
<style scoped>
|
|
@@ -352,7 +352,8 @@ onMounted(() => {
|
|
|
352
352
|
lineStyle: { width: 1.4, color: 'rgba(120, 168, 226, 0.6)' },
|
|
353
353
|
showGrid: true,
|
|
354
354
|
fitPadding: 48,
|
|
355
|
-
|
|
355
|
+
// 批量拐点接口要打开拐点总闸;页面本身不需要画布拖拽
|
|
356
|
+
features: { waypoint: true },
|
|
356
357
|
history: { limit: 50 },
|
|
357
358
|
onReady: () => {
|
|
358
359
|
refreshState()
|
|
@@ -316,7 +316,7 @@ const buildOptions = () => ({
|
|
|
316
316
|
fitPadding: 60,
|
|
317
317
|
// 开关缺省全关(见 features.js):这个演示要的是「拖节点改通道 + 右键通道回线出交互框 + Delete 删除」,逐项显式打开
|
|
318
318
|
// 通道路由与普通右键共用 contextMenu,不再另开一个功能开关
|
|
319
|
-
features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, drag: true, remove: true },
|
|
319
|
+
features: { pan: true, zoom: true, hover: true, select: true, contextMenu: true, keyboard: true, drag: true, remove: true, waypoint: true },
|
|
320
320
|
// 通道选中模式:点一条回线选它自己 / 连所在分组 / 连整条通道
|
|
321
321
|
channelSelectMode: channelSelectMode.value,
|
|
322
322
|
// 分组拐点拖动:共用骨架 / 只移同组同序号拐点
|
|
@@ -326,8 +326,6 @@ const buildOptions = () => ({
|
|
|
326
326
|
channelHiddenLineLayout: channelHiddenLineLayout.value,
|
|
327
327
|
// 删除配置:confirm 缺省(null)表示组件不弹界面、停在待确认 —— 这一页订阅 'remove' 事件自己画确认框
|
|
328
328
|
remove: { scope: 'lines' },
|
|
329
|
-
// 交互框里的「加拐点 / 删拐点」动的是分组骨架,要有这一项才不是灰的(Ctrl + 点击加点、双击删点也一起开了)
|
|
330
|
-
waypointEditable: true,
|
|
331
329
|
// 这两条是假接口的入口:拐点只接 add / remove / end,分组只接整份声明;事件载荷本身已经够组请求
|
|
332
330
|
onWaypointChange: handleWaypointBackend,
|
|
333
331
|
onChannelGroupChange: handleChannelGroupBackend,
|
|
@@ -22,6 +22,11 @@ const clusterOn = ref(true)
|
|
|
22
22
|
const radius = ref(60)
|
|
23
23
|
const minPoints = ref(2)
|
|
24
24
|
const maxZoom = ref(0)
|
|
25
|
+
const clusterColor = ref('#2970db')
|
|
26
|
+
const clusterTextColor = ref('#ffffff')
|
|
27
|
+
const clusterIcon = ref('circle')
|
|
28
|
+
const clusterShowCount = ref(true)
|
|
29
|
+
const clusterSizeScale = ref(1)
|
|
25
30
|
const statsText = ref('—')
|
|
26
31
|
const clusterText = ref('—')
|
|
27
32
|
const eventText = ref('等待点击聚合圆')
|
|
@@ -62,8 +67,25 @@ const buildDataset = (count) => {
|
|
|
62
67
|
return { points }
|
|
63
68
|
}
|
|
64
69
|
|
|
70
|
+
const CLUSTER_ICONS = {
|
|
71
|
+
star: { type: 'svg', path: 'M0 -11 L3.2 -3.4 L11.5 -3.4 L4.9 1.8 L7.3 9.6 L0 5 L-7.3 9.6 L-4.9 1.8 L-11.5 -3.4 L-3.2 -3.4 Z', viewBox: [-12, -12, 24, 24], fill: 'currentColor' },
|
|
72
|
+
pin: { type: 'svg', path: 'M0 -11 C-6 -11 -10 -7 -10 -1 C-10 6 0 12 0 12 C0 12 10 6 10 -1 C10 -7 6 -11 0 -11 Z', viewBox: [-12, -12, 24, 24], fill: 'currentColor' }
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const clusterSymbol = () => CLUSTER_ICONS[clusterIcon.value] ?? clusterIcon.value
|
|
76
|
+
|
|
65
77
|
const clusterOptions = () => clusterOn.value
|
|
66
|
-
? {
|
|
78
|
+
? {
|
|
79
|
+
enabled: true,
|
|
80
|
+
radius: radius.value,
|
|
81
|
+
minPoints: minPoints.value,
|
|
82
|
+
maxZoom: maxZoom.value,
|
|
83
|
+
color: clusterColor.value,
|
|
84
|
+
textColor: clusterTextColor.value,
|
|
85
|
+
symbol: clusterSymbol(),
|
|
86
|
+
showCount: clusterShowCount.value,
|
|
87
|
+
sizeScale: clusterSizeScale.value
|
|
88
|
+
}
|
|
67
89
|
: false
|
|
68
90
|
|
|
69
91
|
const buildOptions = () => ({
|
|
@@ -209,6 +231,31 @@ onBeforeUnmount(() => {
|
|
|
209
231
|
</select>
|
|
210
232
|
</label>
|
|
211
233
|
</div>
|
|
234
|
+
<div class='dv-row'>
|
|
235
|
+
<label>填充色
|
|
236
|
+
<input v-model='clusterColor' data-demo='cluster-color' type='color' @change='applyCluster' />
|
|
237
|
+
</label>
|
|
238
|
+
<label>文字色
|
|
239
|
+
<input v-model='clusterTextColor' data-demo='cluster-text-color' type='color' @change='applyCluster' />
|
|
240
|
+
</label>
|
|
241
|
+
</div>
|
|
242
|
+
<div class='dv-row'>
|
|
243
|
+
<label>图标
|
|
244
|
+
<select v-model='clusterIcon' data-demo='cluster-icon' @change='applyCluster'>
|
|
245
|
+
<option value='circle'>圆形</option>
|
|
246
|
+
<option value='square'>方形</option>
|
|
247
|
+
<option value='star'>星标</option>
|
|
248
|
+
<option value='pin'>定位钉</option>
|
|
249
|
+
</select>
|
|
250
|
+
</label>
|
|
251
|
+
<label>尺寸
|
|
252
|
+
<input v-model.number='clusterSizeScale' data-demo='cluster-size-scale' type='range' min='0.5' max='2' step='0.1' @change='applyCluster' />
|
|
253
|
+
<span class='cl-value'>{{ clusterSizeScale }}×</span>
|
|
254
|
+
</label>
|
|
255
|
+
</div>
|
|
256
|
+
<div class='dv-row'>
|
|
257
|
+
<label><input v-model='clusterShowCount' data-demo='cluster-show-count' type='checkbox' @change='applyCluster' /> 显示数量</label>
|
|
258
|
+
</div>
|
|
212
259
|
<p class='dv-hint'>半径是屏幕像素;maxZoom 是相机比例相对基准比例的倍数,超过后展开原始点。聚合期间发散初始化暂停,缩放 / 数据 / 选中变化才重算。</p>
|
|
213
260
|
</div>
|
|
214
261
|
<div class='dv-group'>
|