gis-view-webgl2 0.1.2 → 0.1.4

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 (66) hide show
  1. package/README.md +21 -14
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +421 -76
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +151 -0
  7. package/docs/GisView.md +4 -4
  8. 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 +137 -0
  9. package/docs/api/index.html +23 -9
  10. package/docs/demos/ApiCoverageDemo.vue +644 -0
  11. package/docs/demos/ChannelDemo.vue +14 -12
  12. package/docs/demos/ClusterDemo.vue +251 -0
  13. package/docs/demos/CopyMergeDemo.vue +3 -4
  14. package/docs/demos/EventDemo.vue +3 -3
  15. package/docs/demos/ExtensionDemo.vue +69 -12
  16. package/docs/demos/GisViewDemo.vue +64 -8809
  17. package/docs/demos/SearchFilterDemo.vue +13 -11
  18. package/docs/demos/api-index.js +58 -47
  19. package/docs/demos/demos.js +34 -17
  20. package/docs/demos/gis-view-demo/GisViewDemoCardHeader.vue +29 -0
  21. package/docs/demos/gis-view-demo/GisViewDemoDialogs.vue +82 -0
  22. package/docs/demos/gis-view-demo/GisViewDemoFooter.vue +16 -0
  23. package/docs/demos/gis-view-demo/GisViewDemoHeading.vue +16 -0
  24. package/docs/demos/gis-view-demo/GisViewDemoPanelStack.vue +71 -0
  25. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +219 -0
  26. package/docs/demos/gis-view-demo/demo.css +1230 -0
  27. package/docs/demos/gis-view-demo/demoContext.js +12 -0
  28. package/docs/demos/gis-view-demo/panels/DataPanels.vue +303 -0
  29. package/docs/demos/gis-view-demo/panels/OpsPanels.vue +174 -0
  30. package/docs/demos/gis-view-demo/panels/StylePanels.vue +561 -0
  31. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +396 -0
  32. package/docs/demos/gis-view-demo/useGisViewDemo.js +6188 -0
  33. 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 +3 -2
  34. package/docs/index.html +41 -4
  35. package/docs/internals/16-03-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/213.md +1 -1
  36. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +3 -2
  37. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +104 -16
  38. package/docs/manual/04-/344/272/213/344/273/266/344/270/216/345/233/236/350/260/203.md +15 -14
  39. 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 +40 -3
  40. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +50 -3
  41. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +6 -2
  42. 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 +39 -7
  43. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +13 -4
  44. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +3 -0
  45. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +121 -49
  46. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +37 -3
  47. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +17 -8
  48. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +45 -25
  49. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +91 -20
  50. 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 +3 -3
  51. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +25 -1
  52. 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 +11 -8
  53. package/docs/steps/README.md +17 -11
  54. package/docs/steps/selftest-actions.html +3 -5
  55. package/docs/steps/selftest-demo.html +27 -18
  56. package/docs/steps/selftest-demos.html +409 -32
  57. package/docs/steps/selftest-docs.html +33 -4
  58. package/docs/steps/selftest-effects.html +200 -0
  59. package/docs/steps/selftest-features.html +603 -21
  60. package/docs/steps/selftest-labels.html +74 -0
  61. package/docs/steps/selftest-perf.html +293 -20
  62. package/docs/steps/selftest-scale.html +150 -29
  63. package/docs/steps/selftest-select.html +90 -0
  64. package/docs/steps/step-10-full.js +1 -1
  65. package/docs/steps/step-12-events.js +1 -1
  66. package/package.json +1 -1
@@ -30,6 +30,39 @@ createGisView(container, {
30
30
 
31
31
  `clickable: false` 的要素既不会悬停高亮也不会被点中;`options.clickable: false` 则整体关闭拾取。
32
32
 
33
+ ## 点击级联高亮(cascadeHighlight)
34
+
35
+ 级联高亮是**另一份状态**,不走选中集:点击节点时算出它自己和直接相连的线路 / 另一端节点;点击线路或通道回线时只算这条线的两端节点。它默认关,`cascadeHighlight: true` 等价于 `{ scope: 'both', triggerOnClick: true, triggerOnMultiSelect: true, triggerOnBoxSelect: false }`;`scope: 'points'` 只亮节点,`both` 连直连线路一起亮。
36
+
37
+ ```js
38
+ const view = createGisView(container, {
39
+ points, lines, channels,
40
+ clickable: true,
41
+ cascadeHighlight: {
42
+ scope: 'both',
43
+ triggerOnClick: true,
44
+ triggerOnMultiSelect: true,
45
+ triggerOnBoxSelect: false,
46
+ pointStyle: { color: '#fff7ed', strokeColor: '#f59e0b', strokeWidth: 2, sizeScale: 1.2 },
47
+ lineStyle: { color: '#f59e0b', width: 2.6, opacity: 0.95 },
48
+ clearOnEmptyClick: true
49
+ },
50
+ onCascadeHighlight: payload => console.log(payload?.origin, payload?.pointIndexes, payload?.lineIndexes)
51
+ })
52
+
53
+ view.getCascadeHighlight() // { origin, scope, points, lines, pointIndexes, lineIndexes } 或 null
54
+ view.updateOptions({ cascadeHighlight: { scope: 'points' } })
55
+ view.updateOptions({ cascadeHighlight: false }) // 关掉并收掉当前高亮
56
+ ```
57
+
58
+ - `pointStyle` / `lineStyle` 是级联专用的两套状态样式,缺省为琥珀色描边 / 线条,不需要先配 `pointSelectStyle` / `lineSelectStyle`。它们只作用于**被级联的邻接项**:直接选中的节点 / 线路仍走 `pointSelectStyle` / `lineSelectStyle`(多选 / 框选各走自己的那一档);没配选中样式时保持普通外观,也不会回落到级联样式;只有 `defaultHighlight: true` 时才补上内置选中强调。
59
+ - 触发方式三个开关彼此独立:`triggerOnClick` 管普通单击(缺省 `true`),`triggerOnMultiSelect` 管 Ctrl / ⌘ 累加点击与 `multiSelect`(缺省 `true`),`triggerOnBoxSelect` 管框选 / `selectInBox()`(缺省 `false`)。某一路关掉时,该操作只改变选中集,并把旧级联收掉;另一路仍可正常触发。
60
+ - 框选触发时,框内每个命中项都参与邻接计算,取并集后一次派发;`origin` 取框内按数据顺序的第一个命中项作代表。框内直接命中的项走 `boxSelectStyle` / `lineSelectStyle`,框外的邻接节点 / 线路才套级联样式。
61
+ - 点空白处是否收掉由 `clearOnEmptyClick` 控制,缺省 `true`。收掉、首次点亮、换节点 / 线路 / 通道或改范围都会发 `onCascadeHighlight` 与总线事件 `cascadeHighlight`;读数与回调收到的是同一份对象,收掉时是 `null`。
62
+ - `origin.target` 为 `point` / `line` / `channel`:通道回线在底层仍是线路,但 `origin` 会另带 `channelId` / `channelIndex` / `groupId` / `groupIndex` / `channel`,方便直接回显“点的是哪条通道的哪一组”。
63
+ - 数据整份更新后会按新的邻接关系刷新;来源被删除、被隐藏到不可拾取,或 `cascadeHighlight` 关掉时,高亮自动收掉。它不写 `selectionKeys`,`getSelectionData()` / `getSelections()` 不会因为级联高亮多出条目。
64
+ - 综合 Demo(`?demo=full`)的「视图 · 交互 → 交互开关与快捷键」面板已接入这项能力:可现场开关、在「节点 + 直连线路 / 只亮节点」之间切换,并直接读 `getCascadeHighlight()` 的来源、范围与节点 / 线路条数。
65
+
33
66
  ## 指针事件
34
67
 
35
68
  鼠标的每一种动作都有回调,载荷里都能拿到「当前是哪个元素」:`onHover`(悬停)、`onClick`(单击左键)、`onDblClick`(双击)、`onPointerDown` / `onPointerUp`(按下与抬起,含左 / 中 / 右键)、`onContextMenu`(右键)。悬停是 `GisViewHoverPayload`、点击与双击是 `GisViewClickPayload` —— 都是在 `GisViewPickResult`(点 / 线 / 通道回线的 `type`、`index`、`item`、`screen`、`coordinate` 与通道字段)上再补光标位置与一份 `action`;按下 / 抬起 / 右键统一发 `GisViewPointerPayload`(同样是拾取结果 + `action`)。
@@ -95,13 +128,13 @@ createGisView(container, {
95
128
  })
96
129
  ```
97
130
 
98
- - 载荷形状一致,取信息只需认三处:`type`(`'point'` / `'line'` / `null`)、`item`(要素原始数据,可读业务字段)、`world` / `coordinate`(前者是光标处的世界坐标,后者是要素自身的坐标)。命中通道回线时,`channelIndex` / `channelId` / `groupId` / `groupIndex` / `channel` 一并给出,等价于 `onChannelContextMenu` 里的那一份。
131
+ - 载荷形状一致,取信息只需认三处:`type`(`'point'` / `'line'` / `null`)、`item`(要素原始数据,可读业务字段)、`world` / `coordinate`(前者是光标处的世界坐标,后者是要素自身的坐标)。命中通道回线时,`channelIndex` / `channelId` / `groupId` / `groupIndex` / `channel` 一并给出;右键载荷还会在 `channelContext` 上带整份分组上下文。
99
132
  - 配了 GeoJSON 背景时,以上载荷都多一个 `lngLat`:按光标的世界坐标(要素命中用 `coordinate`,悬停用最近一次光标位置)反算出来的真实经纬度,点 / 线 / 通道都一样;想自己换算就用 `worldToLngLat` / `screenToLngLat`,光标级别的屏幕坐标 + 世界坐标 + 经纬度看 `onPointer` 与 `getPointer()`。
100
133
  - `onPointerDown` / `onPointerUp` 不改变组件行为:选中、拖拽、框选、拐点编辑照旧。左键的一次「点」(没有拖动)仍然走 `onClick` / `onDblClick`,两者是并列的,不会互相吞掉 —— 载荷里的 `action.type` 各说各话:按下是**意图**、松开是**刚做完的那件事**(`action.moved` 说明是拖还是点)、点击是**真正发生的那一件**。
101
134
  - 点空白处照发:`onPointerDown` / `onPointerUp` 的 `action.target` 是 `empty`(开了平移时按下是 `pan` 的意图),`onClick` / `onDblClick` 的载荷是 `null`(选中在那一刻已经清空)——「点了空白」这件事也可以看 `onSelectChange` 的 `action.type: 'clearSelection'`。
102
135
  - **中键按住就是临时平移**(`pan` 开关开着时):`onPointerDown` 的 `action.type` 是 `pan`、`action.button` 是 `'middle'`,按住拖动平移画布、松开结束(`onPointerUp` 的 `action.type` 也是 `pan`);这一路**不经过** `onClick`,中键也不会改选中 —— 想自己接中键(中键菜单 / 快捷操作)就按 `action.button === 'middle'` 认它。长按(触摸 / 触控笔)与中键一样有明确的读数:看松开 / 点击载荷的 `action.longPress` 与 `action.press` / `action.duration`。
103
- - `onContextMenu` 每次都发;命中通道回线时**额外**再发一条 `onChannelContextMenu`(分组上下文,见「通道右键交互框」)。**右键缺省整个不接**(`contextMenu` 开关没开时系统菜单照弹、回调也不发);接住之后一律 `preventDefault()` 收掉系统菜单(点 / 线 / 空白 / 通道都一样),回调照常发。想自己弹菜单就写 `interactive: true` / `contextMenu: true` / `features: 'view'`,想让系统菜单自己弹出来就写 `preventContextMenu: false`。
104
- - 命中的层级是**点 → 线 → 通道**:右键落在节点上时只发 `onContextMenu`(`type: 'point'`),`onChannelContextMenu` 收到 `null` —— 节点压在通道回线上(端点最常见)不会被当成通道右键。
136
+ - `onContextMenu` 每次都只发这一条;命中通道回线时,分组上下文挂在同一份载荷的 `channelContext` 上(见「通道右键交互框」),不再额外发第二个回调。**右键缺省整个不接**(`contextMenu` 开关没开时系统菜单照弹、回调也不发);接住之后一律 `preventDefault()` 收掉系统菜单(点 / 线 / 空白 / 通道都一样),回调照常发。想自己弹菜单就写 `interactive: true` / `contextMenu: true` / `features: 'view'`,想让系统菜单自己弹出来就写 `preventContextMenu: false`。
137
+ - 命中的层级是**点 → 线 → 通道**:右键落在节点上时载荷是 `type: 'point'`,没有 `channelContext` —— 节点压在通道回线上(端点最常见)不会被当成通道右键;点空白处同样没有 `channelContext`,可以直接用它收框。
105
138
  - 悬停是高频率回调:大数据量下建议只做节流的读数或轻量样式切换(demo 里的读数就是这么做的)。
106
139
 
107
140
  - 命中的要素**在不在选中集里**、以及整份选中集有多少,载荷里直接给:`selected`(这一刻在不在选中集里)、`wasSelected`(`onClick` 给的是「点之前」的那一份,双击同理;右键与按下 / 松开都是「当下」,此时与 `selected` 相同)、`selection`(整份选中集的读数 `{ ids, count, points, lines, channels }`,通道回线按通道算一个)。左键 / 右键要自己接一套操作(按命中分支、弹自己的面板、决定要不要保留多选)时,看这三项就不用再去比对 `getSelectionData().ids`:
@@ -558,6 +591,8 @@ onPointMerge: async payload => {
558
591
 
559
592
  接口调用不做确认,直接按规则合并:`view.mergePoints('a', 'b')`(来源、目标收 id、下标或 `{ id }` / `{ index }`)。合并的数据规则是:
560
593
 
594
+ 这里的「合并样式」不是另一项样式配置,也不是新的 API;它指的是**合并后保留节点最终写进数据的 `size` 与 `symbol`**。Demo 的读数用它回答“合完会长什么样、用哪个图标”:尺寸取双方较大值、图标优先目标节点。它决定合并结果的外观,需要自定义时改 `merge.createItem(...)` 的返回值或 `payload.item` 即可。
595
+
561
596
  - 节点数据缺省按「被并入的一端在前、保留端在后」合并(保留端优先,另一端独有的字段带过来),坐标与 id 取保留端;拖动合并确认里缺省保留碰撞目标,`keep: 'source'` 时保留拖动的来源节点;
562
597
  - **图标大小取双方的最大值**:高电压往低电压合,原来的图标跟着变大(结果写成节点自己的 `size`,`sizeScale` 归 1);**图标类型优先目标节点**,目标没配符号才用来源的;
563
598
  - 想自己决定就用 `merge.createItem(source, target, context)`,`context` 里带双方的 `sourceStyle` / `targetStyle`(`{ index, size, symbol, zIndex }`,样式链定下来的那一份,不含状态样式与热力倍率);
@@ -689,4 +724,6 @@ const view = createGisView('#map', {
689
724
 
690
725
  ---
691
726
 
727
+ **相关章节**:[03 配置项(options)](03-配置项.md) · [08 选中与拐点](08-选中与拐点.md) · [14 视图与筛选](14-视图与筛选.md) · [17 参数速查](17-参数速查.md)
728
+
692
729
  [← 06 数据与场景更新](06-数据与场景更新.md) · [手册目录](../GisView.md) · [08 选中与拐点 →](08-选中与拐点.md)
@@ -34,7 +34,27 @@ createGisView(container, {
34
34
 
35
35
  拖动期间选框实时跟随,但事件只在松手时派发一次,不会拖动一帧报一次。**框选默认只收节点**,保持旧口径;写 `selectionKinds` 后可以把通道外的线路与通道回线一起纳入(三类任意组合),见下节。选中结果用 `getSelections()` / `getSelectionData().ids` 读回。用代码框选等价于调用 `selectInBox({ x, y, width, height })`,矩形同样是画布 CSS 像素;第二段还能临时覆盖类型过滤。框选命中的节点套用框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),缺省与选中样式完全一致;框选出来的线路走普通的 `lineSelectStyle`(线路没有单独的框选样式)。
36
36
 
37
- 不想拖动时也可以用 Ctrl(macOS 为 Cmd)逐个点击节点累加选中,命中已选中的节点会把它取消掉;两种方式得到的选中集合一样,之后按住组内任意一个节点拖动就是整组移动。不想按修饰键就把多选开关打开(`options.multiSelect: true`,运行时用 `updateOptions({ multiSelect })` 切):普通单击即取反累加,线路也认(受 `channelSelectMode` 的整组 / 整条摊开约束);双击的第二次点击不再取反,`onDblClick` 照常派发。多选命中的节点走多选样式(`options.pointMultiSelectStyle`,节点自己的 `multiSelectStyle` 优先),框选命中的走框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),两者缺省都与选中样式完全一致。注意框选样式只给节点;选中线路 / 通道回线时仍按 `lineSelectStyle` / `lineMultiSelectStyle` 上色。
37
+ 不想拖动时也可以用 Ctrl(macOS 为 Cmd)逐个点击节点累加选中,命中已选中的节点会把它取消掉;两种方式得到的选中集合一样,之后按住组内任意一个节点拖动就是整组移动。不想按修饰键就把多选开关打开(`options.multiSelect: true`,运行时用 `updateOptions({ multiSelect })` 切):普通单击即取反累加,线路也认(受 `channelSelectMode` 的整组 / 整条摊开约束);单击触发模式下双击的第二次点击不再取反,`onDblClick` 照常派发。多选命中的节点走多选样式(`options.pointMultiSelectStyle`,节点自己的 `multiSelectStyle` 优先),框选命中的走框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),两者缺省都与选中样式完全一致。注意框选样式只给节点;选中线路 / 通道回线时仍按 `lineSelectStyle` / `lineMultiSelectStyle` 上色。
38
+
39
+ ### 点击选中与 dblClick
40
+
41
+ `selectTrigger` 缺省是 `'click'`:命中的节点 / 线路在第一次单击时就进选中集,点空白也在第一次单击时清空。写 `selectTrigger: 'dblClick'` 后,第一次点击只派发 `onClick`(`action.type` 是 `'click'`),同一位置 320ms 内、6px 范围内的第二下才应用选择结果:
42
+
43
+ - 点要素:选中它;`multiSelect` 或按住 Ctrl / ⌘ 时取反累加。第二下同时派发 `onClick`(`action.double: true`,取反时 `action.type` 为 `'toggleSelect'`,普通选中为 `'select'`)与 `onDblClick`(`action.type: 'dblClick'`)。
44
+ - 点空白:第二下才清空选中集并派发 `onDblClick`。
45
+ - `click` 模式下普通双击的 `onDblClick` 也照常派发,只是第一次单击已经完成了选中;Ctrl / `multiSelect` 的取反快捷路保持原口径(不额外发 `onClick` / `onDblClick`)。两种模式都不影响拐点双击删除与通道草稿等既有双击交互。
46
+
47
+ ```js
48
+ createGisView(container, {
49
+ features: { select: true },
50
+ selectTrigger: 'dblClick',
51
+ onDblClick: payload => panel.open(payload) // 第二下必发;payload.action.double === true
52
+ })
53
+
54
+ view.updateOptions({ selectTrigger: 'click' }) // 运行时切回单击选中
55
+ ```
56
+
57
+ 运行时切换 `selectTrigger` 会清掉上一次点击的双击判定状态,切换后的第一下不会被算成旧点击序列的第二下。`selectFeatures()` / `select()` 这类接口选中不受触发方式影响。
38
58
 
39
59
  ### 选择类型过滤(selectionKinds)
40
60
 
@@ -74,6 +94,8 @@ view.selectFeatures(['station-1', { type: 'point', index: 5 }])
74
94
  // ② 通道:整条通道的回线 / 通道里的某一组 / 通道里的某几条回线
75
95
  view.selectFeatures({ type: 'channel', channelId: 'channel-a' })
76
96
  view.selectFeatures({ channelId: 'channel-a', groupId: 'g-2' })
97
+ view.selectFeatures({ type: 'group', channelId: 'channel-a', groupId: 'g-2' })
98
+ view.selectFeatures({ type: 'group', groupId: 'g-2' }) // groupId 全局唯一时可省略通道
77
99
  view.selectFeatures({ channelId: 'channel-a', lineIds: ['channel-channel-a-0', 'channel-channel-a-1'] })
78
100
  view.selectFeatures({ channelIndex: 0, lineIndexes: [3, 4] })
79
101
 
@@ -90,6 +112,15 @@ view.selectFeatures({ type: 'channel', channelId: 'channel-a' }, { mode: 'remove
90
112
 
91
113
  拾取载荷(`pickAt` 或指针事件里的那一份)与搜索命中里可能带着 `channelId` / `channelIndex` / `groupId` —— 那是「这条回线属于哪条通道」的上下文,不是「要选整条通道」:它们的 `type` / `kind` 写着 `line` / `point`,落地选中的就是那一条要素自己(与在画布上点它一个结果)。要整条通道,显式写 `{ type: 'channel', channelId: 'channel-a' }`。
92
114
 
115
+ 聚焦同一套目标写法交给 `focusOn`,显式 `type` 还能解决节点与线路同 id 的歧义;整条通道可以写 `{ channelId }`,分组写 `{ type: 'group', channelId?, groupId }`(`groupId` 全局唯一时可省略 `channelId`)。节点、线路、通道与分组的取范围与缩放都走 `zoomToBounds`,不会因为类型不同换一套放大倍率:
116
+
117
+ ```js
118
+ view.focusOn({ type: 'point', id: 'shared-id' })
119
+ view.focusOn({ type: 'line', id: 'shared-id' })
120
+ view.focusOn({ type: 'channel', channelId: 'channel-a' })
121
+ view.focusOn({ type: 'group', channelId: 'channel-a', groupId: 'g-2' })
122
+ ```
123
+
93
124
  读回选中用 `getSelectionData()`,一次拿全(单击、多选、框选、接口选中都是这一份;只要下标与要素引用时用 `getSelections()`):
94
125
 
95
126
  ```js
@@ -104,8 +135,9 @@ selection.lines // 选中的线路数据(通道回线也算一条)
104
135
  selection.channels // 选中回线所属的通道(按通道去重,同一条通道的几回线算一条)
105
136
  selection.items // 与 getSelections() 同一份:带下标、屏幕坐标与通道上下文
106
137
  selection.bounds // 世界包围盒(空选中时 null),直接交给 zoomToBounds / focusOn
107
- view.focusOn(selection.items) // 把视角挪到这一批上(整体居中 + 按范围适配)
108
- view.focusOn(selection.items, { select: true }) // 反过来也行:聚焦的同时把它们选上
138
+ view.focusOn(selection.items) // 把视角中心挪到这一批(比例不动)
139
+ view.focusOn(selection.items, { zoom: true }) // 按这一批的整体范围适配
140
+ view.focusOn(selection.items, { select: true, zoom: true }) // 反过来也行:聚焦的同时把它们选上
109
141
  ```
110
142
 
111
143
  `mode` 只决定「这一批怎么并进当前选中集」,落地之后与手动选中完全一样(通道回线照旧按通道抬层,见上一节)。认不出来的条目跳过(与 `patchPoints` 的 `skipped` 一个态度)。按 id 定位用的是与 `patchPoints` 同一张 id 表(按数据版本缓存),`isSelected` 与 `selectFeatures` 的去重也都走常数级的键集合 —— 十万级数据下「按 id 选一批」是 O(选中数),不会每个 id 扫一遍全量。
@@ -133,6 +165,19 @@ view.selectFeatures('station-7', { mode: 'remove' }) // 与 deselect('sta
133
165
 
134
166
  写 `waypointEditable: true`(缺省关)后,线路的中间顶点会画出可交互的拐点:按住 Ctrl 点击线路在点击位置插入拐点,单击选中,拖动即移动顶点、线路随之变成折线,双击或选中后按 Delete 删除。节点很密时 Ctrl 点击会先比较点到节点与点到线路的屏幕距离:**线路更近就按加点处理**,比节点远才走 Ctrl 多选节点,所以贴着节点云也能给线路补拐点。通道回线加点后的高亮范围与普通点选共用 `channelSelectMode`:`'line'` 只高亮点中的一条、`'group'` 连这一组、`'channel'` 整条通道。
135
167
 
168
+ ### 代码选中拐点(selectWaypoint)
169
+
170
+ `selectWaypoint()` 走与点选同一份拐点状态:字符串 / 数字写法是「线路 id 或下标 + `pathIndex`」,对象写法是 `{ lineIndex, pathIndex }` 或 `{ lineId, pathIndex }`。`pathIndex` 用 `getWaypoints()` 返回的下标(首尾端点不算拐点)。选中成功时会把拐点所在线路一起选上,因此缺省 `waypointVisibility: 'selected'` 下拐点立即显示;`options.mode` 与 `selectFeatures` 一样决定线路怎么并进当前选中集(缺省换一份)。
171
+
172
+ ```js
173
+ view.selectWaypoint('line-a-b', 2)
174
+ view.selectWaypoint({ lineIndex: 0, pathIndex: 1 }, { mode: 'add' })
175
+ view.selectWaypoint({ lineId: 'line-a-b', pathIndex: 2 })
176
+ view.selectWaypoint(null) // 只取消拐点选中,不动要素选中集
177
+ ```
178
+
179
+ 找不到线路或下标不是可编辑拐点时不会凭空选中一个拐点;传 `null` 只清拐点高亮,原来的要素选中集保持不动。
180
+
136
181
  需要完全停掉拐点编辑时写 `features: { waypoint: false }` 或 `waypointEditable: false`:Ctrl + 点击、`addWaypoint` / `addWaypoints` / `addChannelGroupWaypoint`、通道右键框「加拐点」、拖动已有拐点以及双击 / Delete 删除都会一起变成无操作,数据与历史都不动。拐点只有这一道总闸,没有 `waypointAdd` 之类的细分开关;`shortcuts.addWaypoint` 只决定加点用哪个组合键,不改变总闸。运行时也可以 `view.setFeatures({ waypoint: false })`,`getFeatures().waypoint` 读当前有效值。
137
182
 
138
183
  拐点默认只在需要的时候出现:`waypointVisibility: 'selected'`(默认)下,只有被选中的线路(以及刚用 `addWaypoint` 加过拐点的线路)才画出拐点,点击空白处清空选中后拐点一起隐藏;改成 `'always'` 可以回到常显。`getWaypoints()` 返回当前显示中的拐点,`getWaypoints(lineIndex)` 只取某条线路的,拿到的是 `{ lineIndex, pathIndex, coordinate, selected }`。
@@ -210,4 +255,6 @@ view.getParallelGroups()
210
255
 
211
256
  ---
212
257
 
258
+ **相关章节**:[07 指针与要素编辑](07-指针与要素编辑.md) · [09 通道与多回线](09-通道与多回线.md) · [16 实例方法总表](16-实例方法总表.md) · [17 参数速查](17-参数速查.md) · [18 逐方法示例](18-逐方法示例.md)
259
+
213
260
  [← 07 指针与要素编辑](07-指针与要素编辑.md) · [手册目录](../GisView.md) · [09 通道与多回线 →](09-通道与多回线.md)
@@ -4,7 +4,7 @@
4
4
 
5
5
  ## 通道与多回线
6
6
 
7
- 一个通道表示一条走廊里并行敷设的多条回线(二 / 三 / 四 / 五回线都行)。回线本身照常写进 `lines`,通道只描述它们怎么排布。通道两端可以绑在节点上:声明 `from` / `to` 之后,**两端必须有节点,通道才显示**——骨架的首尾顶点就是这两个节点的坐标,拖动节点整条通道跟着走,节点被删掉或设成 `visible: false` 时通道与它的回线一起消失。
7
+ 一个通道表示一条走廊里并行敷设的多条回线(二 / 三 / 四 / 五回线都行)。回线本身照常写进 `lines`,通道只描述它们怎么排布。通道两端可以绑在节点上:声明 `from` / `to` 之后,**两端必须有节点,通道才显示**——骨架的首尾顶点就是这两个节点的坐标,拖动节点整条通道跟着走,节点被删掉或设成 `visible: false` 时通道与它的回线一起消失。写 `lineFollowsNode: false` 时例外:节点退场只影响节点,通道与回线继续停在最后一次解析出的骨架与宽度,见「筛选」与「配置项」。
8
8
 
9
9
  ### 通道显隐(visible)
10
10
 
@@ -161,11 +161,13 @@ console.log(view.getChannels()) // 骨架、每条回线的偏移与推导出来
161
161
 
162
162
  两端锚定的通道还会把切点往节点里收一段:缺省按成员回线里最粗那条的线宽收(切点从节点边缘再往节点里挪一个线宽),所以线不会从节点边缘探出去;`options.channelTangentInset` 或单个通道的 `tangentInset` 可以改成固定的 CSS 像素(`0` 表示切点正好落在节点边上),和线宽一样会随相机缩放折算回世界单位。内收两侧合计最多吃掉节点宽度的 40%,小节点配粗线时不会把回线挤成一条;写成负数就是**外收**:切点从节点边缘往外让一段,整组回线比节点还宽,单侧最多让出节点宽度的 50%。
163
163
 
164
+ 筛选只改变节点自身是否绘制,不改变仍显示的通道宽度:被筛掉的端点仍按 `points` 里的数据和同一条样式链取外框(点自身 > 规则 > `pointStyle`,含热力倍率),所以只筛掉一端时不会改按另一端尺寸算,两端都被筛掉时也不会退回 `width` / `channelWidth`。只有节点真被删除或写了 `visible: false` 时才按「没有节点」处理。
165
+
164
166
  通道的配置项没有重复的,成对出现的都是「**全局默认 + 逐通道覆盖**」:`channelWidth` / `width`、`channelNodeWidth` / `nodeWidth`、`channelInset` / `inset`、`channelTangentInset` / `tangentInset`,单个通道不写就取 `options` 上的同名值;`groups` / `path` / `from` / `to` 分别管分组、骨架与两端锚定,各管一件事(`path` 是可选骨架,不写时由锚点或第一条回线补出)。要留意的是各自的**生效条件**(不是冗余,而是只在特定组合下才有意义):`offsetMode: 'tangent'`(默认)时 `inset` / `channelInset` 不参与计算;`nodeWidth` 不为 0 且两端都锚在节点上时 `width` 只作兜底(宽度按节点尺寸算,改它看不到变化);`tangentInset` 只对两端锚定的通道有效(没锚定时切点就是骨架端点,无所谓往哪收);只有一条回线的通道不排偏移(回线就是骨架),`nodeWidth` 与 `tangentInset` 对它的回线位置没有影响。
165
167
 
166
168
  拐点挂在骨架上:`'parallel'` 模式下拖动通道内任一回线的拐点,整条通道的所有回线一起折线,回线之间保持平行等距;`'vertex'` 模式下只平移当前分组同一序号的拐点(见「分组拐点拖动模式」)。`insertWaypoint` / `removeWaypoint` 增删的也是骨架顶点(首尾两端由节点或原点决定,删不掉)。拐点只显示选中线路的规则对通道一样适用:通道内**任一回线被选中,整条通道的拐点都显示**,这样拖动时能看清整条走廊。拖动不做任何限制:拐点可以拖到两端节点之外、折回去、拐到任意一侧,拖动端点节点时中间拐点也保持原样,骨架怎么走回线就怎么平行偏移(`channelPathFor` 就是骨架的纯平行偏移)。`onWaypointChange` 载荷里的 `coordinate` 与 `line` 都是被操作的那条回线的最新几何(`line.path` 是推导后的折线);通道分组还要一起保存 `waypointOffsets`,只写 `line.path` 或只写公共 `path` 会丢掉 `'vertex'` 拖动结果。`getChannels()` 返回运行时状态:`{ id, channelId, channelIndex, lineIndexes, skeleton, waypointOffsets, offsets, paths, from, to, anchored, spacing }`——`anchored` 为 `true` 表示两端都落在节点上,`spacing` 是按两端节点尺寸算出来的相邻回线中心间距(分组之后 `offsets` 是这一组在整条通道里的那几条车道,`spacing` 仍是整条通道的间距),需要自有覆盖层或回写业务数据时用它。
167
169
 
168
- 两端锚定的通道与节点是联动的:拖动端点节点,骨架首尾跟着走(中间拐点保留),所有回线一起重算;节点被删掉或设成不可见时,通道与它的回线都从这一帧开始不参与渲染,节点回来后通道恢复,之前编辑过的拐点还在。
170
+ 两端锚定的通道与节点默认是联动的:拖动端点节点,骨架首尾跟着走(中间拐点保留),所有回线一起重算;节点被删掉或设成不可见时,通道与它的回线都从这一帧开始不参与渲染,节点回来后通道恢复,之前编辑过的拐点还在。`lineFollowsNode: false` 时只让节点退场,通道、回线与最后一份解析尺寸保持原样;节点仍在时拖动仍会实时更新通道几何。
169
171
 
170
172
  ### 通道分组
171
173
 
@@ -243,4 +245,6 @@ Demo 的「通道与数据」页直接接了两类假接口:通道分组拐点
243
245
 
244
246
  ---
245
247
 
248
+ **相关章节**:[03 配置项(options)](03-配置项.md) · [08 选中与拐点](08-选中与拐点.md) · [10 通道编辑与新增](10-通道编辑与新增.md) · [17 参数速查](17-参数速查.md)
249
+
246
250
  [← 08 选中与拐点](08-选中与拐点.md) · [手册目录](../GisView.md) · [10 通道编辑与新增 →](10-通道编辑与新增.md)
@@ -57,13 +57,14 @@ view.updateOptions({ channelWaypointDragMode: 'vertex' })
57
57
 
58
58
  ### 通道右键交互框
59
59
 
60
- 右键点中通道的回线,引擎把这条通道的上下文交给 `onChannelContextMenu`(**通道右键框缺省关**:右键本身要接得住 —— `contextMenu` / `interactive: true` / `features: 'view'` 任一 —— 再加 `features: { channelMenu: true }`):
60
+ 右键点中通道的回线,引擎把这条通道的上下文挂在**同一份 `onContextMenu` 载荷**的 `channelContext` 上(不再有单独的 `onChannelContextMenu` / `channelMenu`)。右键本身要接得住:`contextMenu` / `interactive: true` / `features: 'view'` 任一即可:
61
61
 
62
62
  ```js
63
63
  view.updateOptions({
64
- onChannelContextMenu: (context, event) => {
64
+ onContextMenu: (payload, event) => {
65
+ const context = payload.channelContext
65
66
  if (!context) {
66
- return closeMenu() // 点空白处:收到 null,用它把框收掉
67
+ return closeMenu() // 点空白或节点:没有 channelContext,用它把框收掉
67
68
  }
68
69
  // 这条通道有几组、当前是哪一组、这一组有几条回线 / 几个拐点
69
70
  console.log(context.channelId, context.groupCount, context.group.index, context.group.lineCount, context.group.waypointCount)
@@ -83,17 +84,23 @@ view.getChannelContext()
83
84
  view.getChannelContext('corridor-1', 'corridor-1::2')
84
85
  ```
85
86
 
86
- - 载荷是 `{ channelId, channelIndex, channel, groupId, groupCount, groups, group, actions, lineIndex, lineId, coordinate, x, y, clientX, clientY, originalEvent }`:`groups` 是这条通道的全部分组读数(`{ id, channelId, index, lineIds, lineIndexes, lineCount, waypointCount, edited, anchored, current, skeleton, path }`;引擎的完整分组读数还带 `waypointOffsets`),`group` 是其中的当前分组,`groupCount` 是组数;`x` / `y` 是命中的画布 CSS 像素(`clientX` / `clientY` 是页面坐标),交互框照着摆就行。
87
+ - `payload` 仍是通用右键载荷(`action.type: 'contextMenu'`、`action.target: 'channel'`、`type: 'line'`、`channelId` / `groupId` / `lineIndex` / `lineId` / `coordinate` 等),`channelContext` 是 `{ channelId, channelIndex, channel, groupId, groupCount, groups, group, actions, lineIndex, lineId, coordinate, x, y, clientX, clientY, originalEvent }`:`groups` 是这条通道的全部分组读数(`{ id, channelId, index, lineIds, lineIndexes, lineCount, waypointCount, edited, anchored, current, skeleton, path }`;引擎的完整分组读数还带 `waypointOffsets`),`group` 是其中的当前分组,`groupCount` 是组数;`x` / `y` 是命中的画布 CSS 像素(`clientX` / `clientY` 是页面坐标),交互框照着摆就行。
87
88
  - `actions` 每项 `{ id, label, enabled, reason? }`:`enabled: false` 时 `reason` 说明原因(拐点编辑关了、这一组还没有拐点、已经是多组先合并再拆…),界面直接照着渲染按钮即可。
88
89
  - 六个动作 id:`select-group`(选中该分组)/ `deselect-group`(取消该分组的选中,等价于 `deselect({ channelId, groupId })`)、`add-waypoint` / `remove-waypoint`(给这一组加 / 删拐点,见上一节)、`split`(按 `options.size` 每组几条把通道拆成多组,只有一组且多于一条回线时可用)、`merge`(其余分组并回当前这一组)。
89
90
  - 更自由的分配走 `moveChannelLine(channelId, lineId, target)`:按**回线**粒度拆成新分组或并进别的分组 —— 两回线选「拆成新分组」就是两个分组各一条(见「通道分组」)。
90
- - 右键**落在节点上**时给的是 `null`:层级是点 → 线 → 通道,节点正压在回线端点是常态,这时该走 `onContextMenu` 的节点载荷(`type: 'point'`)。拾取到节点却弹出通道菜单是最容易被当成 bug 的一处。
91
- - 点在**空白处**(没命中通道回线)回调收到 `null`,正好用来收框;接住右键之后系统菜单一律被收掉(`preventContextMenu` 缺省 `true`,要留系统菜单就写 `false`;`contextMenu` 没开时组件根本不接右键,见「指针事件」)。右键的同时命中的那一组会被记成「当前分组」,`getChannelContext()` 不传参时取的就是它。
91
+ - 右键**落在节点上**时没有 `channelContext`:层级是点 → 线 → 通道,节点正压在回线端点是常态,这时载荷是 `type: 'point'`。拾取到节点却弹出通道菜单是最容易被当成 bug 的一处。
92
+ - 点在**空白处**(没命中通道回线)同样没有 `channelContext`,正好用来收框;接住右键之后系统菜单一律被收掉(`preventContextMenu` 缺省 `true`,要留系统菜单就写 `false`;`contextMenu` 没开时组件根本不接右键,见「指针事件」)。右键的同时命中的那一组会被记成「当前分组」,`getChannelContext()` 不传参时取的就是它。
92
93
  - 新增通道草稿期间右键等于取消草稿(发一条 `phase: 'cancel'`)。
93
94
 
94
95
  ### 新增通道
95
96
 
96
- `beginAddChannel()` 进入新增流程(**草稿缺省关**,写 `features: { channelDraft: true }` 或 `features: 'edit'` 才进得去;程序化的 `addChannel()` 不受限):点第一个节点当起点,一条草稿线跟着光标走(颜色 / 线宽 / 两端圆点由 `channelDraftStyle` 配),点第二个节点出方案表。**同一套流程用 `kind` 分两种**:`'channel'`(缺省)是**新建通道**,两个节点之间已经有通道就加一组 / 并进某一组;`'line'` 是**新建线路**,只加不挂任何通道的独立线路(`channels` 一个字节都不动,两个节点相同才不允许)。`circuits` 决定这一次加几条(通道回线 / 独立线路都一样)。草稿期间点空白处继续等(不会退出)、点起点同一个节点不算数、按住拖动仍然平移画布,Esc / 右键 / `cancelAddChannel()` 退出。
97
+ `beginAddChannel()` 进入新增流程(**草稿缺省关**,写 `features: { channelDraft: true }` 或 `features: 'edit'` 才进得去;程序化的 `addChannel()` 不受限):缺省点第一个节点当起点,一条草稿线跟着光标走(颜色 / 线宽 / 两端圆点由 `channelDraftStyle` 配),点第二个节点出方案表。**`options.from` 可以预指定起点**(节点 id / 下标 / `{ id }` / `{ index }`),调用后直接进入 `phase: 'from'`,只需再点终点;不写 `from` 就保持原来的两次点击。开始事件的 `from` 相位、`select: true` 时预置起点的选中状态都会同步给出;`from` 找不到时草稿不开始并报 warning。**同一套流程用 `kind` 分两种**:`'channel'`(缺省)是**新建通道**,两个节点之间已经有通道就加一组 / 并进某一组;`'line'` 是**新建线路**,只加不挂任何通道的独立线路(`channels` 一个字节都不动,两个节点相同才不允许)。`circuits` 决定这一次加几条(通道回线 / 独立线路都一样)。草稿期间点空白处继续等(不会退出)、点起点同一个节点不算数、按住拖动仍然平移画布,Esc / 右键 / `cancelAddChannel()` 退出。
98
+
99
+ ```js
100
+ view.beginAddChannel({ from: 'station-a' }) // 起点已经指定:接下来只点 station-b
101
+ view.beginAddChannel({ from: { id: 'station-a' }, select: true })
102
+ view.continueAddChannel({ from: 'station-c', kind: 'line' }) // 连续新增的下一段也能换指定起点
103
+ ```
97
104
 
98
105
  **直线 / 正交折线(`routing` / `path`)**:`beginAddChannel()` / `continueAddChannel()` 新增 `routing: 'straight' | 'orthogonal'` 与 `path`。`routing` 缺省 `'straight'`;写 `'orthogonal'` 且没有有效中间拐点时,组件自动补一个「先横后纵」的拐点 `[to.x, from.y]`。`path` 是**中间拐点**(不含两端),写了有效拐点就按它走,此时不会再追加自动拐点;草稿与 `commitAddChannel` / `addLine` / `addChannel` 共用同一份路由解析,预览和落地的折线完全一致。`onChannelDraft` / `onChannelDraftClick` 载荷里也带 `routing` / `path`,两端选好后 `path` 是实际会落地的中间拐点。
99
106
 
@@ -277,8 +284,33 @@ view.addLine({ from: 'station-a', to: 'station-b' }, [{ id: 'n-1' }, { id: 'n-2'
277
284
 
278
285
  **余量怎么算**:`getChannelAddOptions(from, to, count?, kind?)`(实例层按节点 id,引擎层同名方法按下标)里的 `count` 就是这一次要加的回数,每个方案的 `enabled` 都按它判 —— `newGroup` 要求「还能新分组」并且 `count <= channelGroupMaxLines`,`joinGroup` 要求目标分组的余量 `>= count`(`reason` 会写清「只剩 R 回余量,本次要加 M 回」),全都不可用才 `blocked`。`kind: 'line'` 时方案表是空的、`preferred` 为 `null`(独立线路不挑方案),只有两个节点相同才 `blocked`。
279
286
 
287
+ `beginAddChannel()` / `continueAddChannel()` 的 `GisViewChannelDraftOptions` 逐项如下:
288
+
289
+ | 字段 | 类型 | 缺省 | 说明 |
290
+ | --- | --- | --- | --- |
291
+ | `from` | `GisViewPointRef` | 无 | 预指定起点(id / 下标 / `{ id }` / `{ index }`),直接进 `from` 相位;找不到时报 warning 且不开始 |
292
+ | `autoCommit` | `boolean` | `false` | 两个节点一选完就按推荐方案自动落地,不用再调 `commitAddChannel()` |
293
+ | `continuous` | `boolean \| 'chain'` | `false` | 每段落地后自动开下一段;`'chain'` 等价于同时 `chain: true` |
294
+ | `chain` | `boolean` | `false` | 连续新增时以上一段终点当下一段起点;显式 `from` 优先于它 |
295
+ | `kind` | `'channel' \| 'line'` | `'channel'` | 新建通道 / 回线,或新建不挂通道的独立线路 |
296
+ | `circuits` | `number` | `1` | 一次新建几回;`lineItems` 给了有效项时按它的条数对齐 |
297
+ | `name` | `string` | 按两端节点生成 | 新线路名 |
298
+ | `lineId` | `string \| number` | 按通道 id 生成 | 新回线 id 的兼容别名;与 `id` 二选一 |
299
+ | `id` | `string \| number` | 无 | 新回线 id;与 `lineId` 同时给时优先 |
300
+ | `lineProps` | `Partial<GisViewLine>` | 无 | 透传给每条新回线的业务字段(颜色、宽度、电压等级…) |
301
+ | `linePropsByMember` | `Array<Partial<GisViewLine> \| undefined>` | 无 | 逐回属性,第 i 项只盖在第 i 条上;与 `createChannels` 同名同义 |
302
+ | `lineItems` | `GisViewChannelLineItems` | 无 | 连同线路一起新建的一条 / 一批;给了几条就建几条 |
303
+ | `routing` | `'straight' \| 'orthogonal'` | `'straight'` | 直连,或自动补一个先横后纵的拐点 |
304
+ | `path` | `GisViewCoordinate[]` | 无 | 中间拐点(不含两端);有效时优先于 `routing` 的自动拐点 |
305
+ | `channelDraftStyle` | `GisViewChannelDraftStyle` | 全局 `channelDraftStyle` | 本次草稿的预览样式,草稿结束即丢弃 |
306
+ | `handoff` | `boolean` | `false` | `ready` 发完就收掉假通道,由使用方自己落地;与 `autoCommit` 同给时先自动落地 |
307
+ | `select` | `boolean` | `false` | 点节点时同步进选中集(走 `onSelect` / `onSelectChange`);预指定 `from` 时起点当场选中 |
308
+ | `channelProps` | `Partial<GisViewChannel>` | 无 | 新通道透传字段(宽度、节点宽度、偏移方式…) |
309
+ | `originalEvent` | `Event \| null` | `null` | 触发这次草稿的原始事件,原样带进回调载荷 |
310
+
280
311
  ```js
281
312
  view.beginAddChannel({
313
+ from: 'station-a', // 预指定起点:接下来只点终点;不写则点两次
282
314
  autoCommit: false, // true:两个节点一选完就按推荐方案自动落地,不用再调 commitAddChannel
283
315
  name: '新增通道', // 新线路名,缺省按两端节点拼
284
316
  lineId: 'new-line-1', // 新回线 id,缺省按通道 id 生成(撞车自动加后缀)
@@ -147,14 +147,23 @@ view.updateOptions({ labelOptimize: true }) // 切到优化档;false / 不写
147
147
  `html` 与顶层 `labelRender` 解决的是两件事:`html: false` 只关闭字符串的 HTML 解析,标签外壳仍是逐条 `.gis-view__label`(默认 `div`),几万条纯文本标签就会产生几万个节点;`labelRender` 决定这些纯文本标签要不要换成一张画布。
148
148
 
149
149
  - **`auto`(缺省)**:只在**强制显示档**(`labelOptimize` 未开、`denseMode` 未激活)启用。节点层与线路层各自统计这一轮能画布的纯文本标签,某一层达到 **1024** 条,该层就改画到一张 `.gis-view__label-canvas`,不再为这些标签建 `div`;两层互不牵连。
150
+ - **`canvas`**:纯文本标签始终改画到单张 Canvas,不受 1024 条阈值限制,优化档与密集档也生效。`maxCount` / `when` / `scanLimit` / `overlapMode` 等策略照旧,不能画布的标签仍按原规则走 DOM。高密度场景建议同时打开 `labelOptimize`,让截断与避让限制画布标签数量。
150
151
  - **`dom`**:始终逐条建 `.gis-view__label`,保持旧行为。数据量不大、要自己用 CSS / 事件精细控制标签时用它。
151
152
  - **仍走 DOM 的标签**:`html: true`、函数返回 `HTMLElement`、可拖拽 / 可编辑,以及 Canvas 画不全的样式(背景图、阴影、`transform`、多行、字间距、非实线或四边不一致的边框等)会留在同层 DOM 里,和画布标签可以混排。画布只接管纯文本且样式能完整复刻的那部分。
152
- - **画布档不参与优化档**:`labelOptimize: true` 或进入密集档后,标签仍走逐条 DOM —— 避让、换落点、截断与命中判定都要真实量尺寸,Canvas 这条路不会绕过它们。所以高密度场景应优先打开 `labelOptimize`;如果业务要求“一条都不能少”,再让缺省的 `auto` 用画布承接强制显示档。
153
- - **画布与交互**:画布按视口 CSS 尺寸与设备像素比(含宿主显示比例,夹在 0.5 ~ 4)分配像素;平移、缩放、拖节点、拖线路、悬停半径变化时同步重算,`exportImage({ includeLabels: true })` 也会把画布标签写进 SVG。数量回落到阈值以下或切到 `dom` 时,DOM 标签会重新建回来;切换只重排标签层,不动引擎。
153
+ - **优化档与避让**:显式 `canvas` 在 `labelOptimize: true` / 密集档下不会退回 DOM;避让碰撞框、节点换锚点与线路沿线换位都直接使用画布记录的宽高,文字宽度按「字体 + 样式 + 文案」缓存,同一批同文案标签不再逐条 `measureText`。线路内容是函数时无法保证换位后文字同步,换位回退会保守放弃,避免文字与位置错配。
154
+ - **画布与交互**:画布按视口 CSS 尺寸与设备像素比(含宿主显示比例,夹在 0.5 ~ 4)分配像素。首屏排版完成时立即出图;平移、缩放、拖节点、拖线路与悬停半径变化产生的连续重绘按 `requestAnimationFrame` 合并到下一帧,同一帧多次相机变化只画一次。绘制前会剔除视口外标签(旋转标签按外接圆,平移期把整层位移也算进去)。`exportImage({ includeLabels: true })` 会把画布标签写进 SVG。数量回落到阈值以下或切到 `dom` 时,DOM 标签会重新建回来;切换只重排标签层,不动引擎。
154
155
 
155
156
  ```js
156
- view.updateOptions({ labelRender: 'auto' }) // 缺省:高密度纯文本自动走单画布
157
- view.updateOptions({ labelRender: 'dom' }) // 强制逐条 DOM,保持旧行为
157
+ view.updateOptions({ labelRender: 'auto' }) // 缺省:强制显示档达到 1024 条才自动画布化
158
+ view.updateOptions({ labelRender: 'canvas' }) // 纯文本始终画布化(优化档也生效)
159
+ view.updateOptions({ labelRender: 'dom' }) // 强制逐条 DOM,保持旧行为
160
+
161
+ // 大数据下通常和优化档一起用:200 条上限、避让与换落点都保留,只是容器换成画布
162
+ view.updateOptions({
163
+ labelOptimize: true,
164
+ labelRender: 'canvas',
165
+ label: { enabled: true, content: '{name}', maxCount: 200 }
166
+ })
158
167
  ```
159
168
 
160
169
  ## 标签的拖动与编辑
@@ -46,6 +46,8 @@ view.updateOptions({
46
46
 
47
47
  `max` 缺省的 `'auto'` **不是拿场的峰值归一化**:密度场天生长尾(几个热点能比周围高一两个数量级),按峰值线性归一化会把九成面积挤进最冷一档、整张图糊成一片。缺省改成按分位取「膝盖」—— 90% 以下的面积铺满前 3/4 档,剩下那一截留给热点周围的色带;要老行为写 `max: 'peak'`,写数字就是按它线性归一化。`threshold`(门槛)与 `gamma`(对比度)一起决定盖住多大面积:门槛抬起来只留热点,就得到参考图那种「大片浅色 + 一圈圈色带 + 白心」。`weight: 'value'`(缺省)按要素的值域比例算比重(值高 + 密集 = 热),`weight: 'count'` 只数密度。场按世界坐标烘一次,平移不重算,缩放档变化超过 1.35 倍才重烤;要素特别多时撒点自动降采样(档边仍是细栅格的分辨率),`opacity` 只在画的时候乘、不触发重烤。
48
48
 
49
+ 密度场的缓存分两层:坐标、权重字段、显隐或通道值变化时才重新采集要素;只改密度核参数、色带或画层时复用上一份采样。`density.opacity` 与 `layer` 只改绘制、不重烤,换色带或核参数才用旧采样重算场。拖动节点 / 拐点时只记一笔「几何已变」,停手后统一补烤一次,不在每个指针事件里重算;密度场关闭期间发生的坐标 / 权重变化也会记账,下一次开启不会复用旧要素。
50
+
49
51
  **鹰眼图**跟着同一份 `heatmap` 取色带颜色与尺寸倍数,但**不画光晕、也不画密度场**:小图要的是清晰,一层放大的光晕或一张分档色块在几百像素的缩略图上只会糊成一片。
50
52
 
51
53
  **作用对象与通道**:`target` 决定哪些要素吃这份配置。`target: 'channel'` 时通道里的每条回线取**所属通道**的字段值(整条走廊一个颜色,回线不会各染一色);回线自己有值(`target` 含 `'line'`,且该回线命中字段或自己配了 `value`)时按自己的值算。逐要素 `item.heatmap` 与全局同义:`false` 不参与(保持自身配色),`true` 沿用全局那份,对象在全局配置上逐字段覆盖(只想改字段 / 值域 / 色带里的一个就写一个)。
@@ -206,6 +208,7 @@ for (const glyph of view.getLineEffects()) {
206
208
 
207
209
  - **动点进的是既有批次**:形状(圆)进圆点实例缓冲、图片 / SVG 进贴图组,与节点 / 装饰走同一条符号链(同一张烘焙好的贴图,不会为动画再烤一份)。
208
210
  - **每帧只改位置**:`updateLineEffects()` 按弧长算出这一帧的位置,按**槽位连续**攒成一段一次 `bufferSubData`(一段最多 64 个),写入量只跟视野里的动点数走;包围盒与视野不相交的线整条跳过。一帧最多算 4000 个动点,超了下一帧接着补。
211
+ - **每帧只遍历配了动点的线路**:动画线路另建 `lineEffectRuns` 清单,`updateLineEffects()` 不再逐条扫全部线路 —— 普通线路再多,动画循环也只处理真正配了 `effect` 的那几条。拖动端点 / 拐点、通道骨架变化时,路径就地换掉的同时重算该条总长与包围盒,动点按新弧长继续采样,不会按旧长度跳位;多层阴影的实例更新也合并成一次 `bufferSubData`。
209
212
  - **涟漪每帧只传一个 `u_time`**:半径、相位、周期、线宽在建实例时全部定死(相位按 `stagger` 与第几圈算好),实例缓冲一个字节都不重传,GPU 侧一次实例化绘制画完。
210
213
  - **涟漪实例只建「视口里的」**:先问点网格要视口外扩一成的候选,排一遍保证「按数据顺序」,再用与框选同一份可见判定过滤;相机在动但**拖动 / 缩放期间不重建**(涟漪记的是世界坐标,相机怎么动它们自己跟着走),停手后那一帧一次补齐。`limit`(缺省 2000)是兜底:密集数据里一屏几万个节点不会把涟漪铺成一片糊。
211
214
  - **不画的时候一个字节都不花**:动画开着才续帧 —— 视野里没有动点也没有涟漪、页签切到后台(`document.hidden`)、系统要求减少动态效果(`prefers-reduced-motion`)时直接停,静止画面照旧按需渲染;`effects` 没配(数据里也没人逐条配)时这一整层只是几个布尔判断。