gis-view-webgl2 0.1.11 → 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 (54) hide show
  1. package/README.md +0 -24
  2. package/dist/gis-view.css +1 -1
  3. package/dist/index.cjs +1 -1
  4. package/dist/index.d.ts +524 -33
  5. package/dist/index.js +1 -1
  6. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +156 -8
  7. package/docs/GisView.md +6 -6
  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 +157 -7
  9. package/docs/demos/BackgroundDemo.vue +600 -0
  10. package/docs/demos/ChannelDemo.vue +145 -3
  11. package/docs/demos/StyleDemo.vue +280 -69
  12. package/docs/demos/api-index.js +35 -19
  13. package/docs/demos/demos.js +26 -18
  14. package/docs/demos/gis-view-demo/demo.css +76 -0
  15. package/docs/demos/gis-view-demo/panels/StylePanels.vue +24 -8
  16. package/docs/demos/gis-view-demo/panels/ViewPanels.vue +33 -3
  17. package/docs/demos/gis-view-demo/useGisViewDemo.js +236 -22
  18. package/docs/format/02-/350/212/202/347/202/271/344/270/216/347/272/277/350/267/257.md +1 -1
  19. package/docs/format/03-/346/213/220/347/202/271/350/243/205/351/245/260/344/270/216/346/240/267/345/274/217.md +3 -3
  20. package/docs/from0/06-/344/273/216/346/225/260/346/215/256/345/210/260/345/256/236/344/276/213-/350/247/243/346/236/220/345/261/202.md +13 -11
  21. package/docs/from0/15-/351/231/204/345/275/225-/345/270/270/351/207/217/351/200/237/346/237/245/344/270/216/350/270/251/345/235/221/346/270/205/345/215/225.md +1 -1
  22. package/docs/index.html +2 -2
  23. package/docs/internals/10-/350/247/204/345/210/231/346/235/241/344/273/266/344/270/216/347/212/266/346/200/201/346/240/267/345/274/217.md +3 -3
  24. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +1 -1
  25. package/docs/internals/14-/350/203/214/346/231/257/345/261/202.md +1 -0
  26. 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 +199 -0
  27. package/docs/internals/17-/350/260/203/350/257/225/344/270/216/346/211/251/345/261/225/345/273/272/350/256/256.md +1 -1
  28. 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 +32 -0
  29. package/docs/manual/02-/345/235/220/346/240/207/344/270/216/346/225/260/346/215/256.md +1 -1
  30. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +217 -26
  31. 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 +2 -2
  32. package/docs/manual/08-/351/200/211/344/270/255/344/270/216/346/213/220/347/202/271.md +37 -4
  33. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +58 -1
  34. package/docs/manual/11-/346/240/267/345/274/217/344/270/216/350/247/204/345/210/231.md +59 -8
  35. package/docs/manual/12-/346/240/207/347/255/276/344/270/216/350/243/205/351/245/260.md +1 -1
  36. package/docs/manual/13-/345/212/250/346/225/210/344/270/216/345/233/276/345/205/203.md +220 -2
  37. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +77 -16
  38. package/docs/manual/15-/345/257/274/345/207/272/344/270/216/351/233/206/346/210/220.md +30 -17
  39. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +11 -4
  40. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +17 -8
  41. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +128 -4
  42. 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 +2 -2
  43. package/docs/manual/20-/346/200/247/350/203/275/344/270/216/345/244/247/346/225/260/346/215/256.md +13 -1
  44. 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 +4 -1
  45. package/docs/steps/index.html +1 -1
  46. package/docs/steps/selftest-demo.html +2 -2
  47. package/docs/steps/selftest-demos.html +626 -11
  48. package/docs/steps/selftest-docs.html +8 -4
  49. package/docs/steps/selftest-features.html +1428 -2
  50. package/docs/steps/selftest-perf.html +296 -25
  51. package/docs/steps/selftest-scale.html +61 -3
  52. package/docs/steps/selftest-select.html +303 -1
  53. package/docs/steps/step-06-style.js +3 -2
  54. package/package.json +2 -2
@@ -6,7 +6,7 @@ const {
6
6
  perItemFollow, perItemOff, applyPrebake, applyPrebakeExact, runPrebakeNow, clearPrebake, building, activePanel,
7
7
  pointSize, lineWidth, lineTypeMode, dcLineMark, dcLineAnchor, dcLineParallel, lineDirectionMark,
8
8
  lineDirectionCount, lineDirectionReverse, dcLineButtonText, cursorMode, pointSizeScale, lineWidthScale,
9
- pointZoomScale, lineZoomScale, fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance, fitSpacingMaxZoom, pointSpacingReadout, zoomBaseReadout,
9
+ pointZoomScale, lineZoomScale, fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance, pointSpacingReadout, zoomBaseReadout,
10
10
  pointShape, symbolByStationType, symbolIconMode, symbolStrokeWidth, pointLayering, lineLayering, perPointStyle,
11
11
  perPointSymbol, applyRules, showLabels, labelPosition, labelMaxCount, labelVoltageScope, LABEL_VOLTAGE_SCOPES,
12
12
  labelAvoidOverlap, labelScanMode, labelOptimize, labelTemplate, labelHtml, labelBackground, labelColor,
@@ -21,7 +21,9 @@ const {
21
21
  heatmapDensityStyle, heatmapDensityLayer, heatmapDensityLevels, heatmapDensityRadius, heatmapDensityThreshold,
22
22
  heatmapDensityCore, heatmapDensityGamma, heatmapDensityWeight, heatmapDensityOpacity, heatmapGlow,
23
23
  heatmapGlowStrength, heatmapGlowLine, heatmapGlowPoint, heatmapGlowBlend, heatmapOpacityMin, heatmapOpacityMax,
24
- heatmapMinimap, heatmapFieldStats, applyZoomBase, labelEditId, newLabelPosition, newLabelPositions,
24
+ heatmapMinimap, heatmapFieldStats, visualMapEnabled, visualMapType, visualMapInteractive, visualMapCustom,
25
+ visualMapOutOfRange, VISUAL_MAP_OUT_OF_RANGE_MODES,
26
+ visualMapReadout, visualMapLastChange, applyZoomBase, labelEditId, newLabelPosition, newLabelPositions,
25
27
  labelEditContent, labelEditFontSize, labelEditColor, labelEditReadout, labelStateReadout, applyLabelEdit,
26
28
  resetLabelEdit, resetAllLabels, labelRows, labelListReadout, labelRoundTripReadout, pickLabelRow, addLabelHere,
27
29
  removeLabelHere, seedInitialLabels, roundTripLabels, saveLabelStates, restoreLabelStates, rerollHeatData,
@@ -33,6 +35,25 @@ const {
33
35
  <div v-show="activePanel === 'style-heat'" class="demo-panel-body">
34
36
  <div class="demo-controls">
35
37
  <label><input v-model="heatmapEnabled" type="checkbox" /> 启用热力图</label>
38
+ <label><input v-model="visualMapEnabled" type="checkbox" :disabled="!heatmapEnabled" /> 显示图例(visualMap)</label>
39
+ <label class="demo-slider">
40
+ 图例形态
41
+ <select v-model="visualMapType" :disabled="!heatmapEnabled || !visualMapEnabled">
42
+ <option value="ruler">连续尺 ruler</option>
43
+ <option value="piecewise">分段色块 piecewise</option>
44
+ </select>
45
+ </label>
46
+ <label><input v-model="visualMapInteractive" type="checkbox" :disabled="!heatmapEnabled || !visualMapEnabled" /> 图例可交互(点选 / 拖动区间)</label>
47
+ <label><input v-model="visualMapCustom" type="checkbox" :disabled="!heatmapEnabled || !visualMapEnabled" /> 完全自定义渲染</label>
48
+ <label class="demo-slider">
49
+ 落选值
50
+ <select v-model="visualMapOutOfRange" :disabled="!heatmapEnabled || !visualMapEnabled">
51
+ <option v-for="item in VISUAL_MAP_OUT_OF_RANGE_MODES" :key="item.value" :value="item.value">{{ item.label }}</option>
52
+ </select>
53
+ </label>
54
+ <span v-if="heatmapEnabled && visualMapEnabled" class="demo-control-hint">
55
+ {{ visualMapReadout || (visualMapCustom ? '自定义图例:点色块按钮调用 context.setSelection / setRange;不写 target 时节点、独立线路与通道一起按 max_loading_rate 门控' : '点分段或拖动区间:不写 target 时三类一起门控;落选值默认隐藏对应网架,也可切成只退热力或仅 onChange') }}<template v-if="visualMapLastChange"> · {{ visualMapLastChange }}</template>
56
+ </span>
36
57
  <label class="demo-slider">
37
58
  作用对象
38
59
  <select v-model="heatmapTarget" :disabled="!heatmapEnabled">
@@ -162,7 +183,7 @@ const {
162
183
  <span class="demo-control-hint">{{ heatmapFieldStats }}</span>
163
184
  </div>
164
185
  <div class="demo-controls">
165
- <details class="demo-hint-more"><summary>说明</summary><span class="demo-control-hint">热力图按 field 取数(这里固定 max_loading_rate)到 colors 色带上取颜色,再按 width / size 的区间用同一个比例缩放线宽与节点尺寸;优先级是「状态样式 &gt; 热力图 &gt; 要素自身 &gt; 规则 &gt; style &gt; 默认」,逐要素可以用 heatmap: false 排除、或写对象换字段 / 值域。回线自己带 max_loading_rate 就按自己的上色;自己没这个字段时才回落到所属通道的值(作用对象里要勾上通道),两边都没有就按「缺值」处理 —— 合成数据的通道与回线都带值,切「只按通道自己的值上色」就能对比「每一回各自的颜色」。节点与线路共用同一条色带:合成数据的节点与线路都按同一份「热区场」取值(几团热点),切「只按节点上色」看节点自己的颜色、切「节点 + 线路 + 通道」看两层叠在一起的效果。new.json 里线路这一列恒为 1.5、节点根本没有这一列,真实数据看不出渐变 —— 想看效果就切到「合成数据」,或在任意数据集上点上面的「重新随机」现生成一份(样例数据的线路是按序号派生的演示值)。这里的每一项都是 updateOptions({ heatmap }),数据与拾取都不动</span></details>
186
+ <details class="demo-hint-more"><summary>说明</summary><span class="demo-control-hint">热力图按 field 取数(这里固定 max_loading_rate)到 colors 色带上取颜色,再按 width / size 的区间用同一个比例缩放线宽与节点尺寸;优先级是「状态样式 &gt; 热力图 &gt; override 规则 &gt; 要素自身 &gt; 普通规则 &gt; style &gt; 默认」,逐要素可以用 heatmap: false 排除、或写对象换字段 / 值域。回线自己带 max_loading_rate 就按自己的上色;自己没这个字段时才回落到所属通道的值(作用对象里要勾上通道),两边都没有就按「缺值」处理 —— 合成数据的通道与回线都带值,切「只按通道自己的值上色」就能对比「每一回各自的颜色」。节点与线路共用同一条色带:合成数据的节点与线路都按同一份「热区场」取值(几团热点),切「只按节点上色」看节点自己的颜色、切「节点 + 线路 + 通道」看两层叠在一起的效果。new.json 里线路这一列恒为 1.5、节点根本没有这一列,真实数据看不出渐变 —— 想看效果就切到「合成数据」,或在任意数据集上点上面的「重新随机」现生成一份(样例数据的线路是按序号派生的演示值)。这里的每一项都是 updateOptions({ heatmap }),数据与拾取都不动</span></details>
166
187
  </div>
167
188
  </div>
168
189
 
@@ -271,11 +292,6 @@ const {
271
292
  <input v-model.number="fitSpacingMaxDistance" type="number" min="1" step="1" :disabled="!fitSpacing" />
272
293
  <span>{{ fitSpacingMaxDistance }}px</span>
273
294
  </label>
274
- <label class="demo-slider">
275
- 最大放大
276
- <input v-model.number="fitSpacingMaxZoom" type="number" min="0" step="1" :disabled="!fitSpacing" />
277
- <span>{{ fitSpacingMaxZoom > 0 ? `×${fitSpacingMaxZoom}` : '不限' }}</span>
278
- </label>
279
295
  <button type="button" @click="applyZoomBase()">以当前比例为基准</button>
280
296
  <span class="demo-control-hint">放大与跟随缩放是两个系数:滚轮缩放时再乘一次联动系数(基准为首次适应视图的比例);「适应节点间距」的最小 / 最大中心距二选一,首次适应与 fitView 会按所选屏幕距离调整比例,远处孤立点允许落到视口外{{ pointSpacingReadout ? ` · ${pointSpacingReadout}` : '' }}{{ zoomBaseReadout ? ` · ${zoomBaseReadout}` : '' }}</span>
281
297
  </div>
@@ -5,7 +5,9 @@ const {
5
5
  backgroundMode, backgroundOpacity, BACKGROUND_SOURCES, backgroundSource, backgroundImageFit,
6
6
  backgroundImageCorners, provinceAspectScale, provincePalette, provinceLabelShow, provinceLabelSize,
7
7
  provinceLabelColor, provinceLoading, INITIAL_VIEW_MODES, initialViewMode, initialZoom, initialCenterText,
8
- initialViewReadout, draggable, liveDrag, liveDragHint, clickable, boxSelect, multiSelect, selectionBoxMode,
8
+ initialViewReadout, draggable, liveDrag, liveDragHint, clickable, boxSelect, selectionBoxPersistent,
9
+ selectionBoxRender, selectionBoxBorderStyle, selectionBoxRadius, selectionBoxUseClassName, selectionBoxReadout,
10
+ readSelectionBox, clearRetainedSelectionBox, multiSelect, toggleSelect, selectionBoxMode,
9
11
  SHORTCUT_MODES, shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate,
10
12
  shortcutReadout, COPY_OFFSETS, copyEnabled, copyOffsetMode, mergeEnabled, mergeTrigger, MERGE_TRIGGERS,
11
13
  mergeDistance, MERGE_CONFIRM_MODES, mergeConfirmMode, mergeChannelGroups, selectionOnTop, copyRollback,
@@ -191,6 +193,7 @@ const {
191
193
  <label><input v-model="clickable" type="checkbox" /> 可点击</label>
192
194
  <label><input v-model="boxSelect" type="checkbox" /> 框选</label>
193
195
  <label><input v-model="multiSelect" type="checkbox" /> 点击多选(multiSelect:不用按修饰键,单击即取反累加)</label>
196
+ <label><input v-model="toggleSelect" type="checkbox" /> 再次选中即取消(toggleSelect:Ctrl 选中后也可普通点击取消)</label>
194
197
  <label><input v-model="waypointEditable" type="checkbox" /> 可编辑拐点</label>
195
198
  <label><input v-model="waypointAlways" type="checkbox" /> 拐点始终显示</label>
196
199
  <label><input v-model="hoverHighlight" type="checkbox" /> 悬停强调</label>
@@ -370,14 +373,41 @@ const {
370
373
  </label>
371
374
  <label class="demo-slider">
372
375
  框选矩形
373
- <select v-model="selectionBoxMode">
376
+ <select v-model="selectionBoxMode" data-demo="selection-box-mode">
374
377
  <option value="default">组件默认(蓝)</option>
375
378
  <option value="amber">琥珀色 · 粗边框</option>
376
379
  <option value="pink">粉色 · 半透明填充</option>
377
380
  <option value="line">只描边(背景全透明)</option>
378
381
  </select>
379
382
  </label>
380
- <span class="demo-control-hint">Ctrl + 点击 / Shift + 拖动分别走多选与框选样式;节点或线路自己配了样式时以自身的为准;「框选矩形」换的是框本身(options.selectionBoxStyle:fill / fillOpacity / stroke / strokeWidth)</span>
383
+ <label class="demo-slider">
384
+ 矩形渲染
385
+ <select v-model="selectionBoxRender" data-demo="selection-box-render">
386
+ <option value="canvas">canvas(WebGL 实线直角)</option>
387
+ <option value="div">div(DOM,可线型 / 圆角 / 类名)</option>
388
+ </select>
389
+ </label>
390
+ <label><input v-model="selectionBoxPersistent" data-demo="selection-box-persistent" type="checkbox" /> 框选结束后保留矩形(框内拖动整组平移)</label>
391
+ <template v-if="selectionBoxRender === 'div'">
392
+ <label class="demo-slider">
393
+ 边框线型
394
+ <select v-model="selectionBoxBorderStyle" data-demo="selection-box-border-style">
395
+ <option value="solid">实线</option>
396
+ <option value="dashed">虚线</option>
397
+ <option value="dotted">点线</option>
398
+ </select>
399
+ </label>
400
+ <label class="demo-slider">
401
+ 圆角
402
+ <input v-model.number="selectionBoxRadius" data-demo="selection-box-radius" type="range" min="0" max="20" step="1" />
403
+ <span>{{ selectionBoxRadius }} px</span>
404
+ </label>
405
+ <label><input v-model="selectionBoxUseClassName" data-demo="selection-box-class-name" type="checkbox" /> 挂 demo-retained-box 类名(标签 + 阴影)</label>
406
+ </template>
407
+ <button type="button" data-demo="selection-box-read" @click="readSelectionBox()">读取 getSelectionBox()</button>
408
+ <button type="button" data-demo="selection-box-clear" @click="clearRetainedSelectionBox()">收起保留框 clearSelectionBox()</button>
409
+ <span class="demo-control-hint" data-demo="selection-box-readout">{{ selectionBoxReadout }}</span>
410
+ <span class="demo-control-hint">Shift + 拖动框选(跟随快捷键面板的「框选」档)后会保留矩形;打开「拖拽节点」时,在框内按住并拖动即可把选中的节点整组平移。节点或线路自己配了样式时以自身的为准;selectionBoxStyle 的 fill / fillOpacity / stroke / strokeWidth 管颜色与边框,div 档另有 borderStyle / radius / className。</span>
381
411
  </div>
382
412
 
383
413
  </div>
@@ -211,7 +211,7 @@ export const useGisViewDemo = () => {
211
211
  /**
212
212
  * 通道两端节点的样式:只把那一圈描边换成亮粉色做标记(节点都是白底 + 电压色描边,末端几个换个颜色才认得出;
213
213
  * 不能用琥珀 / 金这类颜色,525kV 那一档的描边就是琥珀色)——
214
- * 大小仍旧由电压分档决定,同一个电压等级的节点大小完全一致(点自身字段优先级最高,
214
+ * 大小仍旧由电压分档决定,同一个电压等级的节点大小完全一致(普通规则只低于点自身字段、高于 pointStyle;
215
215
  * 这里多写一个 color 就会把白底顶掉,所以标记只动描边这一层)
216
216
  */
217
217
  const ANCHOR_POINT_STYLE = {
@@ -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 }
@@ -587,6 +599,8 @@ export const useGisViewDemo = () => {
587
599
  */
588
600
  const handleSelectChange = payload => {
589
601
  syncSelectionFromView()
602
+ // 框选结束后保留框已经建立;这里顺手刷新读数,面板上的坐标不用再点一次按钮才会变。
603
+ readSelectionBox()
590
604
  const ids = payload.ids.slice(0, 2).map(String).join(' / ')
591
605
  selectChangeLabel.value = `${payload.cause} · ${payload.count} 个${payload.ids.length ? ` · ${ids}${payload.ids.length > 2 ? ' …' : ''}` : ''}`
592
606
  }
@@ -1684,8 +1698,19 @@ export const useGisViewDemo = () => {
1684
1698
  : '停手更新:拖动期间线路停在原处,松开指针后一次性跟上(大场景拖动更省)'))
1685
1699
  const clickable = ref(true)
1686
1700
  const boxSelect = ref(true)
1701
+ /** 框选结束后保留矩形:框内按下并拖动会把选中的节点整组平移(线路 / 通道跟着走) */
1702
+ const selectionBoxPersistent = ref(true)
1703
+ /** 保留框渲染在 WebGL 还是画布上方的 DOM:div 模式才能演示线型 / 圆角 / className */
1704
+ const selectionBoxRender = ref('div')
1705
+ const selectionBoxBorderStyle = ref('dashed')
1706
+ const selectionBoxRadius = ref(8)
1707
+ const selectionBoxUseClassName = ref(true)
1708
+ const selectionBoxReadout = ref('getSelectionBox():—')
1709
+ const SELECTION_BOX_CLASS = 'demo-retained-box'
1687
1710
  /** 多选开关:打开后不用按修饰键,普通单击就是取反累加(走 multiSelectStyle) */
1688
1711
  const multiSelect = ref(false)
1712
+ /** 再次选中即取消:普通点击已选要素取消;Ctrl 先选中后再普通点击也会取消 */
1713
+ const toggleSelect = ref(false)
1689
1714
  /**
1690
1715
  * 框选矩形(Ctrl + 拖动时画的那个框)的样式:颜色、背景、边框都在 options.selectionBoxStyle 里配,
1691
1716
  * 与选中样式是两回事;这里给几档预设方便对比,default 就是组件自带的那份蓝。
@@ -1697,7 +1722,35 @@ export const useGisViewDemo = () => {
1697
1722
  pink: { fill: '#ff9ff3', fillOpacity: 0.24, stroke: '#e056a0', strokeWidth: 1 },
1698
1723
  line: { fill: '#2f6fd0', fillOpacity: 0, stroke: '#2f6fd0', strokeWidth: 1 }
1699
1724
  }
1700
- const buildSelectionBoxStyle = () => SELECTION_BOX_STYLES[selectionBoxMode.value]
1725
+ /**
1726
+ * 框的样式分两层:上面的预设只管颜色 / 填充 / 边框宽;render 与 DOM 专属项叠在后面。
1727
+ * className 只在 demo 里挂一个可见的标签与阴影,证明整块外观都能由使用方接管。
1728
+ */
1729
+ const buildSelectionBoxStyle = () => {
1730
+ const base = SELECTION_BOX_STYLES[selectionBoxMode.value] ?? {}
1731
+ return {
1732
+ ...base,
1733
+ render: selectionBoxRender.value,
1734
+ ...(selectionBoxRender.value === 'div'
1735
+ ? {
1736
+ className: selectionBoxUseClassName.value ? SELECTION_BOX_CLASS : undefined,
1737
+ borderStyle: selectionBoxBorderStyle.value,
1738
+ radius: selectionBoxRadius.value
1739
+ }
1740
+ : {})
1741
+ }
1742
+ }
1743
+ const readSelectionBox = () => {
1744
+ const box = viewRef.value?.getSelectionBox() ?? null
1745
+ selectionBoxReadout.value = box
1746
+ ? `getSelectionBox():x ${box.x.toFixed(1)} · y ${box.y.toFixed(1)} · ${box.width.toFixed(1)} × ${box.height.toFixed(1)}`
1747
+ : 'getSelectionBox():null(还没有保留框;Shift + 拖动框选一批节点)'
1748
+ }
1749
+ const clearRetainedSelectionBox = () => {
1750
+ viewRef.value?.clearSelectionBox()
1751
+ readSelectionBox()
1752
+ writeLastEvent('clearSelectionBox():保留框已收起,选中集不变')
1753
+ }
1701
1754
  /**
1702
1755
  * 快捷键:组件所有快捷键都能改(options.shortcuts),这里挑「按住某键再操作」的四个动作做下拉。
1703
1756
  * 换成 Shift / Alt 之后,光标变体(cursorStyle.ctrl 与逐要素 ctrlCursor)也跟着新的修饰键走 ——
@@ -1990,7 +2043,6 @@ export const useGisViewDemo = () => {
1990
2043
  const fitSpacingMode = ref('min')
1991
2044
  const fitSpacingMinDistance = ref(24)
1992
2045
  const fitSpacingMaxDistance = ref(480)
1993
- const fitSpacingMaxZoom = ref(0)
1994
2046
  const pointSpacingReadout = ref('')
1995
2047
  const zoomBaseReadout = ref('')
1996
2048
  /** fitSpacing 只发送当前选中的距离模式,避免 minDistance 与 maxDistance 同时出现 */
@@ -1998,10 +2050,9 @@ export const useGisViewDemo = () => {
1998
2050
  if (!fitSpacing.value) {
1999
2051
  return false
2000
2052
  }
2001
- const maxZoom = fitSpacingMaxZoom.value
2002
2053
  return fitSpacingMode.value === 'max'
2003
- ? { maxDistance: fitSpacingMaxDistance.value, maxZoom }
2004
- : { minDistance: fitSpacingMinDistance.value, maxZoom }
2054
+ ? { maxDistance: fitSpacingMaxDistance.value }
2055
+ : { minDistance: fitSpacingMinDistance.value }
2005
2056
  }
2006
2057
  /** 节点形状:auto 用数据 / 规则自带的符号,其余把全部节点改成内置的圆或正方形 */
2007
2058
  const pointShape = ref('auto')
@@ -2032,13 +2083,13 @@ export const useGisViewDemo = () => {
2032
2083
  const lineLayering = ref(true)
2033
2084
  /**
2034
2085
  * 逐点样式(默认关):勾上之后每 43 个节点自带 color / size(就是前面那个金色节点),
2035
- * 演示「节点自身字段优先于批量规则与整体配置」——同电压等级的节点这时会看起来不一样。
2086
+ * 演示「节点自身字段高于普通规则与整体配置(override 规则除外)」——同电压等级的节点这时会看起来不一样。
2036
2087
  */
2037
2088
  const perPointStyle = ref(false)
2038
2089
  /**
2039
2090
  * 逐点符号(默认关):勾上之后每 24 个节点自带一个 symbol(琥珀色圆角方块),
2040
2091
  * 演示「单个节点可以单独配置符号」——它连批量规则都盖得住:勾上「节点形状」把整层换成圆 / 方,
2041
- * 这几个节点还是它们自己的样子(点自身字段 > 规则 > pointStyle 这条链的最后一环)。
2092
+ * 这几个节点还是它们自己的样子(普通规则高于 pointStyle,但低于点自身字段;override 规则才会压过逐点值)。
2042
2093
  */
2043
2094
  const perPointSymbol = ref(false)
2044
2095
  /** 逐点符号用的样式:颜色、形状、圆角、描边都写在节点自己身上(描边用深色,白底上看不见白边) */
@@ -2197,7 +2248,7 @@ export const useGisViewDemo = () => {
2197
2248
  /**
2198
2249
  * 点的批量样式规则:vn_kv 决定大小与描边色(填充固定白色),符号走 gisStyle.js 的类型表
2199
2250
  * (station 圆,其余类型都用 test.js 的 SVG 路径 —— 方框里画线那种)。
2200
- * 规则按顺序命中,后命中的覆盖前面的;点自身写了的字段始终优先(勾上面板上的「逐点样式」会出现几个自带走色的金色节点,用来看这一层优先于规则)。
2251
+ * 规则按顺序命中,后命中的覆盖前面的;点自身写了的字段优先于普通规则,单条规则写 override: true 后才会压过它(勾上面板上的「逐点样式」会出现几个自带走色的金色节点,用来看这一层优先于普通规则)。
2201
2252
  */
2202
2253
  const buildPointRules = () => [
2203
2254
  // 电压分档:阈值与 getGisStyle 完全一致,节点半径与描边粗细都取它的宽度分档(1 / 2 / 3 / 4 / 5),
@@ -3729,6 +3780,19 @@ export const useGisViewDemo = () => {
3729
3780
  const heatmapOpacityMax = ref(1)
3730
3781
  /** 鹰眼图要不要跟着上色:勾着(缺省)就沿用主图那一份,勾掉就只给小图关 */
3731
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('')
3732
3796
  /** 当前数据里 max_loading_rate 的读数:直接说明这一份数据「看不看得出渐变」,节点与线路分开报 */
3733
3797
  const heatmapFieldStats = computed(() => {
3734
3798
  const scan = (items) => {
@@ -3820,6 +3884,131 @@ export const useGisViewDemo = () => {
3820
3884
  : false
3821
3885
  }
3822
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
+ }
3823
4012
  /** 可见节点数:标签最多也就渲染这么多,面板上给个读数 */
3824
4013
  const syncVisibleCount = () => {
3825
4014
  visiblePointCount.value = viewRef.value?.listPoints({ visibleOnly: true }).length ?? 0
@@ -4079,18 +4268,18 @@ export const useGisViewDemo = () => {
4079
4268
  /**
4080
4269
  * 缩放限制(minScale / maxScale):相机比例的单位是「像素 / 世界单位」,
4081
4270
  * 滚轮、双击放大与程序化的 setCamera / zoomTo / zoomBy 都会被夹在这两条线之间;
4082
- * 缺省 0.002 ~ 20000 等于不限制(两个数字就是组件缺省值,这里原样摆出来做对照)。
4271
+ * 缺省 0.002 ~ 1000000000 覆盖经纬度这类小量纲数据(两个数字就是组件缺省值,这里原样摆出来做对照)。
4083
4272
  */
4084
4273
  const minScale = ref(0.002)
4085
- const maxScale = ref(20000)
4086
- const scaleLimitReadout = ref('当前未限制(组件缺省 0.002 ~ 20000 px/单位)')
4274
+ const maxScale = ref(1000000000)
4275
+ const scaleLimitReadout = ref('当前未限制(组件缺省 0.002 ~ 1000000000 px/单位)')
4087
4276
  /** 面板读数:当前比例 + 限制区间(区间等于缺省时明说「未限制」) */
4088
4277
  const syncScaleLimitReadout = () => {
4089
4278
  const view = viewRef.value
4090
4279
  if (!view) {
4091
4280
  return
4092
4281
  }
4093
- const limited = minScale.value > 0.0025 || maxScale.value < 19999
4282
+ const limited = minScale.value > 0.0025 || maxScale.value < 999999999
4094
4283
  scaleLimitReadout.value = `当前 ${view.getCamera().scale.toPrecision(4)} px/单位 · 限制 ${minScale.value} ~ ${maxScale.value}${limited ? '' : '(等于不限制)'}`
4095
4284
  }
4096
4285
  /** 把面板上的两条线写进组件(updateOptions),并立刻把当前相机夹回区间内 */
@@ -4122,7 +4311,7 @@ export const useGisViewDemo = () => {
4122
4311
  /** 解除限制:回到组件缺省的两条线 */
4123
4312
  const clearScaleLimits = () => {
4124
4313
  minScale.value = 0.002
4125
- maxScale.value = 20000
4314
+ maxScale.value = 1000000000
4126
4315
  applyScaleLimits()
4127
4316
  }
4128
4317
  /** 撞一下上下限:喂一个远超区间的比例,相机被夹到哪里由读数说 */
@@ -4215,7 +4404,10 @@ export const useGisViewDemo = () => {
4215
4404
  liveDrag: liveDrag.value,
4216
4405
  clickable: clickable.value,
4217
4406
  boxSelect: boxSelect.value,
4407
+ // 框选结束后保留矩形:框内拖动整组平移选中的节点;面板可随时改,右侧读数能看到坐标
4408
+ selectionBoxPersistent: selectionBoxPersistent.value,
4218
4409
  multiSelect: multiSelect.value,
4410
+ toggleSelect: toggleSelect.value,
4219
4411
  // 快捷键:框选 / 多选 / 加点 / 复制这四个「按住 + 操作」的修饰键都能改(缺省 Ctrl / Ctrl / Ctrl / Alt)
4220
4412
  shortcuts: buildShortcuts(),
4221
4413
  // 节点复制:Alt 点节点跟随鼠标落地,接口复制落在节点旁边(落点偏移在 copy.offset 里)
@@ -4238,6 +4430,8 @@ export const useGisViewDemo = () => {
4238
4430
  minimap: buildMinimapOptions(),
4239
4431
  // 热力图:按 max_loading_rate 给线路 / 通道回线(target 带上 point 时还有节点)取色带颜色并缩放尺寸
4240
4432
  heatmap: buildHeatmapOptions(),
4433
+ // 图例:不写字段 / 值域 / 色带,默认与热力图共用同一把尺
4434
+ visualMap: buildVisualMapOptions(),
4241
4435
  pointRules: applyRules.value ? buildPointRules() : [],
4242
4436
  lineRules: applyRules.value ? buildLineRules() : [],
4243
4437
  label: buildLabelOptions(),
@@ -4379,7 +4573,14 @@ export const useGisViewDemo = () => {
4379
4573
  watch(liveDrag, value => viewRef.value?.updateOptions({ liveDrag: value }))
4380
4574
  watch(clickable, value => viewRef.value?.updateOptions({ clickable: value }))
4381
4575
  watch(boxSelect, value => viewRef.value?.updateOptions({ boxSelect: value }))
4576
+ watch(selectionBoxPersistent, value => {
4577
+ viewRef.value?.updateOptions({ selectionBoxPersistent: value })
4578
+ if (!value) {
4579
+ readSelectionBox()
4580
+ }
4581
+ })
4382
4582
  watch(multiSelect, value => viewRef.value?.updateOptions({ multiSelect: value }))
4583
+ watch(toggleSelect, value => viewRef.value?.updateOptions({ toggleSelect: value }))
4383
4584
  // 快捷键自定义:四个「按住 + 操作」的动作改完立刻下发(框选 / 多选 / 加点的修饰键同时也是光标变体的判据)
4384
4585
  watch([shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate], () => {
4385
4586
  viewRef.value?.updateOptions({ shortcuts: buildShortcuts() })
@@ -4395,8 +4596,11 @@ export const useGisViewDemo = () => {
4395
4596
  viewRef.value?.updateOptions({ selectionOnTop: value })
4396
4597
  writeLastEvent(value ? '选中抬层:开(选中的节点 / 线路画在最上层)' : '选中抬层:关(完全按 zIndex 与数据顺序画)')
4397
4598
  })
4398
- // 框选矩形自身的样式:颜色、背景与边框宽度都在这一份里
4399
- watch(selectionBoxMode, () => viewRef.value?.updateOptions({ selectionBoxStyle: buildSelectionBoxStyle() }))
4599
+ // 框选矩形自身的样式:颜色 / 边框 / 渲染方式与 DOM 专属的线型、圆角、类名都在这一份里
4600
+ watch([selectionBoxMode, selectionBoxRender, selectionBoxBorderStyle, selectionBoxRadius, selectionBoxUseClassName], () => {
4601
+ viewRef.value?.updateOptions({ selectionBoxStyle: buildSelectionBoxStyle() })
4602
+ readSelectionBox()
4603
+ })
4400
4604
  watch(waypointEditable, value => viewRef.value?.updateOptions({ waypointEditable: value }))
4401
4605
  watch(waypointAlways, value => viewRef.value?.updateOptions({ waypointVisibility: value ? 'always' : 'selected' }))
4402
4606
  watch([pointSize, lineWidth], () => viewRef.value?.updateOptions({ pointStyle: buildPointStyle(), lineStyle: buildLineStyle(), ...hoverStylePatch() }))
@@ -4476,7 +4680,7 @@ export const useGisViewDemo = () => {
4476
4680
  pointSpacingReadout.value = `可见 ${spacing.count} 个 · 最近 ${screen(spacing.minDistance)} · 最远 ${screen(spacing.maxDistance)}`
4477
4681
  }
4478
4682
  // 节点间距兜底:改参数不移动相机,这里补一次 fitView 让面板上立刻看到效果;关掉时也恢复普通适应
4479
- watch([fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance, fitSpacingMaxZoom], ([enabled], [wasEnabled]) => {
4683
+ watch([fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance], ([enabled], [wasEnabled]) => {
4480
4684
  const view = viewRef.value
4481
4685
  if (!view) {
4482
4686
  return
@@ -4845,6 +5049,11 @@ export const useGisViewDemo = () => {
4845
5049
  heatmapGlow, heatmapGlowStrength, heatmapGlowLine, heatmapGlowPoint, heatmapGlowBlend, heatmapOpacityMin, heatmapOpacityMax,
4846
5050
  heatmapDensity, heatmapDensityStyle, heatmapDensityLayer, heatmapDensityLevels, heatmapDensityRadius, heatmapDensityThreshold, heatmapDensityCore, heatmapDensityGamma, heatmapDensityWeight, heatmapDensityOpacity
4847
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
+ })
4848
5057
  // 小图那一份:默认沿用主图(不写 heatmap 字段),勾掉「鹰眼图沿用」就只给小图关(heatmap: false)
4849
5058
  watch([heatmapEnabled, heatmapMinimap], () => viewRef.value?.updateOptions({ minimap: buildMinimapOptions() }))
4850
5059
  const applyChannelOptions = () => {
@@ -6128,7 +6337,9 @@ const members = (payload.lineData ?? payload.linePropsByMember ?? []).filter(Boo
6128
6337
  provinceGeoJson, provinceLoading, demoImageUrl, demoCanvasImage, DEMO_SVG_PATH, provinceGeoJsonUrl,
6129
6338
  loadProvinceGeoJson, buildDemoImageUrl, buildDemoCanvasImage, INITIAL_VIEW_MODES, initialViewMode,
6130
6339
  initialZoom, initialCenterText, initialViewReadout, draggable, liveDrag, liveDragHint, clickable, boxSelect,
6131
- multiSelect, selectionBoxMode, SELECTION_BOX_STYLES, buildSelectionBoxStyle, SHORTCUT_MODES,
6340
+ selectionBoxPersistent, selectionBoxRender, selectionBoxBorderStyle, selectionBoxRadius,
6341
+ selectionBoxUseClassName, selectionBoxReadout, SELECTION_BOX_CLASS, readSelectionBox,
6342
+ clearRetainedSelectionBox, multiSelect, toggleSelect, selectionBoxMode, SELECTION_BOX_STYLES, buildSelectionBoxStyle, SHORTCUT_MODES,
6132
6343
  shortcutBoxSelect, shortcutMultiSelect, shortcutAddWaypoint, shortcutDuplicate, shortcutValue, shortcutLabel,
6133
6344
  shortcutReadout, buildShortcuts, COPY_OFFSETS, copyEnabled, copyOffsetMode, buildCopyOptions, pointLabel,
6134
6345
  mergeEnabled, mergeTrigger, MERGE_TRIGGERS, mergeDistance, MERGE_CONFIRM_MODES, mergeConfirmMode,
@@ -6146,7 +6357,7 @@ const members = (payload.lineData ?? payload.linePropsByMember ?? []).filter(Boo
6146
6357
  cascadeTriggerOnBoxSelect, cascadeReadout, buildCascadeHighlightOptions,
6147
6358
  handleCascadeHighlight, readCascadeHighlight,
6148
6359
  searchKeyword, searchScope, searchFocus, searchReadout, pointSizeScale, lineWidthScale, pointZoomScale,
6149
- lineZoomScale, fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance, fitSpacingMaxZoom, pointSpacingReadout, zoomBaseReadout, pointShape,
6360
+ lineZoomScale, fitSpacing, fitSpacingMode, fitSpacingMinDistance, fitSpacingMaxDistance, pointSpacingReadout, zoomBaseReadout, pointShape,
6150
6361
  symbolByStationType, symbolIconMode, symbolStrokeWidth, pointLayering, lineLayering, perPointStyle,
6151
6362
  perPointSymbol, PER_POINT_SYMBOL, applyRules, topVoltageOf, voltageWhen, showLabels, labelPosition,
6152
6363
  labelMaxCount, labelVoltageScope, LABEL_VOLTAGE_SCOPES, topPointVoltage, labelWhen, labelAvoidOverlap,
@@ -6186,7 +6397,10 @@ const members = (payload.lineData ?? payload.linePropsByMember ?? []).filter(Boo
6186
6397
  heatmapDensityStyle, heatmapDensityLayer, heatmapDensityLevels, heatmapDensityRadius, heatmapDensityThreshold,
6187
6398
  heatmapDensityCore, heatmapDensityGamma, heatmapDensityWeight, heatmapDensityOpacity, heatmapGlow,
6188
6399
  heatmapGlowStrength, heatmapGlowLine, heatmapGlowPoint, heatmapGlowBlend, heatmapOpacityMin,
6189
- 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,
6190
6404
  boundsOfData, currentBounds, PROVINCE_PALETTE, provinceHash, buildProvinceStyle, buildImagePlacement,
6191
6405
  buildBackgroundOptions, frameProvince, applyBackground, BACKGROUND_HINTS, backgroundHint, parseInitialCenter,
6192
6406
  parsePair, convertFromLngLat, convertFromScreen, buildInitialViewOptions, applyInitialViewFromControls,
@@ -8,7 +8,7 @@
8
8
  | `x` / `y` | `number` | **必填** | 世界坐标;单位由数据自己定(经纬度要先过 `createGeoMapper` / `lngLatToWorld` 搬成世界坐标) |
9
9
  | `name` / `label` | `string` | 无 | 业务字段:标签模板里 `{name}` / `{label}` 取的就是它,提示框同理 |
10
10
  | `vn_kv` 等业务字段 | 任意 | 无 | 组件不认识的字段一律原样留着:筛选 / 规则 / 标签 `when` 能按字段名取,导出业务口径时也照抄回去(含 `props.vn_kv` 这样的嵌套路径) |
11
- | `color` / `size` / `strokeColor` / `strokeWidth` | `string` / `number` | 无,回落到 `pointStyle` → `pointRules` → 内置默认 | 逐点外观,压过 `pointStyle` 与规则(优先级:状态样式 > 热力图 > 要素自身 > 规则 > `pointStyle` > 默认);`size` 是节点宽度(圆的直径 = `size × 2 / √π`) |
11
+ | `color` / `size` / `strokeColor` / `strokeWidth` | `string` / `number` | 无,回落到普通规则 → `pointStyle` → 内置默认 | 逐点外观:默认压过普通规则与 `pointStyle`,被 `override: true` 的规则压过(优先级:状态样式 > 热力图 > `override` 规则 > 要素自身 > 普通规则 > `pointStyle` > 默认);`size` 是节点宽度(圆的直径 = `size × 2 / √π`) |
12
12
  | `symbol` | `GisViewSymbolInput` | `'circle'` | 内置形状的简写 `'circle'` / `'square'`,或 `{ type: 'circle' }` / `{ type: 'square', radius }` / `{ type: 'image', src }` / `{ type: 'svg', path \| markup, viewBox, fill, stroke, strokeWidth }`,见 GisView.md 的「符号」 |
13
13
  | `opacity` | `number` | `1` | `0 ~ 1`,越小越透 |
14
14
  | `zIndex` | `number` | `0` | 绘制层级:大的盖在上面,同一个值算一层;不参与状态样式,悬停 / 选中不会把它抬层 |
@@ -96,13 +96,13 @@ view.exportData({ format: 'test', parts: ['waypoint'] })
96
96
  | 线路标记偏移 | `lines[].decorations[].position` / `.at` | `position` 按**沿线**算(比例 / 百分比 / 世界单位距离),`at` 是**世界坐标** |
97
97
  | 节点标记偏移 | `points[].decorations[].offset` / `.distance` + `.direction` / `.at` | `offset` 与 `distance` 是**屏幕像素**(缩放时保持像素距离),`at` 是**世界坐标** |
98
98
 
99
- 颜色与尺寸按「全局 → 规则 → 要素自身 → 状态(悬停 / 选中 / 框选)→ 热力图」逐层覆盖:
99
+ 颜色与尺寸从低到高按「全局 → 普通规则 → 要素自身 → `override` 规则 → 热力图 → 状态(悬停 / 选中 / 框选)」逐层覆盖;普通规则始终高于全局样式,只有单条规则写 `override: true` 才会压过要素自身字段:
100
100
 
101
101
  | 想改什么 | 写在哪 |
102
102
  | --- | --- |
103
103
  | 一类要素的默认外观 | `pointStyle` / `lineStyle` / `waypointStyle`,通道另有 `channelWidth` / `colors` / `lineWidth` |
104
- | 按业务字段批量配色 | `pointRules` / `lineRules`(条件 + 样式,见 GisView.md 的「批量样式规则」) |
105
- | 某一个要素 | 节点 `color` / `size` / `strokeColor` / `strokeWidth`、线路 `color` / `width` / `opacity`、通道 `colors[]` / `lineWidth` |
104
+ | 按业务字段批量配色 | `pointRules` / `lineRules`(条件 + 样式;普通规则只补空位,单条写 `override: true` 可压过要素字段,见 GisView.md 的「批量样式规则」) |
105
+ | 某一个要素 | 节点 `color` / `size` / `symbol` / `strokeColor` / `strokeWidth`、线路 `color` / `width` / `opacity` / `decorations`、通道 `colors[]` / `lineWidth` |
106
106
  | 悬停 / 选中 / 框选 | `pointHoverStyle` / `pointSelectStyle` / `pointMultiSelectStyle` / `pointBoxSelectStyle`、`lineHoverStyle` / `lineSelectStyle` / `lineMultiSelectStyle`,逐要素可用 `hoverStyle` / `selectStyle` / `multiSelectStyle` 覆盖 |
107
107
  | 标签 / 标记的文字与符号 | 标签 `style`(内联 CSS)+ `className`(自带类名);标记 `color` / `strokeColor` / `strokeWidth` / `opacity` / `shadow` |
108
108