gis-view-webgl2 0.1.12 → 0.1.13

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 (30) hide show
  1. package/dist/gis-view.css +1 -1
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +250 -4
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +63 -11
  6. package/docs/GisView.md +6 -6
  7. 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 +69 -11
  8. package/docs/demos/api-index.js +6 -2
  9. package/docs/demos/gis-view-demo/demo.css +56 -0
  10. package/docs/demos/gis-view-demo/panels/StylePanels.vue +22 -1
  11. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +2 -1
  12. package/docs/demos/gis-view-demo/useGisViewDemo.js +168 -4
  13. package/docs/index.html +2 -2
  14. package/docs/internals/15-03-/351/200/220/350/275/256/350/256/260/345/275/225-/346/200/247/350/203/275/344/270/216/344/277/256/345/244/215-/344/270/213.md +72 -1
  15. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +58 -4
  16. 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 +1 -1
  17. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +9 -4
  18. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +2 -0
  19. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +219 -1
  20. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +3 -1
  21. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +2 -2
  22. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +2 -1
  23. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +3 -1
  24. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +8 -0
  25. package/docs/steps/selftest-demos.html +3 -3
  26. package/docs/steps/selftest-docs.html +4 -4
  27. package/docs/steps/selftest-features.html +736 -0
  28. package/docs/steps/selftest-perf.html +296 -25
  29. package/docs/steps/selftest-select.html +182 -1
  30. package/package.json +2 -2
@@ -7,7 +7,7 @@ const {
7
7
  provinceLabelColor, provinceLoading, INITIAL_VIEW_MODES, initialViewMode, initialZoom, initialCenterText,
8
8
  initialViewReadout, draggable, liveDrag, liveDragHint, clickable, boxSelect, selectionBoxPersistent,
9
9
  selectionBoxRender, selectionBoxBorderStyle, selectionBoxRadius, selectionBoxUseClassName, selectionBoxReadout,
10
- readSelectionBox, clearRetainedSelectionBox, multiSelect, selectionBoxMode,
10
+ readSelectionBox, clearRetainedSelectionBox, multiSelect, toggleSelect, selectionBoxMode,
11
11
  SHORTCUT_MODES, shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate,
12
12
  shortcutReadout, COPY_OFFSETS, copyEnabled, copyOffsetMode, mergeEnabled, mergeTrigger, MERGE_TRIGGERS,
13
13
  mergeDistance, MERGE_CONFIRM_MODES, mergeConfirmMode, mergeChannelGroups, selectionOnTop, copyRollback,
@@ -193,6 +193,7 @@ const {
193
193
  <label><input v-model="clickable" type="checkbox" /> 可点击</label>
194
194
  <label><input v-model="boxSelect" type="checkbox" /> 框选</label>
195
195
  <label><input v-model="multiSelect" type="checkbox" /> 点击多选(multiSelect:不用按修饰键,单击即取反累加)</label>
196
+ <label><input v-model="toggleSelect" type="checkbox" /> 再次选中即取消(toggleSelect:Ctrl 选中后也可普通点击取消)</label>
196
197
  <label><input v-model="waypointEditable" type="checkbox" /> 可编辑拐点</label>
197
198
  <label><input v-model="waypointAlways" type="checkbox" /> 拐点始终显示</label>
198
199
  <label><input v-model="hoverHighlight" type="checkbox" /> 悬停强调</label>
@@ -320,13 +320,19 @@ export const useGisViewDemo = () => {
320
320
  continue
321
321
  }
322
322
  const first = members[0]
323
+ // 通道自己也要有值:只开 channel 目标时,回线取的是这个字段,network 门控也按它判断
324
+ const maxLoadingRate = members.reduce((max, member) => {
325
+ const value = Number(member.max_loading_rate)
326
+ return Number.isFinite(value) ? Math.max(max, value) : max
327
+ }, Number.NEGATIVE_INFINITY)
323
328
  channels.push({
324
329
  id: channelId,
325
330
  name: first.channel_name ?? channelId,
326
331
  lineIds: members.map(member => member.line_id),
327
332
  from: first.from_station,
328
333
  to: first.to_station,
329
- path: (first.coords_list ?? []).slice(1, -1).map(coordinate => [Number(coordinate[0]) || 0, Number(coordinate[1]) || 0])
334
+ path: (first.coords_list ?? []).slice(1, -1).map(coordinate => [Number(coordinate[0]) || 0, Number(coordinate[1]) || 0]),
335
+ ...(Number.isFinite(maxLoadingRate) ? { max_loading_rate: maxLoadingRate } : {})
330
336
  })
331
337
  }
332
338
  return { points, lines, channels }
@@ -387,13 +393,19 @@ export const useGisViewDemo = () => {
387
393
  max_loading_rate: member.max_loading_rate
388
394
  })
389
395
  })
396
+ // 通道目标同样要能从自己的字段取到值;取成员最大值,和 new.json 里恒定的 1.5 保持同一档
397
+ const maxLoadingRate = members.reduce((max, member) => {
398
+ const value = Number(member.max_loading_rate)
399
+ return Number.isFinite(value) ? Math.max(max, value) : max
400
+ }, Number.NEGATIVE_INFINITY)
390
401
  channels.push({
391
402
  id: channel.channel_name,
392
403
  name: channel.channel_name,
393
404
  lineIds,
394
405
  from,
395
406
  to,
396
- path: (channel.coords ?? []).slice(1, -1).map(coordinate => [Number(coordinate[0]) || 0, Number(coordinate[1]) || 0])
407
+ path: (channel.coords ?? []).slice(1, -1).map(coordinate => [Number(coordinate[0]) || 0, Number(coordinate[1]) || 0]),
408
+ ...(Number.isFinite(maxLoadingRate) ? { max_loading_rate: maxLoadingRate } : {})
397
409
  })
398
410
  }
399
411
  return { points, lines, channels }
@@ -1697,6 +1709,8 @@ export const useGisViewDemo = () => {
1697
1709
  const SELECTION_BOX_CLASS = 'demo-retained-box'
1698
1710
  /** 多选开关:打开后不用按修饰键,普通单击就是取反累加(走 multiSelectStyle) */
1699
1711
  const multiSelect = ref(false)
1712
+ /** 再次选中即取消:普通点击已选要素取消;Ctrl 先选中后再普通点击也会取消 */
1713
+ const toggleSelect = ref(false)
1700
1714
  /**
1701
1715
  * 框选矩形(Ctrl + 拖动时画的那个框)的样式:颜色、背景、边框都在 options.selectionBoxStyle 里配,
1702
1716
  * 与选中样式是两回事;这里给几档预设方便对比,default 就是组件自带的那份蓝。
@@ -3766,6 +3780,19 @@ export const useGisViewDemo = () => {
3766
3780
  const heatmapOpacityMax = ref(1)
3767
3781
  /** 鹰眼图要不要跟着上色:勾着(缺省)就沿用主图那一份,勾掉就只给小图关 */
3768
3782
  const heatmapMinimap = ref(true)
3783
+ /** 内置图例:默认关,开启后跟随热力图的字段、值域与色带;类型可切连续尺 / 分段色块 */
3784
+ const visualMapEnabled = ref(false)
3785
+ const visualMapType = ref('ruler')
3786
+ const visualMapInteractive = ref(true)
3787
+ const visualMapCustom = ref(false)
3788
+ const visualMapOutOfRange = ref('network')
3789
+ const VISUAL_MAP_OUT_OF_RANGE_MODES = [
3790
+ { value: 'heatmap', label: '退出热力(网架恢复普通样式)' },
3791
+ { value: 'network', label: '隐藏对应网架' },
3792
+ { value: 'none', label: '保持原样(仅 onChange)' }
3793
+ ]
3794
+ const visualMapReadout = ref('')
3795
+ const visualMapLastChange = ref('')
3769
3796
  /** 当前数据里 max_loading_rate 的读数:直接说明这一份数据「看不看得出渐变」,节点与线路分开报 */
3770
3797
  const heatmapFieldStats = computed(() => {
3771
3798
  const scan = (items) => {
@@ -3857,6 +3884,131 @@ export const useGisViewDemo = () => {
3857
3884
  : false
3858
3885
  }
3859
3886
  }
3887
+ /**
3888
+ * 图例配置:只演示「跟随热力图」这一种用法,target / field / domain / colors / opacity
3889
+ * 全部留空,由组件从当前 heatmap 继承。不写 target 时展示值域取有数据的第一类,
3890
+ * 选区仍门控全部启用目标;热力图关掉时图例也一并关掉,避免留下一条孤立的色带。
3891
+ */
3892
+ const visualMapNumberText = (value) => {
3893
+ const number = Number(value)
3894
+ if (!Number.isFinite(number)) {
3895
+ return '—'
3896
+ }
3897
+ return String(Number(number.toFixed(2)))
3898
+ }
3899
+ const handleVisualMapChange = (payload) => {
3900
+ visualMapReadout.value = payload.type === 'piecewise'
3901
+ ? `分段:选中 ${payload.selected.length} 段${payload.selected.length ? `(${payload.selected.join(' / ')})` : ''}`
3902
+ : payload.range
3903
+ ? `区间:${visualMapNumberText(payload.range[0])} ~ ${visualMapNumberText(payload.range[1])}`
3904
+ : '区间:未启用'
3905
+ visualMapLastChange.value = `${payload.source} / ${payload.reason}${payload.originalEvent ? ` / ${payload.originalEvent.type}` : ''}`
3906
+ }
3907
+ /** 自定义渲染:图例选区仍会门控全部热力作用目标;额外筛选等业务联动再由 onChange 决定 */
3908
+ const renderVisualMapCustom = (context) => {
3909
+ const wrapper = document.createElement('div')
3910
+ wrapper.className = 'demo-visual-map-custom'
3911
+ const readout = document.createElement('span')
3912
+ readout.className = 'demo-visual-map-custom__readout'
3913
+ const actions = document.createElement('div')
3914
+ actions.className = 'demo-visual-map-custom__actions'
3915
+ const active = new Set(context.selected)
3916
+ let currentRange = context.range ? [...context.range] : null
3917
+ const syncReadout = () => {
3918
+ readout.textContent = context.type === 'piecewise'
3919
+ ? `已选 ${active.size} 段`
3920
+ : currentRange
3921
+ ? `${context.formatValue(currentRange[0])} ~ ${context.formatValue(currentRange[1])}`
3922
+ : '区间未启用'
3923
+ }
3924
+ if (context.type === 'piecewise') {
3925
+ const buttons = []
3926
+ for (let index = 0; index < context.pieces.length; index += 1) {
3927
+ const piece = context.pieces[index]
3928
+ const button = document.createElement('button')
3929
+ button.type = 'button'
3930
+ button.className = 'demo-visual-map-custom__piece'
3931
+ button.setAttribute('aria-pressed', active.has(index) ? 'true' : 'false')
3932
+ const swatch = document.createElement('span')
3933
+ swatch.className = 'demo-visual-map-custom__swatch'
3934
+ swatch.style.background = piece.color
3935
+ button.append(swatch, document.createTextNode(piece.label))
3936
+ button.addEventListener('click', () => {
3937
+ if (active.has(index)) {
3938
+ active.delete(index)
3939
+ }
3940
+ else {
3941
+ active.add(index)
3942
+ }
3943
+ const changed = context.setSelection([...active])
3944
+ if (!changed) {
3945
+ return
3946
+ }
3947
+ for (const [buttonIndex, item] of buttons) {
3948
+ item.setAttribute('aria-pressed', active.has(buttonIndex) ? 'true' : 'false')
3949
+ }
3950
+ syncReadout()
3951
+ })
3952
+ buttons.push([index, button])
3953
+ actions.append(button)
3954
+ }
3955
+ }
3956
+ else {
3957
+ const low = Math.min(context.domain[0], context.domain[1])
3958
+ const high = Math.max(context.domain[0], context.domain[1])
3959
+ const span = high - low
3960
+ const presets = [
3961
+ { label: '低段', range: [low, low + span * 0.34] },
3962
+ { label: '中段', range: [low + span * 0.34, low + span * 0.67] },
3963
+ { label: '高段', range: [low + span * 0.67, high] },
3964
+ { label: '全量', range: [low, high] }
3965
+ ]
3966
+ for (const preset of presets) {
3967
+ const button = document.createElement('button')
3968
+ button.type = 'button'
3969
+ button.className = 'demo-visual-map-custom__piece'
3970
+ button.textContent = preset.label
3971
+ button.addEventListener('click', () => {
3972
+ if (context.setRange(preset.range)) {
3973
+ currentRange = [...preset.range]
3974
+ syncReadout()
3975
+ }
3976
+ })
3977
+ actions.append(button)
3978
+ }
3979
+ const reset = document.createElement('button')
3980
+ reset.type = 'button'
3981
+ reset.className = 'demo-visual-map-custom__piece'
3982
+ reset.textContent = '恢复全量'
3983
+ reset.addEventListener('click', () => {
3984
+ if (context.clearSelection()) {
3985
+ currentRange = [low, high]
3986
+ syncReadout()
3987
+ }
3988
+ })
3989
+ actions.append(reset)
3990
+ }
3991
+ syncReadout()
3992
+ wrapper.append(readout, actions)
3993
+ return wrapper
3994
+ }
3995
+ const buildVisualMapOptions = () => {
3996
+ if (!heatmapEnabled.value || !visualMapEnabled.value) {
3997
+ return null
3998
+ }
3999
+ return {
4000
+ type: visualMapType.value,
4001
+ title: 'max_loading_rate',
4002
+ position: 'bottom-left',
4003
+ tickCount: 5,
4004
+ interactive: visualMapInteractive.value,
4005
+ selectedMode: visualMapType.value === 'piecewise' ? 'multiple' : false,
4006
+ calculable: visualMapType.value === 'ruler' && visualMapInteractive.value,
4007
+ outOfRange: visualMapOutOfRange.value,
4008
+ render: visualMapCustom.value ? renderVisualMapCustom : undefined,
4009
+ onChange: handleVisualMapChange
4010
+ }
4011
+ }
3860
4012
  /** 可见节点数:标签最多也就渲染这么多,面板上给个读数 */
3861
4013
  const syncVisibleCount = () => {
3862
4014
  visiblePointCount.value = viewRef.value?.listPoints({ visibleOnly: true }).length ?? 0
@@ -4255,6 +4407,7 @@ export const useGisViewDemo = () => {
4255
4407
  // 框选结束后保留矩形:框内拖动整组平移选中的节点;面板可随时改,右侧读数能看到坐标
4256
4408
  selectionBoxPersistent: selectionBoxPersistent.value,
4257
4409
  multiSelect: multiSelect.value,
4410
+ toggleSelect: toggleSelect.value,
4258
4411
  // 快捷键:框选 / 多选 / 加点 / 复制这四个「按住 + 操作」的修饰键都能改(缺省 Ctrl / Ctrl / Ctrl / Alt)
4259
4412
  shortcuts: buildShortcuts(),
4260
4413
  // 节点复制:Alt 点节点跟随鼠标落地,接口复制落在节点旁边(落点偏移在 copy.offset 里)
@@ -4277,6 +4430,8 @@ export const useGisViewDemo = () => {
4277
4430
  minimap: buildMinimapOptions(),
4278
4431
  // 热力图:按 max_loading_rate 给线路 / 通道回线(target 带上 point 时还有节点)取色带颜色并缩放尺寸
4279
4432
  heatmap: buildHeatmapOptions(),
4433
+ // 图例:不写字段 / 值域 / 色带,默认与热力图共用同一把尺
4434
+ visualMap: buildVisualMapOptions(),
4280
4435
  pointRules: applyRules.value ? buildPointRules() : [],
4281
4436
  lineRules: applyRules.value ? buildLineRules() : [],
4282
4437
  label: buildLabelOptions(),
@@ -4425,6 +4580,7 @@ export const useGisViewDemo = () => {
4425
4580
  }
4426
4581
  })
4427
4582
  watch(multiSelect, value => viewRef.value?.updateOptions({ multiSelect: value }))
4583
+ watch(toggleSelect, value => viewRef.value?.updateOptions({ toggleSelect: value }))
4428
4584
  // 快捷键自定义:四个「按住 + 操作」的动作改完立刻下发(框选 / 多选 / 加点的修饰键同时也是光标变体的判据)
4429
4585
  watch([shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate], () => {
4430
4586
  viewRef.value?.updateOptions({ shortcuts: buildShortcuts() })
@@ -4893,6 +5049,11 @@ export const useGisViewDemo = () => {
4893
5049
  heatmapGlow, heatmapGlowStrength, heatmapGlowLine, heatmapGlowPoint, heatmapGlowBlend, heatmapOpacityMin, heatmapOpacityMax,
4894
5050
  heatmapDensity, heatmapDensityStyle, heatmapDensityLayer, heatmapDensityLevels, heatmapDensityRadius, heatmapDensityThreshold, heatmapDensityCore, heatmapDensityGamma, heatmapDensityWeight, heatmapDensityOpacity
4895
5051
  ], () => viewRef.value?.updateOptions({ heatmap: buildHeatmapOptions() }))
5052
+ watch([heatmapEnabled, visualMapEnabled, visualMapType, visualMapInteractive, visualMapCustom, visualMapOutOfRange], () => {
5053
+ visualMapReadout.value = ''
5054
+ visualMapLastChange.value = ''
5055
+ viewRef.value?.updateOptions({ visualMap: buildVisualMapOptions() })
5056
+ })
4896
5057
  // 小图那一份:默认沿用主图(不写 heatmap 字段),勾掉「鹰眼图沿用」就只给小图关(heatmap: false)
4897
5058
  watch([heatmapEnabled, heatmapMinimap], () => viewRef.value?.updateOptions({ minimap: buildMinimapOptions() }))
4898
5059
  const applyChannelOptions = () => {
@@ -6178,7 +6339,7 @@ const members = (payload.lineData ?? payload.linePropsByMember ?? []).filter(Boo
6178
6339
  initialZoom, initialCenterText, initialViewReadout, draggable, liveDrag, liveDragHint, clickable, boxSelect,
6179
6340
  selectionBoxPersistent, selectionBoxRender, selectionBoxBorderStyle, selectionBoxRadius,
6180
6341
  selectionBoxUseClassName, selectionBoxReadout, SELECTION_BOX_CLASS, readSelectionBox,
6181
- clearRetainedSelectionBox, multiSelect, selectionBoxMode, SELECTION_BOX_STYLES, buildSelectionBoxStyle, SHORTCUT_MODES,
6342
+ clearRetainedSelectionBox, multiSelect, toggleSelect, selectionBoxMode, SELECTION_BOX_STYLES, buildSelectionBoxStyle, SHORTCUT_MODES,
6182
6343
  shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate, shortcutValue, shortcutLabel,
6183
6344
  shortcutReadout, buildShortcuts, COPY_OFFSETS, copyEnabled, copyOffsetMode, buildCopyOptions, pointLabel,
6184
6345
  mergeEnabled, mergeTrigger, MERGE_TRIGGERS, mergeDistance, MERGE_CONFIRM_MODES, mergeConfirmMode,
@@ -6236,7 +6397,10 @@ const members = (payload.lineData ?? payload.linePropsByMember ?? []).filter(Boo
6236
6397
  heatmapDensityStyle, heatmapDensityLayer, heatmapDensityLevels, heatmapDensityRadius, heatmapDensityThreshold,
6237
6398
  heatmapDensityCore, heatmapDensityGamma, heatmapDensityWeight, heatmapDensityOpacity, heatmapGlow,
6238
6399
  heatmapGlowStrength, heatmapGlowLine, heatmapGlowPoint, heatmapGlowBlend, heatmapOpacityMin,
6239
- heatmapOpacityMax, heatmapMinimap, heatmapFieldStats, heatDensityText, buildHeatmapOptions, syncVisibleCount,
6400
+ heatmapOpacityMax, heatmapMinimap, heatmapFieldStats, heatDensityText, buildHeatmapOptions,
6401
+ visualMapEnabled, visualMapType, visualMapInteractive, visualMapCustom, visualMapOutOfRange,
6402
+ VISUAL_MAP_OUT_OF_RANGE_MODES, visualMapReadout, visualMapLastChange,
6403
+ buildVisualMapOptions, renderVisualMapCustom, syncVisibleCount,
6240
6404
  boundsOfData, currentBounds, PROVINCE_PALETTE, provinceHash, buildProvinceStyle, buildImagePlacement,
6241
6405
  buildBackgroundOptions, frameProvince, applyBackground, BACKGROUND_HINTS, backgroundHint, parseInitialCenter,
6242
6406
  parsePair, convertFromLngLat, convertFromScreen, buildInitialViewOptions, applyInitialViewFromControls,
package/docs/index.html CHANGED
@@ -1421,7 +1421,7 @@
1421
1421
  { file: 'manual/14-视图与筛选.md', title: '手册 14 · 视图与筛选', blurb: '筛选 / 搜索 / 视图与背景', parent: 'GisView.md' },
1422
1422
  { file: 'manual/15-导出与集成.md', title: '手册 15 · 导出与集成', blurb: '导出 / 插件 / 自建界面', parent: 'GisView.md' },
1423
1423
  { file: 'manual/16-实例方法总表.md', title: '手册 16 · 实例方法总表', blurb: '实例方法主表', parent: 'GisView.md' },
1424
- { file: 'manual/17-参数速查.md', title: '手册 17 · 参数速查', blurb: '281 个成员逐个列全', parent: 'GisView.md' },
1424
+ { file: 'manual/17-参数速查.md', title: '手册 17 · 参数速查', blurb: '283 个成员逐个列全', parent: 'GisView.md' },
1425
1425
  { file: 'manual/18-逐方法示例.md', title: '手册 18 · 逐方法示例', blurb: '每个成员一段示例', parent: 'GisView.md' },
1426
1426
  { file: 'manual/19-读数与拖拽预览.md', title: '手册 19 · 读数与拖拽预览', blurb: '读数 / 定位 / 落点预览', parent: 'GisView.md' },
1427
1427
  { file: 'manual/20-性能与大数据.md', title: '手册 20 · 性能与大数据', blurb: '大数据开关与实测', parent: 'GisView.md' },
@@ -2074,7 +2074,7 @@
2074
2074
  { badge: 'START', title: '第一次使用', desc: '引入、最小数据与首屏配置,先把图画出来。', meta: '01 快速开始 · 约 10 分钟', file: 'manual/01-快速开始.md' },
2075
2075
  { badge: 'BACKEND', title: '接后端与导出', desc: '编辑接口、导出回灌、插件与自建界面。', meta: '15 导出与集成', file: 'manual/15-导出与集成.md' },
2076
2076
  { badge: 'PERF', title: '十万级性能', desc: 'Worker 筛选 / 异步搜索、批量更新与质量档。', meta: '20 性能与大数据', file: 'manual/20-性能与大数据.md' },
2077
- { badge: 'API', title: '查方法与参数', desc: '实例方法、options 与可复制示例集中检索。', meta: 'API 速查 · 281 个成员', href: 'api/index.html', external: true }
2077
+ { badge: 'API', title: '查方法与参数', desc: '实例方法、options 与可复制示例集中检索。', meta: 'API 速查 · 283 个成员', href: 'api/index.html', external: true }
2078
2078
  ]
2079
2079
  const renderReadingPaths = () => `<section class='doc-paths' aria-label='推荐阅读路径'>` +
2080
2080
  `<div class='doc-paths__head'><strong>按任务选一条阅读路径</strong><span>第一次进来先看快速开始;带着明确目标时直接跳到对应章节。</span></div>` +
@@ -643,5 +643,76 @@
643
643
  **核心文件**:`src/gis-view/engine.js`、`create.js`、`types.d.ts`、`src/gis-view/README.md`;`docs/manual/03` / `14` / `16` / `17` / `18`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`panels/StylePanels.vue`、`docs/steps/selftest-scale.html`、`selftest-docs.html`、`selftest-demos.html`、`docs/demos/api-index.js`、`docs/AI对话汇总.md`、`docs/ai/06-会话索引与复查方法.md`。
644
644
 
645
645
  **验证**:`npx tsc --noEmit` 0 错;`node docs/steps/gen-api-index.mjs` 生成 281 个成员;`npm run check:docs` 全绿;`npm run build` 通过(仅既有大 chunk 提示);`git diff --check` 通过。浏览器串行自测:`selftest-features.html` **794/0**、`selftest-demos.html` **441/0**、`selftest-docs.html` **139/0**、`selftest-demo.html` **133/0**;`selftest-scale.html` **126 通过 / 2 失败**,新增断言全过,两条失败仍是既有筛选回调时序(`onFilter 0 条`)。
646
- [← 15-02 逐轮记录:性能与修复(上)](15-02-%E9%80%90%E8%BD%AE%E8%AE%B0%E5%BD%95-%E6%80%A7%E8%83%BD%E4%B8%8E%E4%BF%AE%E5%A4%8D-%E4%B8%8A.md) · [实现原理目录](../GisView实现原理.md) · [15-04 逐轮记录:热力图与性能 →](15-04-%E9%80%90%E8%BD%AE%E8%AE%B0%E5%BD%95-%E7%83%AD%E5%8A%9B%E5%9B%BE%E4%B8%8E%E6%80%A7%E8%83%BD.md)
646
+
647
+ ### 15.108 visualMap 图例、共享色带插值与图例专用快路径(2026-10-31,对着「修改.txt」两条)
648
+
649
+ **需求**(「修改.txt」两条):① 参考 ECharts `visualMap`,给组件加一个类似图例,并结合 ruler 样式;② 继续优化组件性能。
650
+
651
+ **图例模型**:新增 `src/gis-view/visual-map.js`,`options.visualMap` 配置对象本身即开关。`type: 'ruler'`(`'continuous'` 同义)画一条连续渐变尺:纵向低温在下、高温在上,横向从左往右递增,刻度按 `tickCount` 均分;`type: 'piecewise'` 画分段色块,`pieces` 支持 ECharts 的 `gte` / `gt` / `lte` / `lt` / `min` / `max`,可逐段写 `label` / `color`,不写分段时按值域与刻度数等分。另有 `domain`、单端 `min` / `max`、`colors` / `stops` / `opacity`、`title` / `unit` / `formatter`、`orient` / `position` / `width` / `height` / `precision`、`container` / `className` / `style`。默认按 `heatmap.target` 的 `point → line → channel` 顺序继承目标,字段 / 自动值域 / 色带 / 透明度继续继承热力图;没有热力图时按自身 `field` 扫当前数据,也能完全独立于热力图。
652
+
653
+ **读数与生命周期**:`create.js` 单独建一份只读 `visualMapHeatmap` 解析器,不把引擎内部的解析器暴露出去;`readHeatmap(target, withDomain)` 把 `withDomain` 原样传给 `describe()`,显式 `domain` 时不会误触发自动值域扫描。公开 `getVisualMapElement()` 与 `getVisualMap()`:前者返回根节点,后者返回类型、目标、字段、值域、色带、方向、标题、单位以及刻度 / 分段副本。不配 / `null` / `enabled: false` 时根节点从 DOM 摘掉;外部 `container` 也按同一路径卸载。图例本身不参与拾取,默认 CSS `pointer-events: none`,需要区间交互时由使用方在根节点上扩展。
654
+
655
+ **共享色带插值**:`heatmap.js` 导出 `createHeatmapRamp(colors, stops, opacity)`。它复用逐要素上色同一套 `normalizeStops()`、`buildRampLut()`、`opacityScale()` 与 `rgbaCss()`,图例的 CSS 渐变和画布取色不会再各写一份插值;每条图例色带只建一次 128 档 LUT,临时颜色配置不会挤占主热力图的 `rampCache`。`createHeatmap()` 同时补 `primaryTarget()` 与 `describe(kind, withDomain)`,后者自动值域仍走它自己的 `domainOf` 数组缓存。
656
+
657
+ **图例专用快路径**:`visual-map.js` 把更新合并到一帧,并用「类型 / 方向 / 目标 / 字段 / 值域 / 有效色带 / 标题 / 单位 / 尺寸 / 定位 / 容器 / formatter / 刻度与分段内容」组成渲染签名;签名没变时只更新内部状态,不写任何 DOM。数据变化由 `create.js` 的 `emitDataChange()` 调 `markDataDirty()`:清图例独立值域缓存、让热力图解析器的自动值域失效,只在确实需要自动值域时排帧;图例关闭期间发生的数据变化先记账,下次开启再补失效。`updateOptions({ visualMap })`(可带 `on*` 回调)被识别为 `visualMapOnly`,不重排引擎场景、不刷新鹰眼图,也跳过标签层重排;`heatmap` 配置变化另走 `markSourceDirty()` 重读继承的字段与色带。值域两端写死时连补另一端都不扫数据:显式 `domain` 和同时给出 `min` / `max` 都保持零字段读取,只有单端配置才扫自动值域。
658
+
659
+ **Demo 与文档**:`docs/demos/gis-view-demo/useGisViewDemo.js` 增加 `visualMapEnabled` / `visualMapType` 与 `buildVisualMapOptions()`,图例默认关闭,开启且热力图开启时跟随当前字段、值域与色带;`panels/StylePanels.vue` 的热力面板补「显示图例」「连续尺 / 分段」。手册 03 / 11 / 13 / 15 / 16 / 17 / 18 补配置、样式、接入、方法表、参数速查与示例;`docs/GisView.md`、`docs/index.html`、组件 README 与文档中心同步,CSS 增加 `.gis-view__visual-map*` 与 `--gis-view-visual-map-*` 主题变量。
660
+
661
+ **公开面与测试**:实例成员 281 → **283**(方法 276 → **278**,属性仍 5),options 105 → **106**,数据字段 273 → **274**,事件 / 功能开关 / 分组分别保持 44 / 17 / 16。`selftest-features` 新增 2 条字段读取断言:显式 `domain` 与两端 `min` / `max` 都不读数据字段;同时覆盖连续尺 / 分段 / 继承色带 / 外部容器 / 关闭卸载 / 只改图例不重排标签。`selftest-perf` 补 1.2 万点自动值域、50 次同签名更新零 mutation、定向改数据后值域跟随;在稳态标签量测前等待 `document.fonts.ready` 后再 settle,避免冷启动字体失效把真实一次量测误算成回归,原有阈值与断言不放宽。`selftest-docs` / `selftest-demos` 的成员与 options 计数同步为 283 / 106。
662
+
663
+ **核心文件**:`src/gis-view/visual-map.js`、`heatmap.js`、`create.js`、`gis-view.css`、`types.d.ts`、`src/gis-view/README.md`;`docs/manual/03` / `11` / `13` / `15` / `16` / `17` / `18`、`docs/GisView.md`、`docs/index.html`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`panels/StylePanels.vue`、`docs/steps/selftest-features.html` / `selftest-perf.html` / `selftest-demos.html` / `selftest-docs.html`、`docs/demos/api-index.js`、`docs/AI对话汇总.md`、`docs/ai/06-会话索引与复查方法.md`。
664
+
665
+ **验证**:`npx tsc --noEmit` 0 错;`node --check` 覆盖图例 / 热力图 / 组件入口 / Demo 组合式函数;`node docs/steps/gen-api-index.mjs` 生成 283 个成员,`--check` 与 `npm run check:docs` 全绿(283 个实例成员 / 278 个方法 / 5 个属性 / 106 个 options / 274 个数据字段);`npm run build` 通过(仅既有大 chunk 提示);`git diff --check` 通过。浏览器自测:`selftest-features` **808/0**、`selftest-perf` **190/0**、`selftest-demos` **441/0**、`selftest-docs` **139/0**、`selftest-demo` **133/0**。
666
+ ### 15.109 线路热力交互连续显示、热力快路径与 visualMap 自定义交互(2026-10-31,对着「修改.txt」三条)
667
+
668
+ **需求**(「修改.txt」三条):① 开启热力图后平移 / 缩放时线路热力先消失再出现;② 继续优化开启热力图后的交互性能;③ 图例支持完全自定义和交互,参考 ECharts `visualMap`。
669
+
670
+ **热力消失的两个根因**:相机移动会重铺线路实例,`buildLineInstances()` 原来没有把 `heat` 写进重铺后的实例,线路热力值随即归零;同时 `LINE_VERTEX_SHADER` 在顶点阶段把横向坐标写成了 `abs(a_corner.y)`,两个端点都是 `1`,插值后整块四边形恒为 `1`,片元的光晕衰减失效。现在实例统一补 `heat: line.heat ?? 0`,顶点保留带符号的 `v_edge`,片元里再取 `abs(v_edge)`。
671
+
672
+ **交互期合并线**:新增 `HEAT_LINE_FRAGMENT_SHADER` 与 `heatLineProgram`,交互 / 程序化相机运动期由 `drawHeatLineBatch()` 用一笔实例化绘制同时输出本色核与外侧光晕,不再先画本色、再补一遍光晕,也不额外增加 draw call;`u_coreWidthBoost` / `v_coreHalfWidth` 让扩到光晕宽度的四边形仍按原线宽恢复内核。合并批次使用预乘普通混合 `(ONE, ONE_MINUS_SRC_ALPHA)`,避免内核与光晕单遍相加时多条重叠线被推到白色;停稳后的两遍绘制仍按 `glow.blend` 的加法 / 普通混合走原路。
673
+
674
+ **热力性能收口**:密度场四边形改存独立的 `heatDensityBuffer`,按 `STATIC_DRAW` 上传,不再每帧重传;交互与相机运动期跳过密度场重烤,停稳后再补烤。节点光晕在交互期继续让路(`pointGlowOn = glowOn && !(interacting || cameraMoving)`),线路热力则通过合并管线保持连续可见,兼顾“不闪”和减少圆面片开销。
675
+
676
+ **visualMap 交互与完全自定义**:`visual-map.js` 增加 `interactive`、`selectedMode: 'single' | 'multiple' | false`、`selected`、`range`、`calculable`、`onChange` 与 `render(context)`。分段图例支持鼠标点击和 Enter / Space 切换,并可单选 / 多选;连续尺支持区间遮罩、双滑块、拖动单端或整体,以及方向键 / Shift / Alt / PageUp / PageDown / Home / End。`onChange` 通过 rAF 合帧一次,载荷含 `type,target,field,domain,interactive,selectedMode,calculable,selected,range,rangeRatios,element,source,reason,originalEvent`;受控 `updateOptions({ visualMap })` 不回声触发回调。`render(context)` 可返回 DOM / DocumentFragment / HTML 字符串,或直接写 `context.body`,上下文提供 `setSelection` / `setRange` / `clearSelection` / `emitChange`;图例读数同步返回 `interactive,selectedMode,calculable,selected,range,rangeRatios`。图例仍不自动改业务筛选,选择结果由使用方在 `onChange` 中处理。
677
+
678
+ **Demo、文档与公开面**:热力面板增加图例交互开关、完全自定义开关与最近一次 `onChange` 读数,自定义示例用分段色块按钮和连续尺按钮演示 `setSelection` / `setRange` / `clearSelection`。手册 03 / 11 / 13 / 15 / 16 / 17 / 18、`docs/GisView.md`、组件 README、CSS 与四个自测页同步。没有新增实例成员 / options / 数据字段,公开面保持 283 个实例成员(278 方法 + 5 属性)/ 106 options / 274 数据字段。
679
+
680
+ **核心文件**:`src/gis-view/shaders.js`、`engine.js`、`heatmap.js`、`visual-map.js`、`create.js`、`types.d.ts`、`gis-view.css`、`src/gis-view/README.md`;`docs/demos/gis-view-demo/useGisViewDemo.js`、`panels/StylePanels.vue`、`demo.css`、手册 03 / 11 / 13 / 15 / 16 / 17 / 18、`docs/GisView.md`、`docs/steps/selftest-features.html` / `selftest-perf.html` / `selftest-demos.html` / `selftest-docs.html`、`docs/AI对话汇总.md`、`docs/ai/06-会话索引与复查方法.md`。
681
+
682
+ **验证**:`node --check` 覆盖着色器 / 引擎 / 热力图 / 图例 / 组件入口 / Demo;`npx tsc --noEmit` 0 错;`node docs/steps/gen-api-index.mjs` 生成 283 个成员,`--check` 与 `npm run check:docs` 全绿(283 个实例成员 / 278 个方法 / 5 个属性 / 106 个 options / 274 个数据字段);`npm run build` 通过(仅既有大 chunk 提示);`git diff --check` 通过。浏览器串行自测:`selftest-features` **823/0**、`selftest-perf` **195/0**、`selftest-demos` **441/0**、`selftest-docs` **139/0**。
683
+
684
+ ### 15.110 热力密度几何隔离、线路热力连续与 visualMap 运行时门控(2026-11-01,对着「修改.txt」三条)
685
+
686
+ **需求**:① 开启热力图后平移 / 缩放时,仍有部分线路热力先消失再出现;② 开启热力图后背景也会异常放大缩小;③ `visualMap` 交互当前没有实际效果,应参考 ECharts 让选区真正影响热力表现。
687
+
688
+ **背景缩放根因与修复**:密度场虽然有自己的 `heatDensityBuffer`,`renderHeatDensity()` 也把四边形写进了这块缓冲,但 `drawImageQuad()` 没有接收 VAO 参数,始终绑定背景图片的 `imageVao`。VAO 记的是创建时绑定的缓冲,因此密度场实际按背景图片的四边形绘制,相机 / 背景一变就一起缩放。现在新增独立 `heatDensityVao`,`drawImageQuad()` 支持可选 VAO,密度场用 `createImageVao(heatDensityBuffer)` 建自己的几何;背景仍用 `imageVao`,销毁时两块 VAO 分别释放。
689
+
690
+ **线路热力连续性**:旧实现在交互 / 相机运动时改用「无光晕 + 细线快路径」,自动画质降档还可能直接关闭热力光晕,于是线路热力会先消失、停稳后再出现。现在 `engine.js` 不再让 `(interacting || cameraMoving)` 决定线路热力管线:普通 / 自动浅底配置固定走 `drawHeatLineBatch()`,一笔输出本色核与外侧光晕;需要加法混合或驱动属性槽位不匹配时固定走两遍 `drawLineHeatGlow()`。两条路径都只由配置决定,运动与停稳帧一致。节点光晕仍按交互 / 质量档让路,但不会再连带丢掉线路热力;无光晕时才允许细线快路径。
691
+
692
+ **visualMap 运行时门控**:`visual-map.js` 新增 `visualStateSnapshot()` / `emitVisualState()`,把 `type` / `target` / `field` / `selected` / `range` / 选中的 `pieces` 发成内部纯数据,状态不变不重复通知。`create.js` 保存首帧状态,在引擎创建后调用内部 `setVisualMapState(state, false)`,避免首次解析后再多刷一次;后续选择、键盘、API 与受控配置更新都会同步。`heatmap.js` 的 `visualStateAllows()` 只门控与图例同 `target` 且同 `field` 的热力;落选值回普通样式,数据不删除、筛选不变,也不误伤独立图例或另一类要素。默认继承主图热力的鹰眼图读取同一状态,选区变化时只刷新小图配色;显式 `minimap.heatmap` 不跟随。分段空选择关闭对应热力,连续尺按闭区间判断;`setVisualMapState()` 只重算热力样式、光晕与密度权重,几何 / 背景 / 标签不重排,且没有加入公开实例 API。
693
+
694
+ **测试与文档**:`selftest-features` 增加分段取消 / 受控选择、连续尺区间外门控、密度场重采样、继承鹰眼图同选区与显式 `minimap.heatmap` 独立断言;`selftest-perf` 增加图片背景 + 密度场范围断言、18 条线路中点逐条采样,以及加法混合管线断言。新增的密度背景用例原来在 `sleep(300)` 后调用静态画面的 `whenRendered()`,会一直等不到下一帧;现改为先兑现相机帧、等图片解码后再 `refresh()` 并等帧。手册 03 / 11 / 13 / 14 / 15、`docs/GisView.md`、`types.d.ts`、组件 README、Demo 提示与 API 索引同步为「内置门控 + onChange 额外联动」。
695
+
696
+ **核心文件**:`src/gis-view/engine.js`、`heatmap.js`、`visual-map.js`、`create.js`、`types.d.ts`、`src/gis-view/README.md`;`docs/manual/03` / `11` / `13` / `15`、`docs/GisView.md`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`panels/StylePanels.vue`、`docs/steps/selftest-features.html` / `selftest-perf.html`、`docs/AI对话汇总.md`、`docs/ai/06-会话索引与复查方法.md`。
697
+
698
+ **验证**:`node --check` 覆盖引擎 / 热力图 / 图例 / 组件入口;`npx tsc --noEmit` 0 错;`npm run check:docs`、`npm run build`、`git diff --check` 通过;API 索引保持 283 个实例成员(278 方法 + 5 属性)/ 106 options / 274 数据字段。浏览器串行自测:`selftest-features` **828/0**、`selftest-demos` **441/0**、`selftest-docs` **139/0**、`selftest-perf` **197/0**。性能页此前一次完整运行曾出现既有的「改一条可见标签之后的定向重排」布局读数断言失败(1313 次),最终复跑未再出现,与本轮改动无调用关系。
699
+
700
+ ### 15.111 toggleSelect、visualMap 落选门控与交互快路径(2026-11-01,对着「修改.txt」三条;不考虑分组详情)
701
+
702
+ **需求**:① 新增取消选中开关,默认关闭;打开后单击选中的要素再次单击取消,双击选中的再次双击取消,Ctrl / ⌘ 先选中后再普通单击也要取消;② 继续优化交互性能;③ `visualMap` 落选时与热力图 / 网架联动,可选择只退热力、隐藏对应网架,或完全交给业务处理。用户明确「不考虑分组详情」,本轮没有扩展任何分组详情能力。
703
+
704
+ **toggleSelect**:顶层 `options.toggleSelect` 缺省 `false`,`create.js` 的 `pickEngineOptions()` 透传,`engine.js` 收进 `settings`,`updateOptions({ toggleSelect })` 走纯表现快路径,不重建场景,也没有新增实例方法。点击分流按原始选中结果判断:单击模式下,命中项已在选中集且本来会改选中集时,先走 `toggleSelection()` 取反;同一轮双击的第二下由 `keepSelection` 保持,不再二次取反。`selectTrigger: 'dblClick'` 下第一次点击只派发 `onClick`,第二下才取反;再次双击同一要素取消。Ctrl / ⌘ 先选中的要素,后续不按修饰键普通单击也会命中「已选中」分支并取消,不必同时打开 `multiSelect`。取反时 `onClick.action.type` 与 `onSelectChange.action.type` 都是 `toggleSelect`,双击仍补发原有 `onDblClick`;开关关闭时所有路径保持原行为。
705
+
706
+ **outOfRange 归一化**:`visual-map.js` 新增 `OUT_OF_RANGE_MODES` / `normalizeOutOfRange()`,只接受 `'heatmap' | 'network' | 'none'`,缺省或非法值按 `'heatmap'`。归一化结果进入图例状态快照、`getVisualMap()`、`render(context)` 与 `onChange` 载荷;`types.d.ts` 增加 `GisViewVisualMapOutOfRange` 并补齐这些读数。`heatmap.js` 抽出 `visualStateIncludes()`,主图热力与网架隐藏共用同一套分段开闭端点 / 连续尺闭区间判定。
707
+
708
+ **network 隐藏账**:引擎把原来的隐藏集合拆成筛选基线 `filterHiddenPointIndexes` / `filterHiddenLineIndexes` 与 visualMap 派生集合 `visualHiddenPointIndexes` / `visualHiddenLineIndexes`,解析、渲染、拾取和包围盒读取两者并集。`computeVisualHiddenIndexes()` 只在 `outOfRange: 'network'` 时按 `target` 计算:`point` 检查节点自身字段;`line` 按通道声明先收全部成员回线,排除成员后只隐藏独立线路,避免误伤通道;`channel` 检查通道声明自身字段,落选时隐藏该通道声明下的全部成员回线,包括暂时没有锚点或处在隐藏分组里的成员。缺值、空串与非数值不参与隐藏。三档都只改视觉;数据、筛选结果、选中集与通道分组声明不删除、不改写。
709
+
710
+ **刷新与性能**:`setVisualMapState()` 先重推隐藏集合再决定后续:集合内容不变时返回 `{ hiddenChanged: false }`,不重建场景;集合变了才 `markHeatDensityDirty()` + `buildScene()`,并让组件同步标签、提示与鹰眼图。`outOfRange: 'none'` 直接视为不门控;只有旧 / 新状态至少一侧真的门控节点或线路时,才让对应那一半的样式缓存失效。`resolvedStyleInputs` 由单一 `visualMapKey` 拆成 `pointVisualMapKey` / `lineVisualMapKey`,图例只影响节点时不再重算线路的热力样式,反之亦然。`create.js` 在 `network` 隐藏集合变化时重刷标签与提示;鹰眼图的刷新条件包含 `hiddenChanged`,所以即使显式配置了独立 `minimap.heatmap`,落选网架也不会留在小图里。
711
+
712
+ **Demo、测试与文档**:Demo 的视图面板增加「再次选中即取消」开关,热力面板增加落选值下拉并同步三档提示。`selftest-select` 新增 5 条,覆盖单击模式再次单击、双击模式再次双击、Ctrl 先选后普通单击、两类事件的 `toggleSelect` 动作与关闭态;`selftest-features` 新增 8 条,覆盖三种 `outOfRange`、节点 / 独立线路 / 通道、数据与筛选不变、隐藏集合不变不重建与鹰眼图同步。`selftest-perf` 的「改一条可见标签之后」原来把 4 万点 / 4 万线视图挂在 `body` 末尾,视口外的发散渲染会暂停;六轮 `updateOptions` 读到 0 次后,`updatePoint` 的兜底重排才第一次把 1313 条标签真正排出来,导致偶发误报。该用例改为 `progressiveInit: false`、显式 `labelOptimize: true`,并在计量前等待 `isLayoutReady()`;连续三次均 197/0。手册 03 补 `visualMap.outOfRange` 和 `toggleSelect`,手册 08 补选中取反语义,手册 13 补门控载荷与 `network` 细节,手册 14 补鹰眼图同步;`docs/GisView.md`、组件 README、API 索引、`docs/AI对话汇总.md` 与本页同步。
713
+
714
+ **核心文件**:`src/gis-view/engine.js`、`create.js`、`heatmap.js`、`visual-map.js`、`update-policy.js`、`types.d.ts`;`docs/manual/03` / `08` / `13` / `14`、`docs/GisView.md`、`src/gis-view/README.md`、`docs/demos/gis-view-demo/useGisViewDemo.js`、`panels/ViewPanels.vue`、`panels/StylePanels.vue`、`docs/steps/selftest-select.html` / `selftest-features.html` / `selftest-perf.html`、`docs/AI对话汇总.md`、`docs/ai/06-会话索引与复查方法.md`。
715
+
716
+ **验证**:`npx tsc --noEmit` 0 错;`node docs/steps/gen-api-index.mjs` 生成 283 个成员;`npm run check:docs` 为 283 个实例成员(278 方法 + 5 属性)/ 107 个 options / 275 个数据字段;`npm run build`、`git diff --check` 通过。浏览器串行自测 `selftest-select` **107/0**、`selftest-features` **836/0**、`selftest-demos` **441/0**、`selftest-docs` **139/0**、`selftest-perf` **197/0**。
717
+
647
718
  [← 15-02 逐轮记录:性能与修复(上)](15-02-%E9%80%90%E8%BD%AE%E8%AE%B0%E5%BD%95-%E6%80%A7%E8%83%BD%E4%B8%8E%E4%BF%AE%E5%A4%8D-%E4%B8%8A.md) · [实现原理目录](../GisView实现原理.md) · [15-04 逐轮记录:热力图与性能 →](15-04-%E9%80%90%E8%BD%AE%E8%AE%B0%E5%BD%95-%E7%83%AD%E5%8A%9B%E5%9B%BE%E4%B8%8E%E6%80%A7%E8%83%BD.md)
@@ -34,6 +34,7 @@
34
34
  | `lineStyle` | `GisViewLineStyle` | 见下 | 线的默认样式,可被单条线覆盖;运行时按字段合并用 `updateLineStyle(patch)` |
35
35
  | `pointRules` / `lineRules` | `GisViewPointRule[]` / `GisViewLineRule[]` | `[]` | 批量样式规则:按属性给点 / 线批量配色、改大小、换符号,见「批量样式规则」 |
36
36
  | `heatmap` | `GisViewHeatmap \| null` | 无 | 热力图:按业务字段的数值到渐变色带上取颜色,并可按同一个值缩放节点大小 / 线宽,作用对象可选节点 / 线路 / 通道;不配(或 `null`)就是关的,见「热力图」 |
37
+ | `visualMap` | `GisViewVisualMap \| null` | 无 | 图例:参考 ECharts `visualMap`,把热力图的色带画成带刻度的连续尺(`ruler` / `continuous`)或分段色块(`piecewise`)。不配 / `null` / `enabled: false` 时零 DOM;默认跟随 `heatmap` 的字段、自动值域与颜色,也能独立给字段 / 值域 / 颜色 / 外部容器。不写 `target` 时展示值域取第一类有数值的启用目标,交互门控覆盖 `heatmap` 的全部启用目标;显式写才限定为一类,见「图例(visualMap)」 |
37
38
  | `effects` | `GisViewEffectsInput` | 关 | 动画效果(全局缺省):线路上的跑动图标(`line`,ECharts `lines.effect` 那一类)与节点上的扩散涟漪(`point`,`effectScatter` 那一类),逐要素用 `points[].effect` / `lines[].effect` 覆盖;没配就是关的,但「只在数据里逐条配了 `effect`」的场景照样会动。`false` 或 `{ enabled: false }` 是**一处关掉**(谁都别动);运行时有 `updateOptions({ effects })` / `getEffects()` / `getLineEffects()`,见「动画效果(effects)」 |
38
39
  | `lineDirection` | `GisViewLineDirectionInput` | 关(`null`) | 线路方向箭头(全局缺省):沿线摆一串静态箭头,朝向跟着线路切向走(起点 / `from` 那一端 → 终点,`reverse` 指回上游),按弧长等分、缺省每条线只在中点一个;逐条线路用 `lines[].direction` 覆盖,写法与 `effects` 同构(`true` 用全局、对象逐字段覆盖、`false` / `null` 关掉),见「线路方向箭头(lineDirection)」 |
39
40
  | `prebake` | `GisViewPrebakeOptions \| true` | 关 | 贴图符号预热:把「这次画面会出现什么」提前告诉组件 —— 两种写法:`combos` 逐组合(精确,`getSymbolCombos()` 收出来的那份)/ `symbols × pointColors × sizes` 与 `lineColors × 线路动画图标` 全排列(省事,颜色与尺寸由同一个字段决定时会多烤很多)—— 第一帧之前就烤好,画到它们时命中的是现成的贴图(省掉解码 + 光栅化那一下);组合数与数据量无关,不配就是从前那条按需路径。读数 `getPrebake()` / `getSymbolCombos()`、手动再烤一遍 `runPrebake()`,见「贴图符号预热(prebake)」 |
@@ -58,6 +59,7 @@
58
59
  | `fitSpacing` | `boolean \| GisViewFitSpacingOptions` | 关 | 适应视图的节点间距兜底:`true` 把最近节点中心距精确设为 `24` CSS 像素;对象在 `minDistance`(精确屏距,可放大也可缩小)/ `maxDistance`(最远距离上限,只压低比例)中二选一,同时给出时只启用 `maxDistance`;`maxZoom` 仅为旧配置兼容保留,不再限制距离;只在首次默认适应与 `fitView()` 生效,远处孤立点允许落到视口外,见「适应视图的节点间距(fitSpacing)」 |
59
60
  | `boxSelect` | `boolean` | `false` | 是否允许按住框选快捷键(`shortcuts.boxSelect`,默认 Shift)拖动鼠标框选要素;**缺省关**,写 `boxSelect: true` 或 `features: { boxSelect: true }` 才开。框选默认只收节点,`selectionKinds` 可把线路 / 通道也纳入 |
60
61
  | `multiSelect` | `boolean` | `false` | 多选开关:打开后不用按修饰键,直接单击节点 / 线路就取反累加选中(与按住 `shortcuts.multiSelect` 点击同一套行为、同一个 `multiSelectStyle`;取反那一下不经过 `onClick`,`onSelectChange.action.type` 是 `toggleSelect`);**缺省关**,关着时多选走框选(`boxSelect`)与按住多选快捷键(默认 Ctrl / ⌘)。运行时 `updateOptions({ multiSelect })` 切换只重画一帧、不重排场景 |
62
+ | `toggleSelect` | `boolean` | `false` | 再次选中即取消:打开后普通单击已选中的节点 / 线路 / 通道会取消,`selectTrigger: 'dblClick'` 下再次双击取消,Ctrl / ⌘ 先选中后再普通单击也取消;取反时 `onClick.action.type` 与 `onSelectChange.action.type` 都是 `toggleSelect`。缺省关,运行时 `updateOptions({ toggleSelect })` 只重画一帧、不重排场景,见「选中与拐点」 |
61
63
  | `selectTrigger` | `'click' \| 'dblClick'` | `'click'` | 选中触发方式:`click` 第一次单击就改选中集;`dblClick` 第一下只派发 `onClick`,等双击判定命中的第二下才改选中集,并同时派发 `onClick` 与 `onDblClick`。Ctrl / ⌘ 多选与 `multiSelect` 在双击模式下同样等第二下才取反。运行时走 `updateOptions({ selectTrigger })`,只重画一帧、不重排场景 |
62
64
  | `selectionKinds` | `GisViewSelectionKindsInput` | 无(框选只收节点;Ctrl 多选收节点 / 线路 / 通道) | 框选与 Ctrl / 多选点击的类型过滤:`'point'` 节点、`'line'` 通道外的独立线路、`'channel'` 通道回线;另有 `'lineEndpoints'` / `'channelEndpoints'`,只在框选时要求线路 / 通道两端节点都在框内。可写数组任意组合,`'all'` / `true` 全开,`false` / `null` 全关。通道回线只归 `channel` 管,通道外的线路只归 `line` 管;通道回线仍按 `channelSelectMode` 摊开。不写时保留旧口径;普通单选与 `select` / `selectFeatures` 不受它限制。运行时走 `updateOptions({ selectionKinds })`,见「框选」与「选择类型过滤」 |
63
65
  | `selectionBoxStyle` | `GisViewSelectionBoxStyle` | 蓝底 0.16 透明 + 1px 蓝框 | 框选矩形的样式:`fill` / `fillOpacity` / `stroke` / `strokeWidth`,以及 DOM 渲染用的 `render: 'div'` / `className` / `borderStyle` / `radius`;canvas 模式不画线型与圆角,见「框选」 |
@@ -120,11 +122,11 @@
120
122
  | 通道几何 | `channelWidth` · `channelInset` · `channelNodeWidth` · `channelTangentInset` · `channelMaxGroups` · `channelGroupMaxLines` · `channelSimplify` · `channelDraftStyle` · `channelSelectMode` · `channelWaypointDragMode` · `channelHiddenLineLayout` |
121
123
  | 样式与规则 | `pointStyle` · `lineStyle` · `pointRules` / `lineRules` · `heatmap` · `effects` · `lineDirection` · `prebake` · `label` · `labelOptimize` · `labelRender` · `lineLabel` · `labelStates` · `tooltip` · `pointSizeScale` / `lineWidthScale`、`pointZoomScale` / `lineZoomScale`、`zoomBaseScale` · `cursorStyle` · `gridColor` · `gridSize` · `showGrid` · `networkVisible` · `networkOpacity` |
122
124
  | 状态样式与选中 | `pointHoverStyle` / `pointSelectStyle` · `lineHoverStyle` / `lineSelectStyle` · `pointMultiSelectStyle` / `pointBoxSelectStyle` · `lineMultiSelectStyle` · `defaultHighlight` · `selectionBoxStyle` · `selectionBoxPersistent` · `selectionOnTop` · `waypointStyle` / `waypointSelectStyle` |
123
- | 交互开关 | `boxSelect` · `multiSelect` · `selectTrigger` · `waypointEditable` · `waypointVisibility` · `interactive` · `preventContextMenu` · `clickable` · `draggable` · `liveDrag` · `lineFollowsNode` · `shortcuts` · `copy` · `remove` · `merge` |
125
+ | 交互开关 | `boxSelect` · `multiSelect` · `toggleSelect` · `selectTrigger` · `waypointEditable` · `waypointVisibility` · `interactive` · `preventContextMenu` · `clickable` · `draggable` · `liveDrag` · `lineFollowsNode` · `shortcuts` · `copy` · `remove` · `merge` |
124
126
  | 视图与相机 | `minScale` / `maxScale` · `fitPadding` · `fitOnInit` · `center` / `scale` · `initialView` · `zoomStep` |
125
127
  | 画质与渲染 | `quality` · `width` / `height` · `pixelRatio` · `maxCanvasPixels` · `antialias` · `interactionThinLines` · `interactionNoMsaa` · `renderer` · `webglLayers` |
126
128
  | 大数据(筛选 / 搜索 / 读数) | `denseMode` · `filter` · `filterWorker` · `searchWorker` · `progressiveInit` · `statsInterval` · `idleTimeout` · `renderEmitInterval` |
127
- | 外部界面与集成 | `minimap` · `plugins` · `ariaLabel` |
129
+ | 外部界面与集成 | `minimap` · `visualMap` · `plugins` · `ariaLabel` |
128
130
 
129
131
  ### 总开关与历史
130
132
 
@@ -406,6 +408,45 @@ createGisView(container, { heatmap: { field: 'load', radius: 26, colors: ['#2f6f
406
408
  view.updateOptions({ heatmap: { enabled: false } }) // 关掉回正常配色
407
409
  ```
408
410
 
411
+ #### `visualMap`
412
+
413
+ - **类型**:`GisViewVisualMap | null`
414
+ - **默认值**:无
415
+ - **说明**:内置图例。参考 ECharts 的 `visualMap`,把热力图的色带画成带刻度的连续尺(`type: 'ruler'`,`'continuous'` 同义)或分段色块(`type: 'piecewise'`)。配置对象本身就是开关;不写 / `null` / `enabled: false` 时不挂任何 DOM。默认跟随 `heatmap` 的字段、自动值域、色带与透明度。不配 `target` 时不会把门控缩成第一类:展示值域按 `point → line → channel` 取第一个真正有数值的启用目标,选区却门控全部启用目标(同一 `field`);例如节点缺热力字段、线路 / 通道有值时,图例显示线路 / 通道值域,同时仍能隐藏落选节点、独立线路与通道。显式 `target` 才限定只作用这一类。没有 `heatmap` 也可以按自己的 `field` 扫数据画独立图例。默认只读、不参与画布拾取;写 `interactive: true` 后内置分段点选与连续尺区间拖动,并把当前选区同步到热力层。默认继承主图热力的鹰眼图同步门控,显式 `minimap.heatmap` 仍独立。`outOfRange` 决定落选值怎么处理:`'heatmap'`(缺省)只退热力、网架恢复普通样式;`'network'` 把对应节点 / 独立线路 / 通道从画布、拾取和鹰眼中隐藏;`'none'` 不做内置门控,只保留 `onChange`。三种模式都不删除数据、不改筛选或通道声明。`onChange` 返回当前选择供额外业务联动,`render(context)` 可完全接管图例内容;只改图例本身时只重画这一层,不触发无关场景重排。读数用 `getVisualMap()`,根节点用 `getVisualMapElement()`
416
+ - **参考**:见「图例(visualMap)」一节,子字段、`ruler` / `piecewise` 写法、交互 / 自定义渲染、外部容器与性能说明都在那里
417
+ - **示例**:
418
+
419
+ ```js
420
+ const view = createGisView(container, {
421
+ points,
422
+ heatmap: { field: 'load', density: false, glow: false },
423
+ visualMap: {
424
+ type: 'ruler',
425
+ interactive: true,
426
+ outOfRange: 'network',
427
+ title: '负荷',
428
+ unit: 'kW',
429
+ position: 'bottom-left',
430
+ tickCount: 5,
431
+ onChange: ({ range, source }) => console.log('区间', range, source)
432
+ }
433
+ })
434
+
435
+ // 分段图例:写自己的字段 / 值域也能完全独立于 heatmap
436
+ view.updateOptions({
437
+ visualMap: {
438
+ type: 'piecewise',
439
+ field: 'score',
440
+ domain: [0, 100],
441
+ pieces: [
442
+ { lt: 60, label: '低' },
443
+ { gte: 60, lt: 85, label: '中' },
444
+ { gte: 85, color: '#dd4b2e', label: '高' }
445
+ ]
446
+ }
447
+ })
448
+ ```
449
+
409
450
  #### `effects`
410
451
 
411
452
  - **类型**:`GisViewEffectsInput`
@@ -632,7 +673,7 @@ createGisView(container, {
632
673
 
633
674
  - **类型**:`boolean | GisViewCascadeHighlightOptions`
634
675
  - **默认值**:关
635
- - **说明**:点击级联高亮,和选中集是两份互不污染的状态。点节点时亮自身、直接相连的线路和另一端节点;点线路 / 通道回线时只亮两端节点。直接选中的节点 / 线路始终走选中样式,只有邻接项套 `pointStyle` / `lineStyle`。变化会通过 `onCascadeHighlight` / `cascadeHighlight` 事件给出,读数用 `getCascadeHighlight()`
676
+ - **说明**:点击级联高亮,和选中集是两份互不污染的状态。点节点时亮自身、直接相连的线路和另一端节点;点线路 / 通道回线时只亮两端节点。直接选中的节点 / 线路始终走选中样式,只有邻接项套 `pointStyle` / `lineStyle`。变化会通过 `onCascadeHighlight` / `cascadeHighlight` 事件给出,读数用 `getCascadeHighlight()`;通过 `toggleSelect` / Ctrl 取反 / `deselect` / `selectFeatures` 的 remove 或清空取消选中时,级联同步收掉
636
677
  - **参考**:见「点击级联高亮」一节
637
678
  - **子字段**:
638
679
 
@@ -794,6 +835,19 @@ createGisView(container, { multiSelect: true }) // 单击即取反累加,不
794
835
  // 按住快捷键的老写法照旧:Ctrl + 点击
795
836
  ```
796
837
 
838
+ #### `toggleSelect`
839
+
840
+ - **类型**:`boolean`
841
+ - **默认值**:`false`
842
+ - **说明**:再次选中即取消。打开后,普通单击已选中的节点 / 线路 / 通道回线会把它从选中集移除;按住 Ctrl / ⌘ 先选中,再普通单击同一要素也同样取消。通道回线按 `channelSelectMode` 取消单条 / 整组 / 整条;命中的分组或通道已经完整选中时,即使处于 `line` 模式也整组 / 整条取消,避免只删一条后仍像通道未取消。`selectionKinds` 只约束框选与 Ctrl / 多选,不拦截这里的取消。`selectTrigger: 'click'` 下双击仍按一次操作处理,第一下取反、第二下保持;`selectTrigger: 'dblClick'` 下由第二下取反,再次双击取消。取反生效时 `onClick.action.type` 与 `onSelectChange.action.type` 都是 `'toggleSelect'`,同一次点击产生的级联高亮也一起收掉。与 `multiSelect` 同时打开时沿用多选的累加取反口径;缺省关,运行时可用 `updateOptions({ toggleSelect })` 切换
843
+ - **参考**:见「[08 选中与拐点](08-选中与拐点.md#点击选中与-dblclick)」
844
+ - **示例**:
845
+
846
+ ```js
847
+ createGisView(container, { toggleSelect: true })
848
+ view.updateOptions({ toggleSelect: false }) // 关掉后再次单击保持选中
849
+ ```
850
+
797
851
  #### `selectTrigger`
798
852
 
799
853
  - **类型**:`'click' | 'dblClick'`(也认 `doubleClick` / `double` 两种别名)
@@ -811,7 +865,7 @@ view.updateOptions({ selectTrigger: 'click' })
811
865
 
812
866
  - **类型**:`GisViewSelectionKindsInput`
813
867
  - **默认值**:无(框选只收节点;Ctrl 多选仍收节点 / 线路 / 通道)
814
- - **说明**:限定框选与 Ctrl(⌘)/ `multiSelect` 点击能选中哪几类要素。`'point'` 是节点、`'line'` 是通道外的独立线路、`'channel'` 是通道回线;`'lineEndpoints'` / `'channelEndpoints'` 是框选专用的另一种口径,要求线路 / 通道自己的两端节点都落在框内,纯 `path`、缺失端点与自环跳过。数组可任意组合,`'all'` / `true` 五档全开,`false` / `null` 全关;新旧线路 / 通道口径并存时取并集。通道回线只受 `channel` 管、通道外的线路只受 `line` 管;选中通道回线时仍按 `channelSelectMode` 决定只选这一条 / 一组 / 整条通道。端点口径不会顺带选中两端节点,也不依赖 `point` 是否开启。**不写时保持旧口径**:框选只收节点,Ctrl / 多选点击收节点与线路;显式写 `selectionKinds` 后框选按完整过滤、Ctrl / 多选点击只认互斥的 `point` / `line` / `channel`。普通单击与 `select` / `selectFeatures` 接口不受限制;`selectInBox(box, { selectionKinds })` 可一次性覆盖。
868
+ - **说明**:限定框选与 Ctrl(⌘)/ `multiSelect` 点击能选中哪几类要素。`'point'` 是节点、`'line'` 是通道外的独立线路、`'channel'` 是通道回线;`'lineEndpoints'` / `'channelEndpoints'` 是框选专用的另一种口径,要求线路 / 通道自己的两端节点都落在框内,纯 `path`、缺失端点与自环跳过。数组可任意组合,`'all'` / `true` 五档全开,`false` / `null` 全关;新旧线路 / 通道口径并存时取并集。通道回线只受 `channel` 管、通道外的线路只受 `line` 管;选中通道回线时仍按 `channelSelectMode` 决定只选这一条 / 一组 / 整条通道。端点口径不会顺带选中两端节点,也不依赖 `point` 是否开启。**不写时保持旧口径**:框选只收节点,Ctrl / 多选点击收节点与线路;显式写 `selectionKinds` 后框选按完整过滤、Ctrl / 多选点击只认互斥的 `point` / `line` / `channel`。普通单击、`toggleSelect` 的再次点击取消与 `select` / `selectFeatures` 接口不受限制;`selectInBox(box, { selectionKinds })` 可一次性覆盖。
815
869
  - **参考**:见「08 选中与拐点」的「框选」与「选择类型过滤(selectionKinds)」
816
870
  - **示例**:
817
871
 
@@ -60,7 +60,7 @@ view.updateOptions({ cascadeHighlight: false }) // 关掉并收掉当前高亮
60
60
  - 框选触发时,框内每个命中项都参与邻接计算,取并集后一次派发;`origin` 取框内按数据顺序的第一个命中项作代表。框内直接命中的项走 `boxSelectStyle` / `lineSelectStyle`,框外的邻接节点 / 线路才套级联样式。
61
61
  - 点空白处是否收掉由 `clearOnEmptyClick` 控制,缺省 `true`。收掉、首次点亮、换节点 / 线路 / 通道或改范围都会发 `onCascadeHighlight` 与总线事件 `cascadeHighlight`;读数与回调收到的是同一份对象,收掉时是 `null`。
62
62
  - `origin.target` 为 `point` / `line` / `channel`:通道回线在底层仍是线路,但 `origin` 会另带 `channelId` / `channelIndex` / `groupId` / `groupIndex` / `channel`,方便直接回显“点的是哪条通道的哪一组”。
63
- - 数据整份更新后会按新的邻接关系刷新;来源被删除、被隐藏到不可拾取,或 `cascadeHighlight` 关掉时,高亮自动收掉。它不写 `selectionKeys`,`getSelectionData()` / `getSelections()` 不会因为级联高亮多出条目。
63
+ - 数据整份更新后会按新的邻接关系刷新;来源被删除、被隐藏到不可拾取、选中被取消(`toggleSelect` / Ctrl 取反 / `deselect` / `selectFeatures` 的 remove 或清空),或 `cascadeHighlight` 关掉时,高亮自动收掉。取消那一击只清旧级联,不会先点亮再发一条 `null`。它不写 `selectionKeys`,`getSelectionData()` / `getSelections()` 不会因为级联高亮多出条目。
64
64
  - 综合 Demo(`?demo=full`)的「视图 · 交互 → 交互开关与快捷键」面板已接入这项能力:可现场开关、在「节点 + 直连线路 / 只亮节点」之间切换,并直接读 `getCascadeHighlight()` 的来源、范围与节点 / 线路条数。
65
65
 
66
66
  ## 指针事件
@@ -13,6 +13,7 @@
13
13
  | 换一份选中集 | `view.selectFeatures(refs)`(缺省 `mode: 'replace'`) |
14
14
  | 追加 / 取反 / 移除 | `view.selectFeatures(refs, { mode: 'add' \| 'toggle' \| 'remove' })` |
15
15
  | 多选 | 打开 `multiSelect` 后单击,或按住 Ctrl / ⌘ 单击;框选先开 `boxSelect`,再按住 Shift 拖动 |
16
+ | 再次选中取消 | 打开 `toggleSelect` 后再次普通单击已选要素;Ctrl / ⌘ 先选中后再普通单击也取消 |
16
17
  | 取消指定项 | `view.deselect(refs)`,等价于 `view.selectFeatures(refs, { mode: 'remove' })` |
17
18
  | 取消全部 | 单击空白处、无其它编辑态时按 Esc、`view.deselect()`、`view.selectFeatures(null)` 或 `view.selectFeatures([])` |
18
19
  | 读回结果 | `view.isSelected(ref)` / `view.getSelections()` / `view.getSelectionData()` |
@@ -91,13 +92,17 @@ view.clearSelectionBox() // 只收起矩形,不改选中集
91
92
 
92
93
  不想拖动时也可以用 Ctrl(macOS 为 Cmd)逐个点击节点累加选中,命中已选中的节点会把它取消掉;两种方式得到的选中集合一样,之后按住组内任意一个节点拖动就是整组移动。不想按修饰键就把多选开关打开(`options.multiSelect: true`,运行时用 `updateOptions({ multiSelect })` 切):普通单击即取反累加,线路也认(受 `channelSelectMode` 的整组 / 整条摊开约束);单击触发模式下双击的第二次点击不再取反,`onDblClick` 照常派发。多选命中的节点走多选样式(`options.pointMultiSelectStyle`,节点自己的 `multiSelectStyle` 优先),框选命中的走框选样式(`options.pointBoxSelectStyle`,节点自己的 `boxSelectStyle` 优先),两者缺省都与选中样式完全一致。注意框选样式只给节点;选中线路 / 通道回线时仍按 `lineSelectStyle` / `lineMultiSelectStyle` 上色。
93
94
 
95
+ 只想让「再次点同一个就取消」、又不想把普通单击都变成多选时,打开 `toggleSelect`。它只接管本来会改选中集、且命中项已经在选中集里的普通点击:单击模式下第一下取反、双击的第二下保持;`selectTrigger: 'dblClick'` 下由双击第二下取反,再次双击取消。Ctrl / ⌘ 先选中的要素,后续普通单击同样取消。取反生效时点击与选中变更的动作名都是 `toggleSelect`;这条取消路径不受 `selectionKinds` 限制,同一来源的级联高亮也会一起收掉。缺省关,运行时可用 `updateOptions({ toggleSelect })` 切换。
96
+
97
+ 点中通道回线时,取消范围跟随 `channelSelectMode`:`line` 取消命中的单条回线,`group` 取消该回线所在的整组,`channel` 取消整条通道;其他已选要素保留。若该回线所属分组 / 通道已经完整选中(例如先通过右键菜单或 `selectFeatures` 选中了整组 / 通道),即使当前是 `line` 模式,再次点击也会整组 / 整条取消,不会只删命中那一条、留下仍然选中的假象。部分选中时按当前模式取消展开范围,点到组内 / 通道内的另一条回线同样生效。独立线路始终只取消它自己。
98
+
94
99
  ### 点击选中与 dblClick
95
100
 
96
101
  `selectTrigger` 缺省是 `'click'`:命中的节点 / 线路在第一次单击时就进选中集,点空白也在第一次单击时清空。写 `selectTrigger: 'dblClick'` 后,第一次点击只派发 `onClick`(`action.type` 是 `'click'`),同一位置 320ms 内、6px 范围内的第二下才应用选择结果:
97
102
 
98
- - 点要素:选中它;`multiSelect` 或按住 Ctrl / ⌘ 时取反累加。第二下同时派发 `onClick`(`action.double: true`,取反时 `action.type` 为 `'toggleSelect'`,普通选中为 `'select'`)与 `onDblClick`(`action.type: 'dblClick'`)。
103
+ - 点要素:选中它;`multiSelect` 或按住 Ctrl / ⌘ 时取反累加。`toggleSelect` 打开时,已选中的要素在第二下取消,未选中的仍选中。第二下同时派发 `onClick`(`action.double: true`,取反时 `action.type` 为 `'toggleSelect'`,普通选中为 `'select'`)与 `onDblClick`(`action.type: 'dblClick'`)。
99
104
  - 点空白:第二下才清空选中集并派发 `onDblClick`。
100
- - `click` 模式下普通双击的 `onDblClick` 也照常派发,只是第一次单击已经完成了选中;Ctrl / `multiSelect` 的取反快捷路保持原口径(不额外发 `onClick` / `onDblClick`)。两种模式都不影响拐点双击删除与通道草稿等既有双击交互。
105
+ - `click` 模式下普通双击的 `onDblClick` 也照常派发,只是第一次单击已经完成了选中;Ctrl / `multiSelect` 的取反快捷路保持原口径(不额外发 `onClick` / `onDblClick`)。`toggleSelect` 放在任一模式都只取反一次:双击模式看第二下,单击模式看第一下。两种模式都不影响拐点双击删除与通道草稿等既有双击交互。
101
106
 
102
107
  ```js
103
108
  createGisView(container, {
@@ -136,7 +141,7 @@ view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['
136
141
  view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['channelEndpoints'] })
137
142
  ```
138
143
 
139
- **不写 `selectionKinds` 时保留旧行为**:框选只收节点,Ctrl / 多选点击仍收节点与线路(通道回线按 `channelSelectMode` 摊开)。显式写了之后,框选与 Ctrl / 多选点击统一按这一份过滤;普通单选、`selectFeatures` 这些接口入口不受它限制。运行中用 `updateOptions({ selectionKinds })` 切换即可,不需要重排场景。
144
+ **不写 `selectionKinds` 时保留旧行为**:框选只收节点,Ctrl / 多选点击仍收节点与线路(通道回线按 `channelSelectMode` 摊开)。显式写了之后,框选与 Ctrl / 多选点击统一按这一份过滤;普通单选、`toggleSelect` 的再次点击取消、`selectFeatures` 这些接口入口不受它限制。运行中用 `updateOptions({ selectionKinds })` 切换即可,不需要重排场景。
140
145
 
141
146
  ## 接口选中(selectFeatures)与读回选中
142
147
 
@@ -232,7 +237,7 @@ view.focusOn(selection.items, { select: true, zoom: true }) // 反过来也行
232
237
 
233
238
  ### 取消选中(deselect / selectFeatures(null))
234
239
 
235
- 取消某一个 / 某一类 / 全部都是**一句话**(`deselect()` 与 `selectFeatures(null)` 都表示全部取消):与选中落在同一份 state 上,清完 `getSelections()` / `getSelectionData()` 立刻读得到;选中集实际变化时按 `onSelect → onSelectChange` 派发,`onSelect` 的载荷为 `null`、`cause` 为 `'api'`。
240
+ 取消某一个 / 某一类 / 全部都是**一句话**(`deselect()` 与 `selectFeatures(null)` 都表示全部取消):与选中落在同一份 state 上,清完 `getSelections()` / `getSelectionData()` 立刻读得到;选中集实际变化时按 `onSelect → onSelectChange` 派发,`onSelect` 的载荷为 `null`、`cause` 为 `'api'`。这次真的移除了选中项时,点击级联也同步收掉并补一条 `onCascadeHighlight(null)`,不留下残留高亮。
236
241
 
237
242
  ```js
238
243
  view.deselect('station-1024') // 取消一个节点(id,或 { type: 'point', index: 3 })
@@ -236,6 +236,8 @@ createGisView(container, {
236
236
 
237
237
  优先级可以记成一张表:**状态样式 > 热力图 > `override` 规则 > 要素自身字段 > 普通规则 > `pointStyle` / `lineStyle` > 内置默认**。热力图只作用于配了色带 / 倍率的字段,其余字段继续按这张表回落。普通规则永远高于全局样式;默认规则不压逐条配置,只有 `override: true` 才会压过它。多条规则同时命中同一字段时,普通层内后命中的赢,`override` 层内也是后命中的赢,且 `override` 层整体高于普通层,与数组先后无关。
238
238
 
239
+ `options.visualMap` 不单独占用样式优先级,也不会改写规则或状态样式;它读同一套热力字段 / 值域 / 色带,并把当前选区接入运行时门控:显式 `target` 时只有该类、同 `field` 的热力要素在落选后按上面的优先级回落到普通样式;不写 `target` 时覆盖 `heatmap` 的全部启用目标,光晕与密度也一并退出。其它字段的要素不受影响。`onChange` 仍可把选择交给业务侧做额外联动。
240
+
239
241
  点 / 线配置里那些单独的样式字段(`color`、`size`、`symbol`、`hoverStyle`…)只会被 `override` 规则盖住;需要整批换配色时,`updateOptions({ pointRules: [...], lineRules: [...] })` 传新数组即可。规则在编译时预计算真正写到的字段,10 万级数据下逐点判定与字段合并不会成为热点。
240
242
 
241
243
  `zIndex` 写在规则里就是「按属性分层」:同一个值的节点连成一层,层与层之间从下往上画(层内仍是先贴图组、再形状批次)。分层把「谁压谁」从「提交顺序」变成确定的顺序,代价是绘制序列要按层切开——同一张符号贴图跨层出现就会一层一批,形状点也是一层一批,层数越多绘制调用越多(实测 4 层比不分层多 2 次绘制调用,帧耗时不变;几十层才会显出来)。它不减少填充量,因此不提高帧率;十万级数据的性能靠的还是同一层内合批、`progressiveInit` 与增量上传这几项。