gis-view-webgl2 0.1.4 → 0.1.6
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/README.md +10 -7
- package/dist/index.cjs +1 -1
- package/dist/index.d.ts +225 -20
- package/dist/index.js +1 -1
- package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +109 -0
- package/docs/GisView.md +53 -53
- 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 +91 -0
- package/docs/demos/ConnectDemo.vue +114 -3
- package/docs/demos/MinimapDemo.vue +216 -17
- package/docs/demos/SearchFilterDemo.vue +113 -1
- package/docs/demos/api-index.js +28 -20
- package/docs/demos/demos.js +1 -1
- package/docs/demos/gis-view-demo/GisViewDemoStage.vue +25 -16
- package/docs/demos/gis-view-demo/useGisViewDemo.js +27 -16
- package/docs/index.html +3 -3
- package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +8 -5
- package/docs/internals/16-01-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/212.md +18 -5
- 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 -1
- package/docs/internals/18-/346/200/247/350/203/275/345/256/236/346/265/213/344/270/216WebGPU/350/257/204/344/274/260.md +73 -1
- package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +14 -5
- package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +2 -2
- package/docs/manual/05-/347/224/273/350/264/250/344/270/216/346/211/271/351/207/217/346/233/264/346/226/260.md +1 -1
- package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +1 -1
- 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 +423 -3
- package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +99 -30
- package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +12 -7
- package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +14 -6
- package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +137 -6
- 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 -0
- package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +8 -3
- 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 +6 -3
- package/docs/steps/README.md +6 -6
- package/docs/steps/selftest-demos.html +252 -9
- package/docs/steps/selftest-docs.html +3 -3
- package/docs/steps/selftest-full.html +357 -13
- package/docs/steps/selftest-perf.html +599 -382
- package/package.json +1 -1
|
@@ -181,6 +181,16 @@ view.focusOn({ type: 'group', groupId: 'global-group' }) // groupId 全局唯一
|
|
|
181
181
|
view.focusOn([120, 80], { center: true }) // 显式只居中(与缺省一致)
|
|
182
182
|
view.focusOn({ lngLat: [116.4, 39.9] })
|
|
183
183
|
|
|
184
|
+
// flyTo(target, options?):与 focusOn 同一套目标解析,平滑飞过去
|
|
185
|
+
view.flyTo(hit, { zoom: true, duration: 600, easing: 'ease-in-out' })
|
|
186
|
+
view.flyTo(['station-1', 'station-2'], { select: true, duration: 0 }) // 立即落位
|
|
187
|
+
view.flyTo({ center: [120, 80], scale: 12, easing: progress => progress * progress })
|
|
188
|
+
|
|
189
|
+
// flyToCamera(camera, options?):直接飞相机目标;没写的 center / scale 沿用当前相机
|
|
190
|
+
view.flyToCamera({ center: [120, 80], scale: 3 }, { duration: 600, easing: 'ease-out' })
|
|
191
|
+
view.flyToCamera({ scale: 5 }, { duration: 0 }) // 中心不动,比例立即落到 5
|
|
192
|
+
view.flyToCamera({ center: [116.4, 39.9] }) // 比例沿用当前相机
|
|
193
|
+
|
|
184
194
|
// search(input) / searchAsync(input):搜索(只返回结果、不动视角)
|
|
185
195
|
const hits = view.search('甲站')
|
|
186
196
|
const scoped = view.search({ keyword: '甲', scope: ['points', 'channels', 'groups'], limit: 10 })
|
|
@@ -194,6 +204,10 @@ view.setCamera({ scale: 10 })
|
|
|
194
204
|
view.setCamera({ x: 200, y: 120 })
|
|
195
205
|
const camera = view.getCamera()
|
|
196
206
|
|
|
207
|
+
// stopCameraAnimation() / getCameraAnimation():停止飞行 / 读当前飞行读数(没有动画时为 null)
|
|
208
|
+
const flight = view.getCameraAnimation() // { active, progress, duration, from, to }
|
|
209
|
+
view.stopCameraAnimation() // 停在当前帧,不补跳到终点
|
|
210
|
+
|
|
197
211
|
// getZoomBaseScale() / setZoomBaseScale(scale?):缩放联动的基准比例(配合 pointZoomScale / lineZoomScale)
|
|
198
212
|
const zoomBase = view.getZoomBaseScale() // 基准(缺省首次适应视图时的 camera.scale)
|
|
199
213
|
view.setZoomBaseScale() // 以当下这一屏为「原样大小」,之后放大缩小都从它算倍数
|
|
@@ -207,6 +221,9 @@ const { cssWidth, cssHeight, pixelRatio } = view.getViewport()
|
|
|
207
221
|
// isLayoutReady():容器量到尺寸、首批布局完成没有
|
|
208
222
|
const layoutReady = view.isLayoutReady()
|
|
209
223
|
|
|
224
|
+
// getMinimapElement():拿鹰眼图根节点做整图拖动 / 自定义手势(没配 minimap 时为 null)
|
|
225
|
+
const minimapElement = view.getMinimapElement()
|
|
226
|
+
|
|
210
227
|
// getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放
|
|
211
228
|
view.setMinimapZoom(4)
|
|
212
229
|
view.zoomMinimapBy(1.25)
|
|
@@ -305,6 +322,9 @@ const channelInfo = view.getChannel('c1')
|
|
|
305
322
|
// getPointStyle(index):节点解析后的样式快照(样式链定下来的那一份)
|
|
306
323
|
const pointStyle = view.getPointStyle(0)
|
|
307
324
|
|
|
325
|
+
// getLineStyle(index):线路解析后的样式快照(原始线宽 / RGBA / 透明度 / 层级)
|
|
326
|
+
const lineStyle = view.getLineStyle(0) // { index, width, color, opacity, zIndex } 或 null
|
|
327
|
+
|
|
308
328
|
// listPoints(options?) / listLines(options?):按条件列一批
|
|
309
329
|
const listedPoints = view.listPoints({ limit: 5, withScreen: true })
|
|
310
330
|
const listedLines = view.listLines({ inView: true, limit: 200 })
|
|
@@ -538,21 +558,130 @@ view.runChannelAction('split', { lineId: 'l2' })
|
|
|
538
558
|
view.addChannelGroupWaypoint('c1', 'c1::2', [200, 120])
|
|
539
559
|
view.removeChannelGroupWaypoint('c1', 'c1::2', 1)
|
|
540
560
|
|
|
541
|
-
// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)
|
|
561
|
+
// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)
|
|
562
|
+
// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()
|
|
542
563
|
view.beginAddChannel({
|
|
543
564
|
kind: 'channel',
|
|
544
565
|
circuits: 3,
|
|
545
566
|
channelDraftStyle: {
|
|
546
567
|
color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],
|
|
547
|
-
circuitSpacing: 12, opacity: 0.9
|
|
568
|
+
circuitSpacing: 12, opacity: 0.9,
|
|
569
|
+
// 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖
|
|
570
|
+
dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },
|
|
571
|
+
layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后
|
|
548
572
|
}
|
|
549
573
|
}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线
|
|
550
|
-
view.beginAddChannel({ from: 'station-a' }) //
|
|
574
|
+
view.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)
|
|
551
575
|
const draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)
|
|
552
576
|
view.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头
|
|
553
577
|
view.updateAddChannelStyle(null) // 回到全局 channelDraftStyle
|
|
554
|
-
|
|
555
|
-
|
|
578
|
+
|
|
579
|
+
// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。
|
|
580
|
+
// 下面每个 commit 例子都假设当前草稿已经重新 ready;实际连续调用请逐段 begin / 点选。
|
|
581
|
+
// 五类入参:无参、旧 resolution、直接线路、直接通道(必须有 lineIds)、容器(channel / line / lines / ...)。
|
|
582
|
+
// 1. 无参:沿用草稿的 preferred(newChannel / newGroup / joinGroup / newLine)
|
|
583
|
+
view.commitAddChannel()
|
|
584
|
+
|
|
585
|
+
// 2. 旧 resolution:字符串或只含 type / groupId / groupIndex / channelId 的对象
|
|
586
|
+
view.commitAddChannel('newChannel')
|
|
587
|
+
view.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2', groupIndex: 1, channelId: 'c1' })
|
|
588
|
+
|
|
589
|
+
// 3. 从 ready 的方案读数里选一条:只取四个定位字段,不能把整条 option 回传。
|
|
590
|
+
// 整条 option 还带 enabled / count / lineCount / lineIds,可能被误识别成直接通道或容器。
|
|
591
|
+
view.updateOptions({
|
|
592
|
+
onChannelDraft: payload => {
|
|
593
|
+
if (payload.phase !== 'ready' || payload.blocked) return
|
|
594
|
+
const chosen = payload.options.find(option => option.enabled && option.type === 'joinGroup')
|
|
595
|
+
if (!chosen) return
|
|
596
|
+
const result = view.commitAddChannel({
|
|
597
|
+
type: chosen.type,
|
|
598
|
+
groupId: chosen.groupId,
|
|
599
|
+
groupIndex: chosen.groupIndex,
|
|
600
|
+
channelId: chosen.channelId
|
|
601
|
+
})
|
|
602
|
+
console.log(result.ok, result.reason, result.lineIds)
|
|
603
|
+
}
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
// 4. 直接线路对象:没有 lineIds,但至少写 from / to / path / kind: 'line' 之一。
|
|
607
|
+
// 业务字段和 lineProps 都透传到新线路;path 是中间拐点(不含首尾)。
|
|
608
|
+
view.commitAddChannel({
|
|
609
|
+
id: 'new-line',
|
|
610
|
+
name: '对象线路',
|
|
611
|
+
from: 'station-a',
|
|
612
|
+
to: 'station-b',
|
|
613
|
+
path: [[116.72, 39.91], [116.86, 39.95]],
|
|
614
|
+
color: '#ff7a45', // 直接业务字段 → lineProps
|
|
615
|
+
width: 3,
|
|
616
|
+
lineType: 'dashed',
|
|
617
|
+
voltageLevel: '10kV',
|
|
618
|
+
lineProps: { opacity: 0.9 } // 合并顺序:业务字段 → 自己的 lineProps
|
|
619
|
+
})
|
|
620
|
+
|
|
621
|
+
// 5. 直接通道对象:必须写 lineIds;每个 id 生成一条回线,groups 是分组声明。
|
|
622
|
+
// id 只决定通道 id,不是回线 id 种子;并进已有通道要改用容器 + resolution。
|
|
623
|
+
view.commitAddChannel({
|
|
624
|
+
id: 'new-channel', name: '对象通道', from: 'station-a', to: 'station-b',
|
|
625
|
+
lineIds: ['new-channel-1', 'new-channel-2'],
|
|
626
|
+
groups: [
|
|
627
|
+
{ id: 'new-group-1', lineIds: ['new-channel-1'], path: [[116.75, 39.86]] },
|
|
628
|
+
{ id: 'new-group-2', lineIds: ['new-channel-2'], path: [[116.75, 39.98]] }
|
|
629
|
+
],
|
|
630
|
+
width: 28,
|
|
631
|
+
lineProps: { color: '#2f6fd0', lineType: 'dashed' },
|
|
632
|
+
owner: '运维一班' // 其余字段 → channelProps
|
|
633
|
+
})
|
|
634
|
+
|
|
635
|
+
// 6. 容器写法:一段一段覆盖草稿,没写的继续用草稿。
|
|
636
|
+
// lines / lineData / lineItems 同义,优先级 lines > lineData > lineItems,给了几条就建几条。
|
|
637
|
+
view.commitAddChannel({
|
|
638
|
+
resolution: 'newChannel',
|
|
639
|
+
from: 'station-a',
|
|
640
|
+
to: 'station-b',
|
|
641
|
+
channel: { id: 'container-channel', name: '容器通道', nodeWidth: 1 },
|
|
642
|
+
lines: [
|
|
643
|
+
{ id: 'n-1', color: '#dd4b2e', width: 2.2, name: '一回' },
|
|
644
|
+
'n-2',
|
|
645
|
+
{ id: 'n-3', color: '#2ea36b', voltageLevel: '10kV' }
|
|
646
|
+
],
|
|
647
|
+
channelGroups: [
|
|
648
|
+
{ id: 'container-channel::1', lineIds: ['n-1', 'n-2'] },
|
|
649
|
+
{ id: 'container-channel::2', lineIds: ['n-3'], path: [[116.78, 39.96]] }
|
|
650
|
+
],
|
|
651
|
+
routing: 'orthogonal', // 只有容器这一层的 routing 会改走线
|
|
652
|
+
path: [[116.78, 39.9]], // 中间拐点,不含两端
|
|
653
|
+
lineProps: { opacity: 0.9 }, // 容器合并:草稿 → 顶层 → line.lineProps → 逐条
|
|
654
|
+
channelProps: { width: 36 } // 最后覆盖新通道字段
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
// 7. 只给 channel.lineIds 当种子:没有 lines / line / lineData / lineItems 时,每个 id 建一条回线
|
|
658
|
+
view.commitAddChannel({
|
|
659
|
+
channel: { id: 'seed-channel', from: 'station-a', to: 'station-b', lineIds: ['seed-1', 'seed-2'] },
|
|
660
|
+
lineProps: { color: '#2f6fd0', width: 2 }
|
|
661
|
+
})
|
|
662
|
+
|
|
663
|
+
// 8. 空数组不覆盖草稿:lines / lineData / lineItems 给 [] 时,沿用草稿的 lineItems / circuits
|
|
664
|
+
view.commitAddChannel({ channel: { id: 'draft-lines' }, lines: [] })
|
|
665
|
+
|
|
666
|
+
// 9. 覆盖端点 / id / 名称 / 路由:顶层字段优先,lineId 是多回时自动展开的 id 种子
|
|
667
|
+
view.commitAddChannel({
|
|
668
|
+
from: 'station-c', to: 'station-d', name: '改道联络线',
|
|
669
|
+
lineId: 'rerouted', lines: [{ id: 'rerouted-1' }, { id: 'rerouted-2' }],
|
|
670
|
+
routing: 'orthogonal', path: [[116.9, 39.9]]
|
|
671
|
+
})
|
|
672
|
+
|
|
673
|
+
// 10. 并进已有通道 / 分组:直接通道默认新建,所以用容器并显式写 resolution
|
|
674
|
+
view.commitAddChannel({
|
|
675
|
+
resolution: { type: 'joinGroup', groupId: 'c1::2' },
|
|
676
|
+
lines: [{ id: 'join-1', color: '#dd4b2e' }, { id: 'join-2', color: '#2ea36b' }],
|
|
677
|
+
lineProps: { width: 2.4 }
|
|
678
|
+
})
|
|
679
|
+
|
|
680
|
+
// 11. 第二参数只管本次收尾:continuous: false 强制结束;originalEvent 原样带回重开的草稿
|
|
681
|
+
view.commitAddChannel(undefined, { continuous: false, originalEvent: null })
|
|
682
|
+
const commitResult = view.commitAddChannel()
|
|
683
|
+
console.log(commitResult.ok, commitResult.kind, commitResult.lineIds, commitResult.channelLineIds)
|
|
684
|
+
|
|
556
685
|
view.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)
|
|
557
686
|
view.cancelAddChannel() // 取消(Esc / 右键等价)
|
|
558
687
|
|
|
@@ -950,10 +1079,12 @@ offMod() // 注册时返回的注
|
|
|
950
1079
|
#### 属性
|
|
951
1080
|
|
|
952
1081
|
```js
|
|
953
|
-
// el / canvas / gl
|
|
1082
|
+
// el / canvas / gl / cameraAnimating / dataVersion:容器、画布、WebGL2 上下文、相机是否在飞行、数据修订号
|
|
954
1083
|
const host = view.el
|
|
955
1084
|
const canvas = view.canvas
|
|
956
1085
|
const gl = view.gl
|
|
1086
|
+
const cameraAnimating = view.cameraAnimating
|
|
1087
|
+
const dataVersion = view.dataVersion // 数据变更 +1;只改全局样式 / 配置不变
|
|
957
1088
|
```
|
|
958
1089
|
|
|
959
1090
|
---
|
|
@@ -13,6 +13,8 @@ const view = createGisView(el, { data, ... })
|
|
|
13
13
|
view.getPoint('station-1') // { index, item, x, y, radius, layerRank, … }
|
|
14
14
|
view.getLine(0) // { index, item, path, bounds, color, width, channelId?, … }
|
|
15
15
|
view.getChannel('ch-1') // { index, item, id, lineIndexes, skeleton, offsets, paths, context }
|
|
16
|
+
view.getPointStyle(0) // { index, size, symbol, zIndex }:样式链解析后的节点快照
|
|
17
|
+
view.getLineStyle(0) // { index, width, color, opacity, zIndex }:线路快照(原始线宽,不含整体倍率)
|
|
16
18
|
|
|
17
19
|
// 一批要素:按条件列,列表项自带你需要的几何
|
|
18
20
|
view.listPoints({ limit: 5, withScreen: true }) // 前 5 个可见节点 + 屏幕坐标
|
|
@@ -36,6 +38,8 @@ canvas.addEventListener('contextmenu', (event) => {
|
|
|
36
38
|
|
|
37
39
|
`listPoints()` 用的就是引擎画点那一份可见判定(旧版单独暴露的 `getVisiblePoints()` 并进了这一条):`radius` / 图层号 / 屏幕坐标这些几何读数都在它给的条目里。`focusOn()` 只移动相机、不改数据:缺省(以及显式 `{ center: true }`)只居中、比例不动,`{ zoom: true }` 才按目标范围适配,`{ select: true }` 顺带把这批选上,传一批目标(数组)就是整体居中。
|
|
38
40
|
|
|
41
|
+
`getPointStyle()` / `getLineStyle()` 给的是**当前解析结果**:节点看尺寸 / 图标 / 层级,线路看原始线宽 / RGBA / 透明度 / 层级;悬停 / 选中样式、整体倍率、热力倍率都不算在里面。逐条自己写死的字段优先于规则与全局样式,所以用它们对账「预览里多大、落地后多大」或者鹰眼图的小图尺寸时,不需要自己重跑样式链。
|
|
42
|
+
|
|
39
43
|
只要「屏幕里这一片」的节点时用 `forEachPointInView()`:它按数据顺序逐个回调 `(index, item, x, y, radius)`,不建中间数组,回调返回 `false` 可以提前结束 —— 放大看一小片时不会去碰整份数据(十万级数据下,面板读数、导出与排序用上面那两条,按视口画自己的东西用这一条):
|
|
40
44
|
|
|
41
45
|
```js
|
package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md
CHANGED
|
@@ -46,7 +46,7 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
|
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
- **配置**:写 `true` 用缺省档(半径 60px、至少 2 点、不限缩放);`radius` 夹在 8 ~ 500 CSS 像素,`minPoints` 最小 2,`maxZoom: 0` 表示不限。`maxZoom` 以 `zoomBaseScale`(或首次适应视图比例)为基准,相机比例超过它之后 `active` 变 `false`,原始点逐点展开。
|
|
49
|
-
- **算法开销**:`cluster.js` 用固定网格把邻近点串成组,每个点只查相邻 9 格,单次建组按总量 O(n);成员用 `Int32Array`
|
|
49
|
+
- **算法开销**:`cluster.js` 用固定网格把邻近点串成组,每个点只查相邻 9 格,单次建组按总量 O(n);成员用 `Int32Array` 链表保存,稀疏场景不为每个单成员常驻组对象。平移不重算聚合;缩放只在聚合启用且当前档有成员要重算 / 收掉时标脏,关闭聚合或当前缩放档本来就不聚合时不会触发整份场景重排。数据、配置、选中集、拖动、合并与复制预览变化仍立即标脏。相机缩放引起的成员变化在运动期先沿用上一档,停稳后自动重算一次。
|
|
50
50
|
- **绘制与拾取**:聚合圆单独一批绘制,原始点批次过滤掉被收起的成员;标签、涟漪、节点装饰、框选、最近点查询与 `listPoints({ visibleOnly: true })` 都按同一份隐藏集合跳过内部点。`getClusters()` 为需要全量展示组的场景补上单成员,绘制与数量文字只遍历多成员圆。
|
|
51
51
|
- **隔离规则**:选中点、拖动成员、待确认合并的源 / 目标以及复制预览强制保持单成员,避免用户点不到正在操作的点;聚合开启时发散初始化与追加式补点暂停,保证成员关系按整份数据一次算清。
|
|
52
52
|
- **和 denseMode 的关系**:两者可以同时开。`denseMode` 管标签避让 / 截断与动画降级,`pointCluster` 管点本身的展示密度;只希望更快但仍要看每个符号时可关掉聚合,只保留密集档。十万点档在 `?demo=cluster` 可现场切 2 千 / 1.2 万 / 6 万 / 10 万,并观察聚合耗时与展示项数。
|
|
@@ -59,7 +59,8 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
|
|
|
59
59
|
- 拾取与框选共用网格索引:点走按数据范围重建的均匀网格,线走随线段重建的 `createSegmentGrid`,每次交互只遍历光标附近的候选,耗时与总量基本脱钩,`getStats().pickMs` 是随手可读的实测值(10 万规模下热态约 0.2 ~ 0.3 ms / 次)。线段按格线步进登记进它真正穿过的格子(早期按包围盒填格,一条斜跨全图的线会占掉上万格、只好整条丢进「超长线」单,而十万条线里绝大多数都是这种,于是每次拾取都要重算十万条的距离);剩下的长线只记一条包围盒,查询时按盒子先筛一遍。10 万点 / 10 万线的合成数据里(随机配对、长线极密),500 次 `pick` 从 3530 ms 降到 164 ms(0.3 ms / 次)。
|
|
60
60
|
- 拖动时「哪些线跟着这个点走」按需建索引:`collectDragLinks` 从前每次都把全部线路的端点与组内成员逐个比一遍,10 万点 / 12 万线下这一步占总 CPU 的 27%,是拖动路径上唯一还按总量线性付费的循环。现在线路超过 `LINE_BIND_TOLERANCE_SCAN_LIMIT`(2048)条时改用一张线路归属表(按 `from` / `to` 的 id 与端点坐标建,整份换数据 / 增删线 / 改绑定才重建,只改坐标就地把那一条换掉),只对命中的候选逐字段判定 —— 十万级拖动降到 CPU 的 1% 以下,单条 `updatePoint`(改名这类不动几何的改动)55 → 3.3 ms(稳态 0.02 ms)。线路不多时保留原来的浮点容差扫描(行为与从前逐字不差);索引那条路只认 id 绑定与坐标精确相等 —— 这个量级坐标都是同一份来源算出来的,容差是给手写数据留的余量。
|
|
61
61
|
- 视口里的线路改走线段网格:线路标签排版这类「只看屏幕里这一片」的路径(`forEachLineInView`)原先要把每条线的包围盒过一遍,10 万级 + 标签的 CPU profile 里它占 47 ~ 58%。现在线路超过 `LINE_VIEW_GRID_MIN_LINES`(2048)条、且估计出来的候选线段只占总数的 1/32 以内(`LINE_VIEW_GRID_COST_SHARE`)时改查拾取用的那张线段网格,再逐条按包围盒确认;候选排回要素下标顺序,脏线与没有线段的退化线单独补一遍,访问顺序与判定口径与整份扫描逐行相同。网格脏着(正在拖线 / 改线)或线路不多时照旧整份扫描。
|
|
62
|
-
- 视口里的节点遍历也先查点网格:`forEachPointInView` 在节点超过 `POINT_VIEW_GRID_MIN_POINTS`(2048)、点网格不脏且估计候选不足总数的 1/32(`POINT_VIEW_GRID_COST_SHARE`)时,只查视口覆盖的格子、把候选排回数据顺序再逐个精确判定;网格脏着(刚改过数据 / 拖拽中)或视口几乎铺满时仍走整份扫描,避免为了省一趟遍历先付出建网格和排序的成本。`createPointGrid.estimate(..., stopAfter)` 与查询共用同一套格子口径,传 `stopAfter` 时一旦超过阈值立即返回,只判断候选是否过密时不必扫完视口里的全部格子;框选线路则复用已有的 `forEachLineInView`,不再为每条候选线复制路径 / 包围盒 / 颜色快照。
|
|
62
|
+
- 视口里的节点遍历也先查点网格:`forEachPointInView` 在节点超过 `POINT_VIEW_GRID_MIN_POINTS`(2048)、点网格不脏且估计候选不足总数的 1/32(`POINT_VIEW_GRID_COST_SHARE`)时,只查视口覆盖的格子、把候选排回数据顺序再逐个精确判定;网格脏着(刚改过数据 / 拖拽中)或视口几乎铺满时仍走整份扫描,避免为了省一趟遍历先付出建网格和排序的成本。`createPointGrid.estimate(..., stopAfter)` 与查询共用同一套格子口径,传 `stopAfter` 时一旦超过阈值立即返回,只判断候选是否过密时不必扫完视口里的全部格子;框选线路则复用已有的 `forEachLineInView`,不再为每条候选线复制路径 / 包围盒 / 颜色快照。
|
|
63
|
+
- 数据包围盒读数改为缓存:整份解析本来已经顺路算出点与线的范围,`getBounds()` 直接接住这一份;定向改点 / 改线、拖拽、追加要素与复制预览只把缓存标脏,下一次真被问到时才用点坐标和每条线的 `line.bounds` 重建,不再逐顶点重扫。`getBounds()` 每次返回独立副本,调用方就地修改不会污染下一次读数;追加到原范围之外、拖出边界这类增量也会立即失效。10 万点 + 10 万线(每条 10 顶点)实测连续 10 次 `getBounds()`:优化前 **5.2 ~ 6.6 ms / 次**,优化后 **0.00 ms / 次**;改一个点后缓存重建一次 **3.6 ms**,读数正确。
|
|
63
64
|
- 悬停、选中、拖拽只做增量更新:选中集合用点 / 线两张下标 `Set` 维护,样式变化按要素下标重新解析,顶点与实例数据就地改写后按「实例槽位」上传,不重建整批缓冲。
|
|
64
65
|
- 状态样式就地更新:悬停 / 选中样式只改颜色、大小、描边时按实例槽位覆盖,不重建整批;符号换成 / 换出贴图、阴影条数变化这类批次结构变化才回退整批重排。哪些要素当前真的按状态样式画过另有一份记账(`stateStyledSignatures`),整体重建后立刻重算,取消选中时按它精确还原本体样式,不会留下残留。
|
|
65
66
|
- 动效只维护真正的动画线路:`lineEffectRuns` 让每帧的动点循环只遍历配了 `effect` 的线路,路径就地变化时只重算这一条的总长与包围盒;普通线路再多,也不会进入动画的逐帧扫描。
|
|
@@ -70,7 +71,8 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
|
|
|
70
71
|
- 配置变更走一次合并:`updateOptions({ points, lines, channels, background, ... })` 只在需要时重建场景;demo 里的通道宽度、内收距离用 `v-model.lazy` 提交,避免拖动滑块时每帧重排。
|
|
71
72
|
- 配置变更走一次快速通道:`updateOptions` 先按 `pickEngineOptions` 的键表挑一遍「引擎真的会收到的那几项」——纯标签 / 提示 / 根样式(`label` / `lineLabel` / `tooltip` / `minimap` 里的样式与规则 / `ariaLabel`)一个字节都不发给引擎,只把覆盖层重刷一遍;小图配置变更另外作废组件这一层的小图数据缓存并重画(那份数据已经按 `levels` / `cascade` 过滤过,引擎不变它也不会自己更新);光标、网格、交互开关、快捷键、回调、尺寸倍率这些「只影响怎么画」的字段只发这几项、引擎标一帧脏;状态样式(`pointHoverStyle` / `pointSelectStyle` / `lineHoverStyle` …)与 `defaultHighlight` 走「只重解析在悬停 / 选中里的那几个要素」那条路,不必整份重排。实测(10 万点 / 10 万线,`updateOptions` 连调两次取第二次):空补丁、`label`、`lineLabel`、`tooltip`、`ariaLabel`、`statsInterval`、`initialView`、`pointSelectStyle`、`defaultHighlight`、`selectionBoxStyle` 全部从 82 ~ 120 ms 降到 0 ms,同一批用例在「快速通道」与「强制整份重排」两条路上的截图逐像素相同(8 个用例的 mean / max / 变化比例全为 0)。改配置频繁的面板(读数间隔、光标、标签文案、状态样式)现在可以放心连着拨;`pointStyle` / `lineStyle` / `pointRules` / `lineRules` 这类样式补丁这一轮也进了快路径(109 ~ 116 ms → 40 ~ 55 ms),见下一条。
|
|
72
73
|
- 样式类配置也走这条路:`pointStyle` / `lineStyle` / `pointRules` / `lineRules` / `heatmap` 这些「只换外观、不动几何」的补丁,引擎按 `STYLE_PATCH_KEYS` 认出这一类,逐要素按新配置重解析后直接覆盖到实例槽位上,不再重建整份场景 —— 数据包围盒、背景、通道骨架与装饰锚点都跟样式无关,一律不重算(只换样式时装饰锚点整趟不重定位,线多、装饰多时这一笔最大);只有「结构真的变了」才回退整份重排(符号换成 / 换出贴图、阴影条数变化、`zIndex` 层级变化、虚线图案变化)。面板上的常见写法是「基准样式 + 悬停 / 选中样式 + 尺寸倍率」一起下发,判据因此放宽成「至少一项样式键,其余是尺寸倍率 / 画法开关 / 状态样式 / 回调」;引擎另外记一份「这一轮重解析用的输入」,同一份配置原样再发一次只标一帧脏,只动一半时另一半的十万个要素一个都不碰。实测 10 万点 / 10 万线:`pointStyle` 从 109 ~ 116 ms 降到 40 ~ 55 ms(本轮再降到 16.8 ms),原样重发 48.2 → **0.1 ms**、混合补丁 120.6 → 35 ms、换热力图色带 241.9 → 90.1 ms;两条路画出来的截图逐像素相同(9 个用例的 mean / max / 变化比例全为 0),快照逐字段一致另有 `selftest-perf` 的 10 条断言兜着;`heatmap` 配置是按引用存的,只要补丁里出现它就按「变过」记、照旧按当前数据重算一遍(自动值域跟着动)。
|
|
73
|
-
- 样式窄路再收窄一层(尺寸链 / 配色链):面板上拖「节点大小」滑块、换调色板、拖透明度下发的是「只改了 `pointStyle` 里某一类字段」的补丁,引擎按一张字段表(`size` / `sizeScale` / `color` / `strokeColor` / `strokeWidth` / `opacity`)认出尺寸链与配色链,逐要素只重算真的换了的那条链,就地写回上一轮的解析结果(规则匹配、热力取值、阴影与效果归一化、那份 15 个字段的结果对象一概不跑);逐点自己写了这一项(或者状态样式盖住了这一层)的要素算出来与上一轮相同,**这一条直接跳过,连实例槽位都不重写** —— 「一百万个点各自带着颜色、只换基准色」这类场景因此几乎只剩一趟判断。两条链在一次补丁里都变也认(先配色后尺寸,中间那一份不记进预热账本)。实测(100 万点,`docs/steps/bench-scale.html` 的 `styleColor` / `styleToggle`):只换基准色 **首轮 242.8 → 137.8 ms、稳态 156.1 ~ 203.1 → 91.1 ~ 127.8 ms**;只换基准尺寸(每条数据自己写了 `size`、一个要素都不用重算)**95.6 ~ 117.1 → 17.1 ~ 21.2 ms**(过程见实现原理 15.54)。贴图符号(SVG / 图片)整份退回原来的路(尺寸会换烘焙档位、颜色可能进烘焙键),画面与整份重建逐字段一致由 `selftest-perf` 的断言兜着(逐要素重解析之后不再回落整份重排:换配色的贴图就地把实例搬进新配方那一组,见《实现原理》15.91 / 16.42
|
|
74
|
+
- 样式窄路再收窄一层(尺寸链 / 配色链):面板上拖「节点大小」滑块、换调色板、拖透明度下发的是「只改了 `pointStyle` 里某一类字段」的补丁,引擎按一张字段表(`size` / `sizeScale` / `color` / `strokeColor` / `strokeWidth` / `opacity`)认出尺寸链与配色链,逐要素只重算真的换了的那条链,就地写回上一轮的解析结果(规则匹配、热力取值、阴影与效果归一化、那份 15 个字段的结果对象一概不跑);逐点自己写了这一项(或者状态样式盖住了这一层)的要素算出来与上一轮相同,**这一条直接跳过,连实例槽位都不重写** —— 「一百万个点各自带着颜色、只换基准色」这类场景因此几乎只剩一趟判断。两条链在一次补丁里都变也认(先配色后尺寸,中间那一份不记进预热账本)。实测(100 万点,`docs/steps/bench-scale.html` 的 `styleColor` / `styleToggle`):只换基准色 **首轮 242.8 → 137.8 ms、稳态 156.1 ~ 203.1 → 91.1 ~ 127.8 ms**;只换基准尺寸(每条数据自己写了 `size`、一个要素都不用重算)**95.6 ~ 117.1 → 17.1 ~ 21.2 ms**(过程见实现原理 15.54)。贴图符号(SVG / 图片)整份退回原来的路(尺寸会换烘焙档位、颜色可能进烘焙键),画面与整份重建逐字段一致由 `selftest-perf` 的断言兜着(逐要素重解析之后不再回落整份重排:换配色的贴图就地把实例搬进新配方那一组,见《实现原理》15.91 / 16.42)。
|
|
75
|
+
- 线路侧的同一条窄路:`updateLineStyle` / `updateOptions({ lineStyle })` 只改 `width` / `color` / `opacity` 时不重跑线路规则、热力、虚线、阴影、动点与方向箭头,逐条只重算「基准宽 / 半宽 / 颜色 / 透明度」再按槽位补实例;逐线自己写死的字段仍然优先,存在线路规则或热力图时自动回退整份解析。1000 点 + 50000 线连续 5 次交替:换色稳态 **10.1 ~ 11.7 ms → 6.1 ~ 7.4 ms**、换宽 **9.2 ~ 10.4 ms → 5.8 ~ 6.4 ms**(均约 40%),首轮 18.9 / 17.3 ms;profile 从 `applyResolvedStyles` + `refreshLine` 转到 `applyLineNarrowFast`、`patchLineSlotsInner` 与 `resolveLinePaintInto`,两边共用同一份取值链。读最新解析结果用 `getLineStyle(index)`;这条快路不动数据,`dataVersion` 不变。
|
|
74
76
|
- 筛选放进 Web Worker:点 + 线总数超过 `filterWorker.threshold`(默认 20000)且条件里没有函数时,判定在 Worker 里跑,主线程只应用隐藏下标;字段投影带缓存(数据与条件字段没变就复用,单条改动只补那一条),条件变化只传条件本身,10 万级数据下连续切换条件也不会卡住交互。
|
|
75
77
|
- 搜索带文本缓存:每个要素的「所有可搜索字段的小写拼接串」按对象缓存在 `WeakMap` 里(`createSearchCache`),先用它排除肯定不命中的要素,可能命中的那几个才摊平字段算出「命中哪个字段、原文是什么」,载荷与逐字扫描时完全一致。10 万点 / 10 万线默认全字段搜索:改造前每次 30 ms 上下,现在第一次约 80 ms(这一遍顺带把 10 万个要素的拼接串建进缓存)、之后 15 ms 左右,不命中的关键词从 30 ms 降到 9 ms 左右 —— 换来的是「边输边搜」这种连搜场景。
|
|
76
78
|
- 批量规则与标签都是「按需字段」:规则只读条件里出现的字段;标签只对当前视口内的可见点建 DOM,节点出入视口时 DOM 回收进池子复用。缺省(`labelOptimize: false`)是「强制显示」档:视口里该出现的标签一条不少,密集场景画多少条由数据自己决定 —— 十万级数据要显式写 `labelOptimize: true` 打开优化档,按 `maxCount` 截断、按候选上限 `label.scanLimit`(缺省按 `maxCount × 8` 估算、不低于 256,`0` 表示不限)收候选:为了几十个标签把整屏节点都写成 DOM 再量一遍才是真正的开销,扫够视口内的节点就停。实测这一项让「开标签 vs 关标签」的帧率差从 13 倍降到 1.34 倍(见下节与「标签显示优化开关」的两档对照)。
|
|
@@ -79,6 +81,7 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
|
|
|
79
81
|
- 线路补丁「散到一定程度就整批重写」:回线在缓冲里是一条通道一段、段与段之间还夹着别的线路,一次要改的线一多,攒批就退化成几百次小上传。现在先用 `estimateLinePatchRuns` 估一次这次改动会切成多少段(按「槽位跨度 - 改动占用的槽位 + 1」,扫一遍、不排序),段数超过 `LINE_PATCH_REWRITE_RUNS`(48)且改动条数占到全部线路的 1/4 以上时,直接把线路 / 阴影 / 圆头三张缓冲按槽位顺序整份重写、各传一次(`rewriteLineSlots` 会逐条校验槽位表,对不上就退回攒批、由它转整份重排)。实测:2164 点 / 3669 线的真实数据,拖「节点放大」滑块 1 秒,`bufferSubData` 从 30320 次降到 80 次;同一状态下「整批重写」与「逐段攒批」的截图逐像素一致,每步的同步耗时仍是 5 ~ 8 ms。
|
|
80
82
|
- 圆头批次在拖动期间「欠账」:`join: 'round'` 时每个顶点一个圆头,行数比线段还多(10 万条两点折线 = 20 万行 / 12 MB)。一次拖动要改的行数超过 `CAP_DEFER_ROWS`(8192)时这一轮不写、整批圆头先不画,松手后按 `CAP_SYNC_SLICE`(8192 条线)一批分摊到后续几帧补齐 —— 拖动期间圆头一个字节都不传,松手也不再是一帧的卡顿。10 万点 / 10 万线整屏框选后拖动,每帧补丁从 1391 ms 降到 339 ms,其中圆头那部分(8 帧合计 9.8 s)在拖动期间归零。
|
|
81
83
|
- 交互期间自动切轻量渲染:按住拖动 / 滚轮缩放时跳过阴影、强调环、贴图高亮与高亮重算,停手 160 ms 后补一帧全质量;带大选中集合时整帧耗时约减半,画面里的节点与线路位置仍然实时跟随。
|
|
84
|
+
- 程序化相机运动复用同一条轻量渲染路径:`zoomBy` / `setCamera` / `centerOn` / `flyToCamera` 等 API 改相机时,运动帧同样使用细线快路径与非 MSAA 后备缓冲;每次真实位置变化重新计时 160 ms,停稳后自动补一张全质量帧,并刷新通道宽度与恢复符号预烤。同一相机位置重复 `setCamera` 不会续期,避免外部定时同步视角把画面永久钉在轻量档。关闭聚合后连续缩放不再空跑 `layoutScene()` 和整份点实例上传;10 万点 / 5 万线单次实测缩放 p50 从 67.9 ms 降到 22.5 ms,p95 从 76.2 ms 降到 25.1 ms。
|
|
82
85
|
- 标签图层在平移 / 缩放 / 拖点期间跟着相机整体变换,不重排 DOM:标签位置只跟相机与节点坐标有关,整层一次 `translate3d` / `scale` 就能和地图对齐(拖点只挪动过的那几个标签),停手 180 ms 后再补一次真实排版(回收移出视口的、补上新进来的)。十万级数据下这一项把拖动平移从 17.9 FPS 提到 57.9、滚轮缩放从 13.5 提到 57.9、拖动节点从 20.1 提到 55.0,都是「开标签 ≈ 关标签」的水平。
|
|
83
86
|
- 框选抬起时只做必要的工作:候选下标先按数值排好序再逐个造结果,状态差值的去重键用数值(不是字符串),差值用的三个 `Set` 循环复用,圆 / 方符号的解析结果有一层不拼字符串的热缓存。整屏框住约 9.9 万个节点的耗时从 97 ms 降到 52 ms(50% 的框 28 ms、25% 的框 5 ms 上下)。
|
|
84
87
|
- 可见点缓存只在数据或相机真的变化时重建:拖点换了坐标就就地作废,只有整体倍率(`pointSizeScale` 这类)变化时就地按比例缩放缓存的 `radius`,不为一次缩放重建十万个对象。
|
|
@@ -99,6 +102,8 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
|
|
|
99
102
|
- 追加数据只补这几条、标签没变就不排、搜索搬进 Worker:三件都还是同一条思路 —— **把「整份」换成「这一份」、把主线程上的活挪走**。① **追加式补点**:引擎认得出「数据末尾就多了这几条」(`appendPoints`),只解析这几条、只往现成的实例批次里补这几条 —— 实例缓冲按容量记账(够就 `bufferSubData` 只传新段)、形状符号插在圆点那一段末尾并把后面的背景点 / 装饰 / 抬层点整体后移、贴图符号追加到现成那一组、包围盒只扩不缩、背景与背景文字跟着新范围重算;`addPoint` / `addPoints` 因此从「每次整份重建」变成增量(10 万点下逐个 `addPoint` 16.4 → 1.0 ms,第一次 4.6 ms 是整份实例缓冲重传一次),场景结构做不到等价时(新点不在最上面那一层、贴图没有现成的组、通道或线路按 id 认领了它)自己回落整份重建,画出来与整份重建**逐像素相同**(分层 / 贴图 / 阴影三种场景都验过)。② **标签层**多了一道「输入指纹」判据:改动的字段与标签画出来的东西无关时整层重排整个跳过(`overlay.labelInputKey` 把一条要素里影响它那份标签的字段拼成指纹,`updatePoint` / `updateLine` / 批量补丁三处都接),10 万点 + 标签改一条 0.75 → 0.099 ms,改 `name` 这种真会换标签的仍是 0.956 ms。③ **搜索**搬进了 Worker(`search.worker.js` + `search-runner.js` + `view.searchAsync`):数据交给 Worker 一次(改过的按条补、结构变了重新交一份),之后每次搜索只发关键字,命中只回下标与字段、要素与包围盒由主线程按下标补回来,结果按「请求序号 + 数据版本」作废(回来晚了的那一份带着 `stale: true` 交回去,视角不会跟着它动),环境不支持 Worker / 数据量不到 `searchWorker.threshold`(默认 20000)/ Worker 报错时同步回落。10 万点 / 10 万线连着搜 6 次占主线程 43.3 → 0.1 ms(第一次含把数据交给 Worker 的 42.7 ms)。④ 另外 `refresh({ reflow: false })` 只重画一帧、跳过整份重解析(10 万点 / 1 万线 41.0 → 0.1 ms),给「数据没变、就是想重画一遍」的场合一个明确的口子。
|
|
100
103
|
- 标签尺寸按「内容签名」缓存、避让判定不再读布局:整层重排从前要为每条候选 `getBoundingClientRect()` 一次(每读一次就是一次强制重排),根因是 `parseLabelTransform` 的正则从来没匹配上(`0px?` 的写法)—— 量好的盒子每次都被 `writeLabelTransform` 作废。现在:正则修对、解析结果缓存在 `state.placement` 上,尺寸按「画上去的那一份签名」(kind / class / 文字 / 内联样式 / 字体代号)缓存(LRU 8192,与节点身份解耦)、位置从节点自己写下的 `transform` 里解析,避让判定只剩算术,只有签名第一次出现才真量一次;一批里没量过的节点先全部挂进 DOM 再一趟读完(写读分离)。实测 10 万点 / 10 万线、54 条标签下单次定向更新 73.8 → 3.5 ms、建场景 453 → 257 ms;重标签场景(1000 条标签 / 8000 候选)500 ms 上下 → 25 ~ 45 ms。
|
|
101
104
|
- MSAA 与交互期细线:决定十万级交互帧率的是**光栅化的总覆盖量 × MSAA 倍数**,不是条数 —— 微基准(898×518 画布、十二万段、同一份实例数据、真 GPU)里每条线展开成四边形是 192 ms/帧、换成 `gl.LINES` 是 112 ms/帧,关掉 MSAA 之后分别是 38 / 23 ms,而 JS + 命令提交只要 0.01 ms(空页面 7.2 ms、只 clear 7.0 ms)。于是 `antialias` 进选项(软件光栅自动关),交互期新增细线快路径 `interactionThinLines`(线宽 ≤ 3px 且无虚线时改走 `gl.LINES`,停手回到四边形展开;两种画法共用同一张实例缓冲,所以位置不跳),`getStats()` 多出 `antialias` / `software` / `thinLines` 三个读数。实测:同一页交替开关 6 轮(两次独立运行)交互期 p50 194 ~ 208 → 132 ~ 139 ms、同一 2.2 s 窗口帧数 13 → 19 ~ 24;同样 10 万点换成网格邻居线(19.9 万条、线短不叠)拖动 p50 只要 7 ms。再往后一轮把这条账的另一半收掉:交互期与动画期干脆不带 MSAA 画 —— `interactionNoMsaa`(缺省开)让拖动 / 缩放与动画在跑的那些帧先画进一张不带 MSAA 的后备贴图、帧末整幅贴回,停手 / 停帧后那一帧照旧走带 MSAA 的画布(静止画面逐像素不变);真机实测交互期单帧 133 → 36.6 ms、拖动 p50 130 → 16 ~ 22 ms、缩放 p50 239.5 → 16.7 ms、平移 p95 129 → 17 ms、动画帧 212 → 53 ms/帧(只开涟漪 187 → 48、只开线路动点 199 → 47),读数看 `getStats().noMsaa`。
|
|
105
|
+
- **运动期保持全分辨率**:`interactionNoMsaa` 的非 MSAA 后备缓冲只换抗锯齿与细线画法,不降低 viewport / 后备存储尺寸。相机拖动、滚轮、`zoomBy` / `setCamera` / 飞行与尺寸倍率变化的帧都按画布实际像素绘制,整幅贴回保持 1:1,停手 160 ms 后恢复 MSAA 静态帧。上一轮曾按整帧间隔把运动帧降到 **0.65× / 0.5×** 再线性放大贴回,实测会让缩放 / 移动中的画面短暂模糊,现已整体移除;与之配套的 `drawWidth` / `drawHeight` / `framePixelRatio`、`u_offsetScale` 和 UV 裁剪也一并删除。尺寸倍率连续变化仍复用 160 ms 的 `scaleMotion` 窗口,暂停符号贴图重烤与预烤,停手后再对齐当前尺寸档;这部分只省重烤,不改变当前帧分辨率。最终全分辨率基准(1280×800,无头 Chrome + ANGLE / AMD D3D11,`quality: 'high'`,三次运行取中位):10 万点 / 5 万线缩放 p50 **23.2 ms**、p95 **28.6 ms**,平移 **16.7 / 18.2 ms**;10 万点 / 10 万线缩放 **42.6 / 46.3 ms**,平移 **16.7 / 24.5 ms**。`selftest-perf.html` 另有回归断言记录每次 `gl.viewport`,人为拉长运动帧间隔时也必须始终等于画布全尺寸。
|
|
106
|
+
- **片元管线专用化(为什么此前「感觉没提升多少」)**:之前的优化主要落在相机排帧、聚合脏标记、整份重排与符号预烤上;10 万线交互的 CPU profile 约 **98.6% idle**、`render` 自身约 **0.1%**,说明主线程不是瓶颈。真正剩余的是每条可见线在 GPU 上重复执行虚线取模 / `smoothstep` / 光晕分支的填充成本。`shaders.js` 现在给交互期 `gl.LINES` 一条只输出颜色的 `THIN_LINE_FRAGMENT_SHADER`,并给「全实线、无光晕」的四边形批次一条 `SOLID_LINE_FRAGMENT_SHADER`:跳过虚线取模、`smoothstep` 与光晕分支,只保留颜色与(非 MSAA 帧的)边缘覆盖率;有虚线或热力光晕的批次仍走通用片元。`engine.js` 的 `drawLineBatch` 按批选择,`refreshLineThinLedger` 同时给出是否全实线;线宽、端点圆头、全分辨率与停稳后的 MSAA 静态帧都不变。实测(1280×800,无头 Chrome + ANGLE / AMD D3D11,`quality: 'high'`):10 万点 / 5 万线缩放 p50 **23.2 → 16.7 ms**、p95 **28.6 → 16.8 ~ 16.9 ms**;10 万点 / 10 万线缩放三次中位 p50 **42.6 → 18.0 ms**、p95 **46.3 → 21.4 ms**。16 ~ 18 ms 已接近 60Hz 帧节拍,剩余成本仍是 GPU 填充率;细线快路径的实际门槛是**最大半宽 ≤ 0.6 设备像素且无虚线**(dpr = 1 时约 1.2px 线宽),2px 实线不会走 `gl.LINES`,但轻量四边形片元仍把同一 10 万线场景从约 **82.4 → 63.9 ms**。要继续明显提速,方向是减少可见线段数或屏幕覆盖面积,而不是继续抠 JS。
|
|
102
107
|
|
|
103
108
|
- 框选提交(`pointerup`)只做必要的工作:选中集每次同步整份换新,抬层集合因此只比引用(没有拖动 / 合并这类额外抬层时直接引用选中集本身,空集合三处共用一份),撤销选中与「同内容再框一次」不再逐个搬 / 逐个比 3.4 万个下标;组件层的选中集读数(id 表、键集合、点 / 线 / 通道条数)在一趟循环里收好并按数组引用缓存,`selectionInfo()` / `selectChanged()` / `selectChange` 载荷直接取现成的;选中集的存活校验走 `isKeyAlive` 的键查表,不再为每个键造对象、投影一次;抬层欠下的整份重排记在 `raiseLayoutPending` 上、由 `flushRaiseLayout` 在下一帧渲染之前补,`pointerup` 回调里不再同步排十万级场景。实测(10 万点 / 10 万线):框住 3.4 万个节点的 `pointerup` 48.7 → 15.7 ms(框小一些的几轮 9.5 / 6.5 / 6.8 ms),提交后第一帧 19.5 ~ 24.2 → 15.6 / 6.7 ~ 9.5 ms。
|
|
104
109
|
- 整份重排(`layoutScene`)也少扫几趟十万条:点 glyph 从对象池里复用(逐字段就地改写,不再每轮造十万个对象)、圆点实例用 `writeCircleGlyph` 直写进 `prepareInstances` 的批次数组(不再逐条造 `circleFromGlyph` 的中间对象);没有线路装饰 / 没有跑动动画 / 没有节点阴影 / 没有逐要素状态样式时,各自的十万级空扫直接跳过(`hasLineDecorations` / `hasLineEffects` / `hasPointShadows`,节点侧另有 `hasPointStateStyles()`);细线快路径的账本只在「线宽或虚线开关真的变了」时才标脏;`zIndex` 分层只有一层(绝大多数场景)时不再重铺点顺序。同一份 10 万级的 CPU profile 里 `syncRaiseSets` 6.2 → 0 ms、`buildLineEffectGlyphs` 3.69 → 0 ms、`buildLineDecorationGlyphs` 1.66 → 0.62 ms、`thinLineBatchEligible` 2.2 → 0 ms。
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
- 网格与线宽都在屏幕空间计算,`devicePixelRatio` 变化后按设备像素重建场景。
|
|
10
10
|
- 拾取在 CPU 侧按屏幕距离完成:先识别拐点,再按 Ctrl + 点击补拐点(点到线比点到节点更近就加点,否则走节点多选),最后才轮到要素,容差分别由拐点尺寸、点直径与线宽推导。点的拾取与框选共用一张随数据范围重建的均匀网格索引,线也有随线段重建的网格索引,每次交互只遍历光标附近的候选再做精确比较,耗时与总量基本脱钩(`getStats().pickMs` 可以直接读出来)。
|
|
11
11
|
- 拐点用独立的圆点缓冲绘制,拖动只重算这一条线的线段与圆头,不触碰其它要素;框选矩形复用填充管线(一批顶点靠 `drawArrays` 偏移分段画填充与边框),悬停与选中强调仍走原来的高亮缓冲。
|
|
12
|
-
- 内置界面只有鹰眼图,而且要显式配 `minimap` 才有:小图自己画筛选后仍可见的节点与线路(含通道回线,`channelCircuits: false`
|
|
12
|
+
- 内置界面只有鹰眼图,而且要显式配 `minimap` 才有:小图自己画筛选后仍可见的节点与线路(含通道回线,`channelCircuits: false` 时每个分组只画一根逐顶点平均出的居中代表线),沿用主图规则时节点尺寸与圆 / 方 / 图片 / SVG / 自定义图元也和主图最终解析结果一致,但尺寸会再乘一次「小图视口 ÷ 主图视口」的比例,小图里的节点不会和主图一样大;视口框跟着相机走,节点 / 线路 / 框三份样式与位置、尺寸、交互都能单独配,`offset` 可动态移动整张图,`container` 可把根节点挂到外部 `div`(`getMinimapElement()` 用来接整图拖动),框上按下直接拖框。`zoomable` 三态互斥:`false` 关闭、`true` 手动缩放小图自身(主图留给 Ctrl / ⌘ + 滚轮)、`'follow'` 让整体范围跟随主图(`followZoomScale` 调指数);空白处拖动可以平移小图或框选让主图显示过去(`dragAction`,`box` 不依赖缩放,只有手动模式下 Ctrl / ⌘ 才切换动作),每格倍率是 `zoomStep`。`extentFollowCamera` 默认 `false`,外围要素消失时不会自行收窄包围盒;打开后才按相机取舍 `auto` 范围。不配就完全不产生 DOM,见「鹰眼图」。要更自由的预览仍然可以用 `getBounds()` 画全量缩略图、`getViewRect()` 画当前视口矩形,再把小图坐标按比例换算成 `setCamera()` 的目标。`docs/demos/GisViewDemo.vue` 右下角用的就是内置鹰眼图:尺寸、线路显隐、回线根数、拖动动作、缩放模式 / 跟随指数、范围开关与三份样式都在 demo 里换。
|
|
13
13
|
- 拖拽与框选期间只更新受影响要素的坐标与缓冲,不重新解析符号与贴图;通道拖拐点只重算这条通道的成员线。
|
|
14
14
|
- 配置写错不猜谜:键 / 预设 / 档位 / 事件名不认识时走 `onWarning`(与 `'warning'` 事件同一条消息)说一句,同一个错只提醒一次,不进 `onError`、也不打断流程;画质(`quality`)只改「画哪些批次 + 多大分辨率」,`view.getQuality()` 与 `onStats` 里的 `quality` 是同一份读数。
|
|
15
15
|
- 只需要渲染能力时,可以用 `createGisViewEngine(canvas, options)` 在任意 canvas 上复用同一套管线,此时渲染调度由调用方负责;引擎同样通过 `onClick` / `onDblClick` / `onPointDrag` / `onSelect` / `onWaypointChange` 暴露交互,框选与拐点的实例方法在引擎上分别叫 `selectInBox`、`insertWaypoint` / `removeWaypoint` / `selectWaypoint`。
|
|
@@ -86,6 +86,9 @@
|
|
|
86
86
|
**怎么一次给通道加多回?**
|
|
87
87
|
`circuits` 就是「这一次加几条」:`beginAddChannel({ kind: 'channel', circuits: 3 })` / `addChannel({ from, to, circuits: 3 })`(旧写法),新建线路也一样(`addLine({ from, to, circuits: 2 })` 一次两条)。方案表的余量按它算 —— 加 3 回时只剩 2 回余量的分组会置灰,`reason` 写清「只剩 2 回余量,本次要加 3 回」;落地回执里 `lines` / `lineIds` 是这一批线,`onChannelDraft` 的 `commit` 载荷里也一样,`removeLine` 一条条摘掉就行(demo 的「移除上一条新增」就是整批摘)。
|
|
88
88
|
|
|
89
|
+
**鹰眼图的节点大小 / 范围 / 框选怎么调?**
|
|
90
|
+
节点大小:`inheritRules` 打开(默认)时,小图取主图最终解析出的尺寸,再乘「小图视口 ÷ 主图视口」的较小比例 —— 只看两边 CSS 视口宽高,不随主图相机倍数变化;仍嫌大就调小图自己的 `pointSizeScale`,或配自己的 `pointRules` / `pointStyle` 接管尺寸。范围:`extent: 'auto'` 默认取数据 ∪ 底图且**不随相机收窄**;想要外围内容消失后跟着收窄才打开 `extentFollowCamera`。框选:`dragAction: 'box'` 不需要打开 `zoomable`,关闭缩放也能在空白处直接框;`zoomable: 'follow'` 是整体范围跟随主图,跟手动缩放 `true` 互斥。
|
|
91
|
+
|
|
89
92
|
**虚线看不出来?**
|
|
90
93
|
图案是 CSS 像素量屏幕距离,线很粗时应把图案调长(`[10, 6]` 在 3px 线宽下很清楚,在 12px 线宽下就显得密);另外图案槽位上限 16 组,超过的线会退回实线。
|
|
91
94
|
|
|
@@ -136,7 +139,7 @@
|
|
|
136
139
|
配 `quality: 'auto'`:在「全开 → 关光晕 → 关装饰 → 0.75 倍分辨率」之间升降(掉档快、升档慢),判定看两路读数里更差的那一路 —— 渲染耗时(`getQuality().frameMs`)与**整帧间隔**(`getQuality().gapMs`,只在交互 / 动画连续画帧时量),预算与分辨率下限用 `{ mode: 'auto', budgetMs, minScale }` 调;`view.getQuality()` 随时能读当前档位,`view.on('qualityChange', …)` 能拿到每次升降,见「画质」。
|
|
137
140
|
|
|
138
141
|
**十万级数据在无头 / 软件光栅 / 低端显卡上为什么还是卡?卡在哪一半?**
|
|
139
|
-
先分清卡的是哪一半:`getStats().frameMs` 量的是引擎自己那一趟(算变换、攒实例、写缓冲、发绘制),十万级下它常年 0.1 ~ 0.8 ms;真正花时间的是**填充率** —— 十万个节点加十几万条线铺满一屏,光栅化与浏览器合成都由 GPU(没有 GPU 时是软件光栅)扛,这一半不在 `frameMs` 里。于是按顺序看四件事:① **总覆盖量**(线的屏幕总长 × 宽度 × 条数,再算上互相重叠)—— 同样 10 万点,网格邻居线(19.9 万条、线短不叠)拖动 p50 只要 7 ms,而「每 5 个点连一条到 0 号点」的长线在圆心附近叠十几层,p50 就是 194 ~ 208 ms;② **MSAA** —— 同一份几何实测 MSAA 4× 是关掉时的近 5 倍(`antialias: false`,软件光栅下引擎已经自动关),读数看 `getStats().antialias`;交互期与动画期还有更省事的一档 `interactionNoMsaa`(缺省开:拖动 / 缩放与动画在跑的那些帧画进不带 MSAA 的后备贴图、收尾整幅贴回,停手 / 停帧后照旧走 MSAA)—— 十万级实测交互期单帧 133 → 36.6 ms、拖动 p50 130 → 16 ~ 22 ms、动画帧 212 → 53 ms,读数看 `getStats().noMsaa`;③ 交互期默认走**细线快路径**(`interactionThinLines
|
|
142
|
+
先分清卡的是哪一半:`getStats().frameMs` 量的是引擎自己那一趟(算变换、攒实例、写缓冲、发绘制),十万级下它常年 0.1 ~ 0.8 ms;真正花时间的是**填充率** —— 十万个节点加十几万条线铺满一屏,光栅化与浏览器合成都由 GPU(没有 GPU 时是软件光栅)扛,这一半不在 `frameMs` 里。于是按顺序看四件事:① **总覆盖量**(线的屏幕总长 × 宽度 × 条数,再算上互相重叠)—— 同样 10 万点,网格邻居线(19.9 万条、线短不叠)拖动 p50 只要 7 ms,而「每 5 个点连一条到 0 号点」的长线在圆心附近叠十几层,p50 就是 194 ~ 208 ms;② **MSAA** —— 同一份几何实测 MSAA 4× 是关掉时的近 5 倍(`antialias: false`,软件光栅下引擎已经自动关),读数看 `getStats().antialias`;交互期与动画期还有更省事的一档 `interactionNoMsaa`(缺省开:拖动 / 缩放与动画在跑的那些帧画进不带 MSAA 的后备贴图、收尾整幅贴回,停手 / 停帧后照旧走 MSAA)—— 十万级实测交互期单帧 133 → 36.6 ms、拖动 p50 130 → 16 ~ 22 ms、动画帧 212 → 53 ms,读数看 `getStats().noMsaa`;③ 交互期默认走**细线快路径**(`interactionThinLines`):最大半宽 ≤ 0.6 设备像素(dpr = 1 时约 1.2px 线宽)且没有虚线时,拖动 / 缩放期间改用 `gl.LINES` 画,实测 p50 194 ~ 208 ms → 132 ~ 139 ms、同一窗口帧数 13 → 19 ~ 24,读数看 `getStats().thinLines`;2px 实线不会走这一档,但全实线四边形另有轻量片元;④ 画质档的杠杆是**分辨率**,`fast` 档按 0.75× 渲染(画布像素少一半),同一画面实测 156 → 92 ms/帧,自动档还看整帧间隔(连续画帧时掉到 40fps 以下就逐档往下掉);高分屏 / 全屏下按 `maxCanvasPixels`(缺省 400 万像素预算)压渲染倍率、或把窗口开小,都是直接减填充量。见「画质」与「大数据量」。
|
|
140
143
|
|
|
141
144
|
**一次改很多要素,回调被刷屏、历史被撑爆?**
|
|
142
145
|
把这一段包进 `view.batch(fn, { label })`:收尾只发一条 `type: 'batch'` 的 `onDataChange`(`changes` 按顺序列出每一步),历史里也只占一步,见「批量更新」。
|
|
@@ -179,7 +182,7 @@
|
|
|
179
182
|
| 覆盖层 | `GisViewLabelOptions`、`GisViewLineLabelOptions`、`GisViewItemLabel`、`GisViewLabelState`、`GisViewLabelPatch`、`GisViewSetLabelContentOptions`、`GisViewLabelListItem`、`GisViewItemLabelsInfo`、`GisViewRemoveLabelResult`、`GisViewSetLabelStatesResult`、`GisViewLabelChangePayload`、`GisViewLabelDragPayload`、`GisViewLabelClickPayload`、`GisViewLabelPointerPayload`、`GisViewLabelRef`、`GisViewLabelTarget`、`GisViewLabelBatchEntry`、`GisViewLabelRemoveEntry`、`GisViewLabelBatchSkip`、`GisViewAddLabelsResult`、`GisViewUpdateLabelsResult`、`GisViewRemoveLabelsResult`、`GisViewLineDecoration`、`GisViewTooltipOptions`、`GisViewLabelPayload`、`GisViewLineLabelPayload` |
|
|
180
183
|
| 筛选 | `GisViewFilter`、`GisViewFilterInput`、`GisViewFilterShorthand`、`GisViewFilterResult`、`GisViewFilterCascade`、`GisViewFilterMode`、`GisViewFilterWorkerOptions` |
|
|
181
184
|
| 大数据展示模式 | `GisViewDenseMode`、`GisViewDenseModeOptions`、`GisViewDenseModeInput`、`GisViewDenseState` |
|
|
182
|
-
| 通道 | `GisViewChannelInput`、`GisViewChannelBuildOptions`、`GisViewChannelBuild`、`GisViewChannelInfo`、`GisViewChannelGroupInfo`、`GisViewChannelGroupWaypoints`、`GisViewChannelGroupLineWaypoints`、`GisViewChannelMember`、`GisViewChannelContext`、`GisViewChannelAddPlan`、`GisViewChannelBetween`、`GisViewChannelAddOption`、`GisViewChannelAddInput`、`GisViewChannelAddResult`、`GisViewChannelDraftOptions`、`GisViewChannelDraftPayload`、`GisViewChannelLineMoveTarget`、`GisViewChannelLineMoveResult`、`GisViewRemoveLineResult`、`GisViewRemoveChannelResult`、`GisViewRemoveLinesResult`、`GisViewChannelInView`、`GisViewChannelAction`、`GisViewChannelActionId`、`GisViewChannelDraftStyle`、`GisViewChannelWaypointDragMode`、`createChannels`、`CHANNEL_DEFAULT_COLORS` |
|
|
185
|
+
| 通道 | `GisViewChannelInput`、`GisViewChannelBuildOptions`、`GisViewChannelBuild`、`GisViewChannelInfo`、`GisViewChannelGroupInfo`、`GisViewChannelGroupWaypoints`、`GisViewChannelGroupLineWaypoints`、`GisViewChannelMember`、`GisViewChannelContext`、`GisViewChannelAddPlan`、`GisViewChannelBetween`、`GisViewChannelAddOption`、`GisViewChannelAddInput`、`GisViewChannelAddCommitInput`、`GisViewChannelAddResult`、`GisViewChannelDraftOptions`、`GisViewChannelDraftPayload`、`GisViewChannelLineMoveTarget`、`GisViewChannelLineMoveResult`、`GisViewRemoveLineResult`、`GisViewRemoveChannelResult`、`GisViewRemoveLinesResult`、`GisViewChannelInView`、`GisViewChannelAction`、`GisViewChannelActionId`、`GisViewChannelDraftStyle`、`GisViewChannelDraftDots`、`GisViewChannelDraftDotStyle`、`GisViewChannelWaypointDragMode`、`createChannels`、`CHANNEL_DEFAULT_COLORS` |
|
|
183
186
|
| 事件载荷 | `GisViewPickResult`、`GisViewPickAtResult`、`GisViewRectQueryResult`、`GisViewSelectionInfo`、`GisViewSelectedItems`、`GisViewSelectPayload`、`GisViewClickPayload`、`GisViewPointerPayload、GisViewCascadeHighlightScope、GisViewCascadeHighlightOptions、GisViewCascadeHighlightInput、GisViewCascadeHighlightEntry、GisViewCascadeHighlightOrigin、GisViewCascadeHighlightInfo`、`GisViewPointerAction`、`GisViewPointerActionType`、`GisViewPointerTarget`、`GisViewPointerModifiers`、`GisViewWaypointActionData`、`GisViewLabelActionData`、`GisViewHoverPayload`、`GisViewCursorPayload`、`GisViewPointDragPayload`、`GisViewDragPointChange`、`GisViewDragLineChange`、`GisViewPointCopyPayload`、`GisViewPointCopyState`、`GisViewPointMergePayload`、`GisViewMergeImpact`、`GisViewMergeResult`、`GisViewMergeFailure`、`GisViewWaypointPayload`、`GisViewWaypointInfo`、`GisViewWaypointKey`、`GisViewParallelGroup`、`GisViewReadyPayload`、`GisViewEngineStats`、`GisViewEngineHeatDensity`、`GisViewDataChangeType`、`GisViewDataChangePayload`、`GisViewSelectChangePayload`、`GisViewShortcutPayload`、`GisViewRenderPayload`、`GisViewAddItemsResult`、`GisViewAddPointResult` |
|
|
184
187
|
| 快捷键与复制 / 合并 / 删除 | `GisViewShortcuts`、`GisViewShortcut`、`GisViewShortcutAction`、`GisViewCopyOptions`、`GisViewCopyContext`、`GisViewCopyStartOptions`、`GisViewPasteOptions`、`GisViewCopyItem`、`GisViewClipboardItem`、`GisViewMergeOptions`、`GisViewMergeContext`、`GisViewPointStyleSnapshot`、`GisViewPointRef`、`GisViewMergeResult`、`GisViewMergeFailure`、`GisViewMergeOutcome`、`GisViewRemovePointsOutcome`、`GisViewRemoveSelectedOutcome`、`GisViewRemoveOptions`、`GisViewRemoveScope`、`GisViewRemovePayload`、`GisViewRemoveResult`、`GisViewRemoveOutcome`、`GisViewRemoveFailure`、`GisViewRemoveAction`、`GisViewRemovePoint`、`GisViewRemoveLine`、`GisViewRemoveWaypoint`、`GisViewRemoveGroup`、`GisViewRemoveChannel`、`GisViewRemoveCounts`、`GisViewChannelSelectMode`、`GisViewUpdateItemsOutcome`、`GisViewUpdateItemEntry` |
|
|
185
188
|
| 相机与视图 | `GisViewCamera`、`GisViewViewport`、`GisViewFocusOptions`、`GisViewFocusTarget`、`GisViewExportImageOptions`、`GisViewExportSelectionOptions`、`GisViewExportSelectionResult` |
|
package/docs/steps/README.md
CHANGED
|
@@ -16,11 +16,11 @@ npm run dev # 或 npx vite
|
|
|
16
16
|
控制台里 `__gisSteps.view` 就是当前这一步的视图,`__gisSteps.DATA` 是那份园区数据,可以随手调 API。
|
|
17
17
|
地址栏里带步骤号(`index.html#7`)可以直接打开某一步,也能收藏 / 分享。
|
|
18
18
|
|
|
19
|
-
自测页就在同一个目录下(`selftest.html` / `selftest-demo.html` / `selftest-demos.html` / `selftest-labels.html` / `selftest-select.html` / `selftest-actions.html` / `selftest-scale.html` / `selftest-perf.html` / `selftest-features.html` / `selftest-full.html` / `selftest-effects.html` / `selftest-data.html` / `selftest-decoration.html` / `selftest-docs.html`):打开就跑,标题报 `PASS` / `FAIL`,页面上的清单是逐条断言。**无头环境下 `requestAnimationFrame` 基本不推进**,动画类断言要用真时间(CDP 或直接开浏览器)跑 —— `selftest-effects.html` 因此自己装了一个「帧泵」(把 `requestAnimationFrame` 收进 Map、手动按帧推进),涟漪与动点的断言才测得动。另有 docs/index.html(文档中心:把仓库里的 markdown 读成 HTML)、docs/api/index.html(API 速查:
|
|
19
|
+
自测页就在同一个目录下(`selftest.html` / `selftest-demo.html` / `selftest-demos.html` / `selftest-labels.html` / `selftest-select.html` / `selftest-actions.html` / `selftest-scale.html` / `selftest-perf.html` / `selftest-features.html` / `selftest-full.html` / `selftest-effects.html` / `selftest-data.html` / `selftest-decoration.html` / `selftest-docs.html`):打开就跑,标题报 `PASS` / `FAIL`,页面上的清单是逐条断言。**无头环境下 `requestAnimationFrame` 基本不推进**,动画类断言要用真时间(CDP 或直接开浏览器)跑 —— `selftest-effects.html` 因此自己装了一个「帧泵」(把 `requestAnimationFrame` 收进 Map、手动按帧推进),涟漪与动点的断言才测得动。另有 docs/index.html(文档中心:把仓库里的 markdown 读成 HTML)、docs/api/index.html(API 速查:264 个实例成员逐个一张卡,参数 / 返回 / 说明 / 示例与复制,搜索 + `#成员名` 深链、左侧分组跟随与深浅主题)、docs/guide/sample-document.html(sample-document.json 的交互图解)与 docs/steps/selftest-docs.html(上面这三个页面的自测:清单 / 渲染 / 点选标注一页跑完)。
|
|
20
20
|
|
|
21
21
|
## 怎么查全部属性与方法
|
|
22
22
|
|
|
23
|
-
组件的完整 API 是同一份清单的五个去处(当前:实例成员 **
|
|
23
|
+
组件的完整 API 是同一份清单的五个去处(当前:实例成员 **264** 个 = 259 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性,`options` **96** 个键,数据字段 **250** 个):
|
|
24
24
|
|
|
25
25
|
- **手册里查**:`docs/manual/` 把整本手册拆成 21 章(索引页 `docs/GisView.md` 有目录与按任务的快速跳转)—— 找方法先看 [`16 实例方法总表`](../manual/16-实例方法总表.md)、要把每个成员逐个列全(参数写「名字: 类型 = 默认值」、返回值只写类型)看 [`17 参数速查`](../manual/17-参数速查.md)、要能直接复制的调用看 [`18 逐方法示例`](../manual/18-逐方法示例.md);`options` 表与数据字段表分别在 [`03 配置项(options)`](../manual/03-配置项.md) 与 [`02 坐标与数据`](../manual/02-坐标与数据.md)。同名的接口在 `src/gis-view/types.d.ts`。
|
|
26
26
|
- **页面上查**:`docs/api/index.html`(API 速查)—— 左栏按 16 个分组列出全部成员,带搜索(Ctrl+K)与方法 / 属性 / options 三档开关,当前读到的分组会跟着高亮;右栏每个成员一张卡,参数 / 返回 / 说明之外,示例块直接来自 [`18 逐方法示例`](../manual/18-逐方法示例.md),带高亮与「复制名字 / 复制代码」,旁边还有「在调试页跑 →」;地址栏带 `#updateLine` 这样的 hash 直接跳到那一条。右上角主题按钮与文档中心共用 `gisDocs.reading`,跟随系统 / 浅色 / 深色三档换页不会各记一套;深链落点还留了 `scroll-margin-top`,不会被吸顶栏盖住。页面数据来自 `docs/demos/api-index.js`(生成物,禁止手改),自测页 `docs/steps/selftest-docs.html` 盯着它别掉队。
|
|
@@ -105,22 +105,22 @@ API 名字与前面几步是一一对应的;第 11 步再补两件「数据之
|
|
|
105
105
|
|
|
106
106
|
- `selftest.html`:手写版第 1 ~ 9 步的断言(拾取、草稿的 step 1 / 2、落地、移除、连续新增、平移……);
|
|
107
107
|
- `selftest-full.html`:真组件上的同一套断言,外加把 `index.html` 的十三个步骤逐个挂载一遍(第 13 步再点一遍画质 / 批量 / 撤销 / 重做 / 功能预设五个按钮);
|
|
108
|
-
|
|
108
|
+
本轮再补五条(双回线关闭后居中代表线、`backgroundStyle` 接管画布底色、主图方形 / 圆形尺寸与 SVG 图标、`container` 外部挂载与 `offset` 动态位移、换容器复用同一根节点并随 `destroy()` 清理);之前补的四条:小图上的 GeoJSON 底图在主视图放大 3 倍前后红色描边像素数不变(容差 20%)、小图放大 6 倍时 `pointZoomScale` 指数 0 / 1 的像素对比(指数 1 至少 4 倍面积、指数 0 不长大,且倍数回到 1 时指数 1 也不再放大)、以及 stub host 上「拖节点没有结束事件、空闲计时器也会把 `hideOnInteract` 收起的标签层放出来」。
|
|
109
109
|
- `selftest-features.html`(672 条):真组件上的线型 / 线路装饰 / 光标 / 表现型配置断言 —— 把画面用 `exportImage` 读回 2D 画布数像素(实线 > 虚线 > 点线的墨量、`dc_line` 中点装饰的像素差、`showGrid` 只重画一帧),光标则用合成的指针事件与 Ctrl 键读出 `canvas.style.cursor`;装饰那几条还会量「几何变了装饰跟不跟着走」—— 写回新路径、拖绑定的节点、加 / 删拐点之后,装饰的紫色像素质心都要落在新中点上(容差 3px);另外两条量的是**固定档**:`anchor: 'fixed'` + `at` 先把装饰钉在写死的世界坐标上,再把线从水平拖成斜的,装饰必须留在原坐标、不能跑到新中点。线路标签在单独一张画布上验:默认挂在中点、内容取 `name`,`position: 0` 落到起点,`hideOnInteract` 在缩放期间给整层加 `--hidden`、停手恢复,关掉 `lineLabel` 之后标签消失。装饰缺省 `auto` 的那一串也在这里量:没有拐点时拖端点装饰保持同一个比例(一直挂在中点)、加上拐点之后钉在那一刻的坐标上(改折线也不动)、把拐点删回直线又回到比例跟随、`anchor: 'follow'` 则一直跟到折线的新中点;`parallel` 用一条横条符号验(默认包围盒 26×6,平行之后转成 4×26),方形符号走的是 gl.POINTS 那条路(旋转后外接盒从 24×24 撑成 32×32)。通道里「每条回线各显示自己的标签」也在这里验:没有逐条配置时会被 32px 屏幕小格去重(只显示 1 ~ 2 条),给每条回线写上自己的 `lineLabelPosition` 之后 8 条全都在、位置各不相同,斜线上的标签按线路方向转、朝左的自动翻半圈(rotate = 0)。线路的层序(`zIndex`)也在这里验:画布正中一横一竖两条粗线交叉,默认后写的竖线在上(中心像素蓝),规则里给横线写 `zIndex: 5` 就换成红,`updateLine` 给竖线写 `zIndex: 9` 又换回蓝,写回 0 再回到红。这一轮另外验了插件上下文:`options.plugins` 与 `view.use()` 拿到的第二个参数是同一个冻结对象(`apiVersion === 1`、`view` 指回实例),旧的一参插件写法照常可用。这一轮另补 3 条合并触发时机断言(center 差 10px 不触发、继续拖入目标区域触发并带 trigger、collision 同距离照旧触发)。这一轮再补 1 条:13 个旧快捷方法已从实例删除,`updatePoint` / `updateLine` / `updateChannel` / `updateOptions` / `setFilter` / `setDropPreview` 仍在。对账组另补 8 条:`getChangedChannels({ ignoreGroups })` 忽略分组增删、已有分组拐点仍报、`reasons` 收窄与线路字段 / 路径 / 显隐变化,以及删除通道 / 删除线路 / 无通道场景同步清账。本轮再补实例命令的注册 / 同名覆盖 / 异常与销毁断言,并再补 16 条:筛选恢复后通道宽度与回线车道、`focusOn` 显式 `point` / `line` / `channel` / `group` 与统一缩放口径、`selectFeatures` 分组写法、`selectWaypoint` 带选线路,以及 `lineFollowsNode` 的隐藏 / 筛选 / 删除 / 撤销与动态切换。
|
|
110
110
|
- `selftest-decoration.html`(75 条):动态装饰器草稿专项自测 —— 开启后的 `start` 载荷、默认 14px 触发半径与每条线路不限量、指针在线路上 `hover`(命中线 / 自身 `decoration` / 平行角)、同一位置重复移动不重复发事件、移出目标 `leave`、在线路与空白处 `pointerup` 的 `ready` 差异、按下 / 抬起 `action.type` 都是 `decorationDraft`、单线 / 分组 / 整条通道三档作用域与非通道回退、`maxPerLine` 满额时悬浮与松手都不触发且提交前复核不写数据、`allowedLines` / `allowedChannels` / `allowedGroups` 三种白名单在悬浮与提交两处都拦截、`update` 改图标 / 尺寸 / 方向、`commit` 一次 `batch` 写四条且只发一条 `dataChange`、`cancel` 不改数据、重复 `begin` 替换旧草稿、整份换数据后过期草稿静默清掉。节点侧另有 32 条:`target: 'point'` 命中与只展开一个节点目标、方向 / 距离换算成屏幕偏移、相对偏移 / 固定世界坐标 / 鼠标位置三种位置来源、连接线样式随提交保存、`allowedPoints` 与 `maxPerPoint` 在悬浮 / 提交两处拦截、`both` 等距时节点优先且最近节点被白名单拦住时回退线路、节点与线路的重复 `reject` / `offset` 两种策略、节点装饰 get / update / remove 与整批提交、度转角与连接线进入矢量快照。这一轮另补 8 条节点断言:既有的 2 条「装饰随移动」仍保留;新增 6 条覆盖 `pointer + follow`、`fixed + follow`、`offset + fixed` 三种跨组合的预览与提交结果,确认位置来源和落地关系解耦后分别写成 `offset` 或 `at`。
|
|
111
111
|
- `selftest-demo.html`:demo 级自测 —— 面板上的「定位直流线路」按当前数据集的 `dc_line` 条数走(默认数据集 `new.json` 里现在自带 5 条,点一下直接选中并定位到第一条;一条都没有的数据集才切到样例数据、定位到「直流送电」,中点挂着 `PATH_DC_LINE` 装饰),断言按数据算出来的条数写、不写死数字,读数与选中面板都跟着更新;打开时说明段落默认收起、面板按组折叠,节点标签只给最高电压那一档、线路标签默认整个不显示(面板上勾「显示」再按「显示范围」展开),面板上有 PATH_DC_LINE 的「标记定位」下拉(自动 / 一直跟随 / 写死坐标三档)。「通道回线各自显示」这一档从数据层核对(勾上之后 3669 条回线都带上了自己的 `lineLabelPosition`),再放大三步后勾「与线路平行」,画出来的标签 transform 里要出现 `rotate(...)`。这一轮又补了「数据映射」下拉(raw / lngLat / fit / scale)与「节点 / geojson 比例」滑块:切到 fit 后 320 个站点都要落进 GeoJSON 画出来的范围、读数里给出换算比例;「经纬度 → 屏幕」「屏幕 → 经纬度」两个按钮来回换算一致(屏幕读数保留 1 位小数,允许 0.01° 的回差);在画布上挪一下鼠标,面板上就要出现屏幕像素与经纬度两行读数。
|
|
112
|
-
- `selftest-perf.html`(
|
|
112
|
+
- `selftest-perf.html`(183 条):大数据量与画质档位的性能自测 —— 20000 点 / 8000 线的建场景与整份重排耗时、500 次拾取、逐个改 500 个节点对 `view.batch()`、四档画质与 `'auto'` 的自动升降;前面几轮补过 8 条交互期细线的断言(`getStats()` 给出 `antialias` / `software` / `thinLines` 三个读数;交互期 1px 实线整批走 `gl.LINES` 快路径、停手回到四边形展开;`interactionThinLines: false`、线宽 8px、整批带虚线这三种情况下都不走快路径,改回细实线又走);上一轮补过 7 条线路标签候选复用的断言(复用态与整层重排逐字一致,改线路内容 / 挪节点 / 相机变化之后缓存都作废)与 1 条连续更新的耗时 DIAG(2 万点 / 1 万线,中位 0.30 ms / 次);**这一轮再补 3 条标签避让盒的断言**(4 万点 + 4 万条长线放大后连续整层重排:第 2 轮起一次布局都不读、同一轮里没有哪条标签被量第二次)与 1 条量测读数 DIAG(逐轮 1427 / 0 / 0 / 0 / 0 次,旧写法 2662 / 0 … 次、第 1 轮重复量 1235 条;**上一轮把「没有哪条标签被量第二次」从按 DOM 节点去重改成按标签身份(`labelKind` # `labelIndex` # `labelLabel`)去重** —— 标签节点是池化复用的,按节点去重会把「同一格换了条标签」误报成重复量,旧代码实测第 1 轮 1235 条、新代码 0 条;**这一轮再补 3 条线路批次视口裁剪的断言**(20000 点 / 8000 线放大到 `scale: 10`:`lineInstances` 8000 → 27、相机挪回来回到 8000、来回一趟整幅像素差 0 / 241164)与 1 条读数 DIAG。这一轮再补 2 条点视口网格的断言(网格可用时枚举顺序与整份筛出来的下标序列一致、回调返回 `false` 最多只访问一个)。这一轮再补 19 条点聚合断言(归一化、1.2 万点建组与统计、原始数据不变、`visibleOnly` 排除成员、拾取 `type=cluster`、点击语义、`maxZoom` 展开、关闭恢复),页面每段之间让出一帧,边跑边出结果。这一轮另补 7 条热力图缓存断言:`styleKey` / `weightKey` 的语义、只改 `density.opacity` 不重采不重烤、换色带与定向改要素值会重烤。;本轮新增 1 条相机运动帧断言:人为拉长帧间隔并连续改相机,记录到的每次 `gl.viewport` 都必须等于画布全尺寸,防止运动期降采样重新引入模糊;本轮再补 1 条像素级断言:在同一交互状态下把细线专用片元管线与四边形画法逐像素对拍,线像素数量与整幅颜色差都必须在容差内,防止新管线把整批线画丢、画黑或换色。
|
|
113
113
|
- `selftest-scale.html`(125 条:119 PASS + 6 DIAG):大数据交互这一轮的自测 —— 2000 点 / 2000 线、筛选开着:`batch()` 里改 200 个节点只发 1 条 `onFilter` 与 1 条 `onDataChange`(对照组逐个调用是 200 条);改字段 / 改坐标 / 改线路之后「增量补写的隐藏集」与「从零建一个同样条件的视图」逐位一致;点挪到被筛掉的坐标上,按 `coordinate` 联动的线路当场隐藏、挪回去又出来;线段网格的步进登记与多层粗格(沿线查得到、线不经过的角落不进候选、跨层只返回一次、2 万条长线密集时小范围查询的候选远少于总条数、整图估计值与总条数同量级);点 / 线 / 空白的拾取;搜索文本缓存的命中与「改过字段之后按新值搜得到、旧值搜不到」。最底下几条 DIAG 是耗时读数(`Date.now()`),无头浏览器里虚拟时钟不往前走、显示 0.0 ms,带界面的浏览器里才是真实数字。;这一轮又补了 8 条:三万点的发散渲染(超过 2 万点走分批铺开)铺完之后装饰 400 个一条不少、挪一条带装饰的线的端点只动那一条线的装饰且落在新路径中点上、贴图符号进的是贴图批次、选中的节点抬到最上层、整份重排之后装饰与动点不翻倍,以及「节点自己配的涟漪数得清」(这一条顺带抓出了发散渲染期间涟漪补不齐的 bug)。这一轮再补一条 `createPointGrid.estimate` 与 `query` 同口径、带 `stopAfter` 提前封顶的断言(不建候选数组也能判断值不值得走网格);本轮又补了删除型筛选的 10 条回归:同步初始删除先于 `ready`、无 id 线路按下标删、级联线路与空通道一起清掉、单条 `filterRemove` / 单次 `onFilter`、历史一步、撤销不再次删除、重做不补发第二条事件、运行时 `setFilter` 与 `cascade: false` 对照,另校验载荷里的 `removedPoints` / `removedLineItems` 与剩余数据。
|
|
114
114
|
- `bench-filter.html`:基准页(不是自测,只会打印读数)—— 20000 点 / 20000 线、`cascade: 'binding'` 的筛选开着(`filterWorker: false`),量「连换 10 次筛选条件」「`batch()` 里改 500 个节点(筛选开着 / 关掉)」「单个 `updatePoint`」「默认全字段 `search` 跑两次」这几档,每档先热身一遍再计时(`Date.now()`)。标题从 `bench running` 变成 `bench done` 表示跑完;别用 `--virtual-time-budget` 打开它 —— 虚拟时钟下同步脚本里的 `Date.now()` 不往前走,量出来全是 0。
|
|
115
115
|
- `bench-scale.html`:百万级基准页(同样只打印读数)—— 点 / 线规模与视口走 URL 参数,`window.__bench.run(options)` 量生成数据、建场景与发散渲染、平移 / 缩放 / 放大 / 拾取、`refresh` / `updatePoint` / `updateOptions`、内存账与销毁回收;另有 `prepare`(只建场景)、`__bench.op(name)`(单步)与 `__bench.micro(count)`(原语微基准)。配 `scripts/bench-scale.mjs` 用(CDP 驱动,`--points=1000000 --gpu --port=93xx`,另有 `--profile` 抓 CPU profile、`--heap` 抓堆快照);跑全部自测页用 `scripts/run-selftests.mjs`(`node scripts/run-selftests.mjs [页面名…]`,无头 Chrome + 真时间),两个脚本都要先起 dev server。
|
|
116
116
|
- `selftest-labels.html`(165 条):标签拖动与编辑、HTML 语义、高密度画布、导出分项多选的专项自测 —— 按住标签拖 90 px 之后标签真的挪了那么多、同时画布不平移,`labelDrag` 的 `start` / `move` / `end` 三态与 `offset` / `delta` 对得上,偏移进得了 `getLabelStates()`、再拖动节点时偏移照旧叠在节点位移上,`resetLabel` 之后回到默认锚点;`updateLabel` 改内容 / 字号 / 颜色(`style` 是增量合并,只改字号不动颜色)、`resetLabels` 一次清干净;点一下标签发 `labelClick` 并把这一条交给面板;线路标签的拖动与恢复默认同一条路;导出侧验「选什么导什么」(选场站只出场站、选通道自动带上拐点、不认识的分项记一条提示),`exportData({ scope: 'selection' })` 验场站 + 通道 / 只出线路 / 没选中时的 `ok: false` 三个分支。HTML 专项验全局 / 逐条 `true` / `false`、模板与 `field` / 默认名称转义、`updateLabel` 双向切换、线路标签富文本、点击载荷保留 `html` 与原标记、`getLabels()` 报运行时偏移、只改样式不替换 HTML 子节点、导出 → 回灌保持逐条语义,以及 120 条同内容标签首轮布局读取 ≤ 4 次的缓存断言。高密度专项用 1100 条纯文本验缺省 `auto` 只留一张画布、混入一条 `html: true` 仍保留那一条 DOM、`labelRender: 'dom'` 回退逐条 DOM 并清空画布、切回 `auto` 回收上一轮 div、SVG 导出包含画布标签与缩放只推一次;新增断言还验 1100 条同文案只调用 `measureText` ≤ 2 次、同一任务连续 5 次缩放只重绘 1 ~ 2 次、显式 `canvas` 在优化档遵守 `maxCount: 200`,以及 6 条线路标签在优化档全部进画布且 DOM 为 0。另验 GeoJSON 区域文字同一相机重复刷新 0 次 `style` 写入、只改字号复用同一 DOM 节点、离屏时改样式再回到视野仍能恢复显示、只改锚点位置也会重新投影,以及同一帧 12 次相机变化只做 1 次重投影。设备像素专项把 `devicePixelRatio` 临时设为 1.25,验标签落点与拖动期整层位移都落在物理像素网格上,并确认逐条标签不再各自声明 `will-change`。
|
|
117
117
|
- `selftest-actions.html`:事件判读与只读接口的专项自测 —— 左右键、拖动、框选、拐点与标签的 `action` 三件套(type / target / data)、`button` / `modifiers` / `moved` / `double`、Shift 框选(含没框到东西的空框)与 Ctrl 取反在 `selectChange.action.type` 上的分辨、`pickAt` 的客户端坐标与只读性、`getFeaturesInRect`(含宽高为负的矩形)与 `getChannelsInView`、`addPoints` / `addPoint` / `addLines` / `removeLines` 的返回与 `dataChange` 类型;最后一段把第 12 步 `step-12-events.js` 挂起来跑一遍(判读读数、`probeAt` / `probeRect`、两条走廊)。页面底部流式打印 PASS / FAIL。
|
|
118
118
|
- `selftest-select.html`:选择与框选的专项自测(69 条)—— 点选 / 悬停 / Ctrl 取反 / 多选开关的像素级状态样式与恢复,`select` / `deselect` / 通道写法 / 变更来源的接口断言,以及 SVG 状态贴图的预热与不同尺寸配方;这一轮补了 `selectionKinds: 'all'` 下 `selectInBox` 一次收下节点、独立线路与按 `channelSelectMode` 摊开的通道回线;本轮再补 SVG 导出 `currentStrokeColor` 的描边色还原,以及 `selectTrigger` 默认单击、双击选中、空白双击清空、运行时切回单击与双击 + `multiSelect` 的取反 / 累加 / 取消。
|
|
119
|
-
- `selftest-demos.html`:演示外壳与 22 个页签(21 个功能 + 综合,按基础 / 交互 / 样式 / 数据 / 性能 /
|
|
119
|
+
- `selftest-demos.html`:演示外壳与 22 个页签(21 个功能 + 综合,按基础 / 交互 / 样式 / 数据 / 性能 / 综合六组排列)的自测(**347** 条)—— 逐个用 `?demo=<id>` 打开基础视图、标签交互、标签显示、鹰眼图配置、样式与规则、悬停与提示、通道与数据、编辑与保存、事件与方法、接口能力、批量复制与合并、拖拽落点、可视化连线、动态装饰器、批量新增、搜索与筛选、扩展能力、性能与质量、点聚合、动画效果、扩展图形与综合示例,画布与右栏读数都在、切页签会把 `?demo=` 写回地址栏、切走之后上一个实例被销毁(不留画布)、缺省页签是综合示例、页签总数与注册表一致、顶部六个分组按钮一次只展开一组且组名可见;鹰眼图配置页另验 `layers` 的自定义 `draw` 真被调用、`placement` 切到数据前 / 后时 `drawInfo.stage` 跟着变、框拖动触发 `onInteraction` 并带相机、尺寸切换走 `updateOptions` 重铺画布、DPR 2 下 `maxPixelRatio` 的 1× / 2× 备份尺寸、关掉扩展层与 `zoomMinimapBy` / `setMinimapZoom`,以及 `extentFollowCamera` 默认关闭 / 打开生效、缩放关闭时 `dragAction: 'box'` 仍可框选、`zoomable: 'follow'` 跟随主图且 `setMinimapZoom` 不抢控制权;批量复制 / 合并、通道拐点 / 分组两页另有假接口断言(Shift 框选、整份请求体、标准响应包与资源级结果、版本号 / opId、200 / 网络失败 / 500 / 409、原 opId 重试与幂等重放不推进版本、409 rebase、本地回滚);假后端严格契约另有一组直接断言(请求头与 body 不一致返回 400、baseVersion 非有限数字返回 400、同 opId 换 body 返回 409 `IDEMPOTENCY_CONFLICT` 且版本不前进),通道页还验了自定义格式按钮注册 → 导出 → 注销;扩展能力页另验自定义运算符 / 图元 / `view.use` 插件 / `defineExportFormat` / 实例命令的注册、执行、同名覆盖、卸载与业务 DTO 结构,并验同名重新注册图元当场换画笔;可视化连线页另验初始 `channels` 为空、正交折线拐点、预览箭头、取消不写数据,默认提交 / `addLines` / `addChannels` 三种落地,指定起点后一次点击即可 ready、两端示例点的显隐 / 尺寸 / 形状 / 分端颜色与 `below-points` 层级、`commitAddChannel` 直接传线路 / 通道对象(含对象 path / 颜色 / 线宽优先);动态装饰器页另验吸附方式只有「跟随 / 固定」、两档下位置都有「相对节点 / 固定位置 / 鼠标位置」三档,固定 + 相对节点只改 `anchor`,固定 / 跟随 + 鼠标位置分别映射为 `placement: pointer` + 对应 `anchor` 且不带 `at`,跟随 + 固定位置仍传 `at`;通道与数据页另验「线路分组」弹框:分组卡片与「场站对标标识」都在,行内「确认移入」只改本地草稿、组件数据不动,取消 / Esc / 遮罩关闭不写数据,「确定分组」只调用一次 `splitChannelGroups` 把整份 groups 一次落地。点聚合页另验配置控件、聚合点击只增聚合计数、`maxZoom` 展开与适应全部后恢复、半径 / 重建 / 关闭统计;接口能力页另验三种级联触发独立开关、通道 / 分组 / 拐点程序化选中、通道 `focusOn`、`lineFollowsNode` 联动,以及样式字段合并、节点 / 线路装饰 CRUD、通道线宽与线路对账、GeoJSON 背景投影、历史 / 复制 / 删除收尾、`once`、WebGL 层清理、注册表注销与初始化 options 读数;页面底部流式打印 PASS / FAIL。鹰眼图线路层另有 3 条断言:首帧只铺固定范围里的线路、同一份数据补画命中缓存、小图缩放只重铺一次且仍裁剪范围外线路。
|
|
120
120
|
- `selftest-effects.html`:线路 / 节点动画与贴图预热的专项自测(96 条,页面自带「帧泵」—— 无头环境里 `requestAnimationFrame` 几乎不推进,它把 rAF 收进 Map、手动推帧,动画类断言才测得动):纯函数组(归一化、三层「关」的语义、按弧长采样、拐点连续、相位打散)、引擎组(铺批次 / 关掉一层 / 恢复 / 逐要素覆盖 / `getLineEffects` 全程贴线)、像素组(画布上真的有一个红点在两帧之间挪了位置)、**涟漪跟手组**(开着动画拖节点:拖动前圆心在节点坐标上、拖动中跟着走且是就地改实例缓冲、松手后整份重建也对齐、再拖回原位跟着回来,另有一条像素级断言量绿圈质心落在新坐标上)、**预热组**(全排列条数、第二次命中缓存、`getSymbolCombos()` 的去重与线路侧那一条、照着它配的一份**零张新画布**、反向一条「没画过的组合会当场烤、画布 +1」)、提醒组(`effects.line` 里写 `show` 被点名);这一轮另补 4 条动点缓存断言(5000 条普通线 + 2 条动点线:每帧只遍历动画线路,路径换短 / 换长后总长跟着更新)。
|
|
121
121
|
- `selftest-data.html`:标准数据文档与渲染后端开关的自测(103 条)—— `normalizeDocument` 的标准写法与 JSON 安全(无效日期写 `null` 并告警、类实例不静默变成空对象)、整份文档直接当 `createGisView` 入参、`exportData({ format: 'full' })` 导出 → 重建 → 再导出逐字节相同、`importData` 的整份与分列、`renderer` 三档与能力探测、网格顶点缓存窗口,以及自定义导出格式的注册 / 上下文 DTO 映射 / 深拷贝 / 视口导出 / 注销后失效;断言全在接口层,不读像素。
|
|
122
122
|
- `check-sample-fields.mjs`:不是页面、是对账脚本(`node docs/steps/check-sample-fields.mjs`)—— 核对 `sample-document.json` 自己就是标准写法(`normalizeDocument` 后逐字节相同、0 条提醒)、`docs/sample-document字段详解.md` 里的读数与 `documentCounts()` 一致、以及它的「字段索引」与从文件里摘出来的字段路径集合一个不多一个不少(当前 154 个);改完样本数据跑 `--write` 重写那两段机器块(幂等)。
|
|
123
|
-
- `selftest-docs.html`:三个文档页 + 调试页的自测(134 条)—— 文档中心(`docs/index.html`)的清单 92 份 md(10 个根 + 82 份子文档,含新的 `接口协议.md`;树形侧栏按 `parent` / `groupLabel` 分成 6 组 `details`)、首屏 README 渲染、markdown 渲染器(表格 / 带语言代码块 / 两级列表 / 引用 / 行内代码 / md 链接 / 目录 / 首条摘要卡片 / 首段导语)、元信息里的阅读时长、索引页的子文档链接、换一份子文档、代码块复制按钮、跨文档搜索与深链、跨文档锚点链接(`文件.md#小节` 原地切文档并闪一下)、能直接跑的围栏才有的「在调试页跑」与元信息行的「这一份的调试页(N 段示例)」、390px 窄屏首屏 / 深链 / 横向溢出回归、阅读百分比与「回到顶部」的联动;调试演示页(`docs/playground/index.html`)的一个示例下拉(内置示例 + 「这一份文档」两组 optgroup)、`?doc=` 读文档抽示例、片段标注与来源子栏、顶层 await 运行、`channels` 快照、光标行列、草稿自动保存与恢复、`Ctrl+S` 运行、复制 / 清空输出、状态胶囊;API 速查页(`docs/api/index.html`)的 `__gisApi` 钩子、六张统计卡、
|
|
123
|
+
- `selftest-docs.html`:三个文档页 + 调试页的自测(134 条)—— 文档中心(`docs/index.html`)的清单 92 份 md(10 个根 + 82 份子文档,含新的 `接口协议.md`;树形侧栏按 `parent` / `groupLabel` 分成 6 组 `details`)、首屏 README 渲染、markdown 渲染器(表格 / 带语言代码块 / 两级列表 / 引用 / 行内代码 / md 链接 / 目录 / 首条摘要卡片 / 首段导语)、元信息里的阅读时长、索引页的子文档链接、换一份子文档、代码块复制按钮、跨文档搜索与深链、跨文档锚点链接(`文件.md#小节` 原地切文档并闪一下)、能直接跑的围栏才有的「在调试页跑」与元信息行的「这一份的调试页(N 段示例)」、390px 窄屏首屏 / 深链 / 横向溢出回归、阅读百分比与「回到顶部」的联动;调试演示页(`docs/playground/index.html`)的一个示例下拉(内置示例 + 「这一份文档」两组 optgroup)、`?doc=` 读文档抽示例、片段标注与来源子栏、顶层 await 运行、`channels` 快照、光标行列、草稿自动保存与恢复、`Ctrl+S` 运行、复制 / 清空输出、状态胶囊;API 速查页(`docs/api/index.html`)的 `__gisApi` 钩子、六张统计卡、264 张成员卡都带签名网格与示例、96 个 options 四列网格、搜索过滤与命中读数、`select(名字)` 高亮、类型开关能单独收起属性、左侧当前分组高亮与即时跳转、顶栏文档深链、每个分组的手册章节链接、深浅主题切换与阅读设置共用;图解页(`docs/guide/sample-document.html`)的 47 条数据列表、点线路 / 分组 / 标签状态的图上标注、`点击时居中` 与 `显示编号` 开关、清空标注;三个页面都跑在 iframe 里,要用 dev server 打开。
|
|
124
124
|
|
|
125
125
|
上一轮补上的两组断言:`selftest-features.html` 新增 8 条 —— 3 条验 `channelSelectMode` 在 Ctrl + 点击加点后的高亮范围(`line` / `group` / `channel`),5 条验分组 `channelWaypointDragMode: 'vertex'`(`{ x, y }` 偏移归一化并参与实际路由、同组同序号拐点按世界增量平移、相邻拐点与其它分组不动、只写 `waypointOffsets` 不改公共 `path`、`parallel_groups.group_route` 带刚性偏移)。`selftest-demos.html` 新增 1 条,验通道演示页可切到 `vertex`,且「准备两分组拐点」会拆成两组、每组两个拐点。
|
|
126
126
|
|