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
@@ -1,6 +1,6 @@
1
1
  # 13 动效与图元
2
2
 
3
- > 热力图、动画效果、线路方向箭头、贴图预热与自定义图元。
3
+ > 热力图、图例(visualMap)、动画效果、线路方向箭头、贴图预热与自定义图元。
4
4
 
5
5
  ## 热力图
6
6
 
@@ -77,6 +77,224 @@ view.updateOptions({
77
77
 
78
78
  **性能**:取值与比例是逐要素查表(色带按 `colors` 的引用建一次、128 档),但**自动值域要扫一遍数据**(线路的值域会连通道自己的值一起扫,两者共用一把尺子),`updateOptions({ heatmap })`、换数据之后的整份重解析都会重算一次;数据量大又想省掉这次扫描就写死 `domain`。逐要素的 `heatmap` 对象按引用缓存,同一份配置反复解析只会归一化一次。
79
79
 
80
+ ## 图例(visualMap)
81
+
82
+ `options.visualMap` 是内置的热力图图例,写法参考 ECharts 的 `visualMap`:把同一条色带画成一把**带刻度的连续尺**,或者一组**分段色块**。默认它是只读的 DOM 覆盖层,不参与画布拾取;写 `interactive: true` 后由组件内置分段点选、区间拖动与键盘操作,并把当前选区同步到热力层。显式写 `target` 时选区只门控这一类;不写时展示值域取 `heatmap` 启用目标中第一个有数据的一类,选区却门控全部启用目标,字段仍必须一致。落选值由 `outOfRange` 决定是只恢复普通样式、把对应网架一起隐藏,还是完全交给 `onChange`。三种模式都不删除数据、不改筛选,也不会误伤其它字段的要素。`render(context)` 还能完全接管图例内容,在保留定位与交互上下文的前提下画业务自己的 DOM。
83
+
84
+ ```js
85
+ const view = createGisView(container, {
86
+ points,
87
+ heatmap: {
88
+ field: 'load',
89
+ density: false, // 图例只展示色带;密度场 / 光晕可选
90
+ glow: false,
91
+ colors: ['#2f6fd0', '#4cb96b', '#e0c22e', '#dd4b2e']
92
+ },
93
+ visualMap: {
94
+ type: 'ruler', // 连续尺;写 'continuous' 同义
95
+ title: '负荷',
96
+ unit: 'kW',
97
+ position: 'bottom-left',
98
+ tickCount: 5 // 两端 + 中间三个刻度
99
+ }
100
+ })
101
+ ```
102
+
103
+ ### 内置交互
104
+
105
+ 分段图例写 `interactive: true` 后,每一段都是可点击 / 可聚焦的按钮:点击、`Enter` 或空格切换选中态,`selectedMode: 'single'` 单选、`'multiple'`(缺省)多选;单选时再点已选中的那一段会清空选择。连续尺写 `interactive: true` 会同时打开 `calculable`,色带上出现两个区间手柄:拖单端改区间边界,拖区间内部整体平移,方向键 / `PageUp` / `PageDown` 每步 2%,按住 `Shift` 为 10%、`Alt` 为 0.5%,`Home` / `End` 跳到两端。键盘与指针操作都带 ARIA 读数。
106
+
107
+ ```js
108
+ view.updateOptions({
109
+ visualMap: {
110
+ type: 'piecewise',
111
+ interactive: true,
112
+ selectedMode: 'multiple',
113
+ selected: [0, 1],
114
+ pieces: [
115
+ { lt: 60, label: '低' },
116
+ { gte: 60, lt: 85, label: '中' },
117
+ { gte: 85, label: '高' }
118
+ ],
119
+ onChange: ({ selected, reason, source }) => {
120
+ // selected 是 0 起的段下标;图例选区已同步门控热力,下面只是额外的业务联动
121
+ const ranges = [[0, 60], [60, 85], [85, Infinity]]
122
+ const rangesToApply = selected.map(index => ranges[index])
123
+ console.log('图例变化', source, reason, rangesToApply)
124
+ }
125
+ }
126
+ })
127
+ ```
128
+
129
+ 连续尺的受控写法相同:把 `range: [20, 80]` 放进配置,用户拖动后从 `onChange` 的 `range` / `rangeRatios` 读结果;下一次 `updateOptions({ visualMap })` 写回新配置时只移动手柄与遮罩,不会再触发一次 `onChange`。
130
+
131
+ ### 两种形态
132
+
133
+ `ruler`(缺省,`continuous` 同义)是一条渐变尺:纵向时低温在下、高温在上,横向时从左往右递增;每个刻度显示数值,`title` 在上方,`unit` 追加在数值后面。`piecewise` 是分段色块:每段一个色块与一行范围文字,纵向时高档在上、横向时按声明顺序从左往右。
134
+
135
+ ```js
136
+ view.updateOptions({
137
+ visualMap: {
138
+ type: 'piecewise',
139
+ title: '负荷分档',
140
+ unit: 'kW',
141
+ position: 'top-right',
142
+ pieces: [
143
+ { lt: 60, label: '低' }, // < 60
144
+ { gte: 60, lt: 85, label: '中' }, // 60 ~ 85
145
+ { gte: 85, color: '#dd4b2e', label: '高' } // 最后一档不写 max,右端取值域上限
146
+ ]
147
+ }
148
+ })
149
+ ```
150
+
151
+ `pieces` 与 ECharts 的区间写法一致:`gte` / `gt` / `lte` / `lt` / `min` / `max` 都认,`label` 自定义文字、`color` 自定义这一段色块;只写一个边界时按「其余边界自动补齐」处理。不写 `pieces` 就按值域和 `tickCount` 等分成相同数量的色块。
152
+
153
+ ### 配置项
154
+
155
+ | 字段 | 类型 | 默认值 | 说明 |
156
+ | --- | --- | --- | --- |
157
+ | `enabled` | `boolean` | `true` | `false` 等于不配;与 `visualMap: null` 一样会把节点从 DOM 上摘掉 |
158
+ | `type` | `'ruler' \| 'continuous' \| 'piecewise'` | `'ruler'` | 连续尺或分段色块;`continuous` 归一化为 `ruler`,读数里的 `type` 也是 `'ruler'` |
159
+ | `target` | `'point' \| 'line' \| 'channel'` | 跟随 `heatmap` | 展示值域用哪一类要素。不写时跟随 `heatmap` 的全部启用目标,展示值域按 `point → line → channel` 取第一个真正有数值的目标(全都缺值则回落第一个启用目标);显式写只门控这一类。没有 `heatmap` 时按 `point` |
160
+ | `field` | `string` | 跟随 `heatmap`,否则 `'value'` | 取值字段。没有 `heatmap` 时会扫对应数据自动算值域 |
161
+ | `domain` | `[number, number] \| 'auto'` | 自动 | 显式值域;反向写 `[最大, 最小]` 就是颜色反向。显式给出时图例这一侧不再扫数据 |
162
+ | `min` / `max` | `number` | 无 | 只固定一端,另一端仍按自动值域补齐 |
163
+ | `colors` | `string[]` | 跟随 `heatmap.colors`,最后回落到内置色带 | 渐变色(至少两段) |
164
+ | `stops` | `number[]` | 跟随 `heatmap.stops` | 色标位置(0 ~ 1,与 `colors` 一一对应) |
165
+ | `opacity` | `number \| [number, number]` | 跟随 `heatmap.opacity`,否则 `1` | 色带透明度,写法与热力图一致 |
166
+ | `title` | `string` | `''` | 图例标题;空字符串不占标题行 |
167
+ | `unit` | `string` | `''` | 每个数值后追加的单位;自定义 `formatter` 时不会自动追加 |
168
+ | `orient` | `'vertical' \| 'horizontal'` | `'vertical'` | 尺的方向;横向时刻度在色带下方,纵向时刻度在右侧 |
169
+ | `position` | 四角名或 `{ top, right, bottom, left }` | `{ left: 12, bottom: 12 }` | 相对组件根节点定位;数字按 CSS 像素,字符串原样作为 CSS 长度(如 `'50%'`) |
170
+ | `width` / `height` | `number` | 纵向 `14 × 160`;横向 `200 × 14` | `ruler` 色带的 CSS 像素尺寸;`piecewise` 由内容撑开 |
171
+ | `tickCount` | `number` | `5` | 连续尺的刻度数,也是不写 `pieces` 时的自动分段数(2 ~ 12) |
172
+ | `precision` | `number` | 自动 | 固定小数位;不写按值域跨度自动选 0 ~ 4 位 |
173
+ | `formatter` | `string \| (value, info) => string` | 无 | 字符串支持 `{value}` / `{min}` / `{max}` / `{unit}` / `{title}` / `{field}`;函数收到数值与 `{ min, max, ratio, type, target, field, index, piece }` |
174
+ | `pieces` | `GisViewVisualMapPiece[]` | 无 | `piecewise` 的分段定义;不写按值域等分 |
175
+ | `interactive` | `boolean` | `false` | 打开内置交互:`piecewise` 点选分段,`ruler` 出现可拖动区间(同时把 `calculable` 缺省打开)。打开后根节点变为 `pointer-events: auto`;当前选区会门控图例作用目标上同 `field` 的热力上色、光晕与密度 |
176
+ | `outOfRange` | `'heatmap' \| 'network' \| 'none'` | `'heatmap'` | 落选值处理。`heatmap` 只退出热力、网架恢复普通样式;`network` 把落选节点 / 独立线路 / 通道从画布、拾取与包围盒中隐藏,不写 `target` 时三类同时生效;`none` 不做内置门控,仅通过 `onChange` 交给业务 |
177
+ | `selectedMode` | `'single' \| 'multiple' \| boolean \| null` | 交互时 `'multiple'`,否则 `false` | 分段选择方式;`true` 等同 `'multiple'`。`single` 再点已选中的段会清空,`multiple` 可多选或全部取消 |
178
+ | `selected` | `number[] \| Record<string, boolean> \| null` | 全选 | 初始选中段:0 起的下标数组,或按下标 / 标签映射的布尔对象。受控更新会替换当前选择并同步门控热力,但不会发 `onChange` |
179
+ | `range` | `[number, number] \| null` | 完整值域 | 连续尺的初始 / 受控区间(数值低值在前);受控写回只移动手柄、遮罩与图例作用目标上同 `field` 的热力门控,不回声触发 `onChange` |
180
+ | `calculable` | `boolean` | 连续尺交互时 `true` | 是否显示区间手柄;显式 `false` 可以关掉连续尺拖动,分段图例不认这一项 |
181
+ | `render` | `(context) => Node \| string \| null \| void` | 无 | 完全自定义图例内容。返回 DOM / DocumentFragment / HTML 字符串,或往 `context.body` 里追加节点后返回空;内置标题会隐藏,定位与指针开关仍沿用根节点 |
182
+ | `onChange` | `(payload) => void` | 无 | 分段点选、区间拖动 / 键盘操作以及 `context.setSelection` / `setRange` 变化后回调;图例自身已同步门控热力,这个回调用于额外业务联动。同一帧多次变化合并成一次,受控配置更新不回声 |
183
+ | `container` | `string \| HTMLElement` | 组件根节点 | 挂到外部容器;选择器找不到时回落到组件根节点。外部容器最好自带 `position: relative`,图例的绝对定位才会落在它内部 |
184
+ | `className` | `string` | `''` | 追加在根节点上的 class,适合项目自定义主题 |
185
+ | `style` | `Record<string, string \| number>` | 无 | 直接写在这层 DOM 上的内联样式;更新时旧键会被移除,也可写 `--gis-view-visual-map-*` 变量 |
186
+
187
+ ### 跟随热力图与独立值域
188
+
189
+ 不写 `target` / `field` / `domain` / `colors` 时,图例默认复用主 `heatmap` 的那一份:字段相同、自动值域同一套、色带用同一个插值函数。这样画布上的颜色与图例上的颜色不会对不上;逐要素写 `heatmap: false` 的要素仍不参与热力着色,但图例展示的是这一类要素的整体值域,不按单条覆盖拆开。
190
+
191
+ `heatmap.target` 写数组且图例不写 `target` 时,展示值域不会盲目取数组第一项:组件按 `point → line → channel` 找第一类能扫到该字段数值的目标;例如 `new.json` 的节点没有 `max_loading_rate`、线路 / 通道有,图例就直接显示线路 / 通道的值域,而不是节点的 0 ~ 1 空值域。门控范围仍覆盖 `heatmap.target` 的全部类型,不因展示目标切换而缩小。
192
+
193
+ 显式写 `target` 时,当前分段 / 区间只参与这一类的运行时门控;不写 `target` 时覆盖 `heatmap` 的全部启用目标,但与图例 `field` 不一致的字段仍不受影响。独立图例仍按自己的字段展示。
194
+
195
+ `outOfRange: 'network'` 把落选范围扩大到网架本身,但只改显示,不改数据与业务状态:`target: 'point'` 隐藏落选节点;`target: 'line'` 隐藏落选独立线路,属于通道的回线由通道自己决定,不会被线路字段误伤;`target: 'channel'` 按通道声明实时收起落选通道的全部成员回线。不写 `target` 时三路同时处理:落选节点、落选独立线路与落选通道的整组回线一起隐藏。缺值或非数字不会被当作落选值隐藏。被隐藏的选中项仍留在选中集里,恢复选区后原高亮回来;`getData()`、`getFilterResult()`、`getVisibleData()`(仅筛选口径)与 `getChannels()` / `getChannel()` 都保持原样。显式 `minimap.heatmap` 的小图即使独立取色,也会同步这套网架隐藏;`outOfRange: 'none'` 则连热力也不门控,适合完全由业务自己隐藏 / 过滤。
196
+
197
+ 也可以让图例完全独立于热力图。下面的写法没有配 `heatmap`,图例按 `score` 字段自己扫当前节点,显示 0 ~ 100 的独立色带,并挂到页面上的 `#score-legend`:
198
+
199
+ ```js
200
+ view.updateOptions({
201
+ visualMap: {
202
+ type: 'continuous',
203
+ field: 'score',
204
+ domain: [0, 100],
205
+ colors: ['#2f6fd0', '#f5f7fa', '#dd4b2e'],
206
+ orient: 'horizontal',
207
+ container: '#score-legend',
208
+ title: '评分',
209
+ formatter: '{value}{unit}',
210
+ unit: ' 分'
211
+ }
212
+ })
213
+ ```
214
+
215
+ - `target: 'line'` 会按线路自己的字段取域,并像热力图一样把通道自己的值一起纳入范围;通道回线没有自己的值时,仍用通道的值。
216
+ - 只改图例时(`updateOptions({ visualMap })`,可顺带换回调)走独立快路径:引擎场景、鹰眼图与标签层都不重排。`heatmap` 配置变化时图例会重新读取字段 / 色带;数据变化(`updateData`、`updatePoint`、拖拽、撤销等)只记脏,下一帧再按新数据重算。
217
+ - 图例没配置时零 DOM;`updateOptions({ visualMap: null })` 或 `enabled: false` 会立即摘掉节点。外部容器里的根节点同样会被移除,不会留下空壳。
218
+ - 同一份渲染签名重复下发(对象引用变了、字段没变)会合到同一帧并跳过 DOM 写入;连续拖滑杆时只保留每帧一次重画。
219
+ - 自动值域需要扫一遍对应数据。数据量大且值域固定时,显式写 `domain`(或者给 `heatmap.domain` 让图例继承)可以省掉图例这一侧的扫描;两端都给出 `min` / `max` 时同样不扫描,只有只固定一端时才需要扫另一端。
220
+ - 图例是 CSS 覆盖层,位置与主题可以用 `className` / `style` 或下面这组变量整体替换:`--gis-view-visual-map-background` / `-border` / `-radius` / `-shadow` / `-color`、`--gis-view-visual-map-label-color` / `-font-size`、`--gis-view-visual-map-tick`、`--gis-view-visual-map-ruler-border`、`--gis-view-visual-map-swatch-border`,以及交互态的 `--gis-view-visual-map-piece-hover` / `-piece-radius` / `-unselected-opacity`、`--gis-view-visual-map-range-border` / `-range-fill` / `-range-shadow`、`--gis-view-visual-map-handle` / `-handle-border` / `-handle-radius` / `-focus`。
221
+
222
+ ### `onChange` 载荷与数据联动
223
+
224
+ ```js
225
+ onChange: (payload) => {
226
+ // payload = {
227
+ // type, target, field, domain, interactive, selectedMode, calculable, outOfRange,
228
+ // selected, range, rangeRatios, element,
229
+ // source: 'user' | 'api', reason: 'piece' | 'range' | 'api', originalEvent
230
+ // }
231
+ }
232
+ ```
233
+
234
+ - 指针点选 / 拖动与键盘操作发 `source: 'user'`;`context.setSelection` / `setRange`、`clearSelection` / `emitChange` 发 `source: 'api'`。`reason` 区分分段选择、区间变化与主动通知。
235
+ - 同一帧里连续发生的多次变化会合并为一次回调,读到的是这一帧最终状态;`originalEvent` 是触发它的指针 / 键盘事件(受控更新与主动 API 调用可能是 `null`)。
236
+ - 图例选区会直接门控图例作用目标上同 `field` 的热力上色、光晕与密度场:显式 `target` 只作用该类,不写则覆盖 `heatmap` 的全部启用目标。缺省 `outOfRange: 'heatmap'` 让落选值退出热力、恢复普通样式;`'network'` 进一步隐藏对应网架;`'none'` 不做内置效果,只发 `onChange`。数据不删除、不触发筛选,也不影响其它字段的要素。默认继承主图热力的鹰眼图使用同一选区,显式写了 `minimap.heatmap` 的小图仍按自己的配置独立取色,但 `network` 隐藏仍会同步。`onChange` 用来在门控之外做业务联动(例如同步业务表或自己的状态);把结果写回 `visualMap.selected` / `range` 属于受控用法,这次写回不会再触发 `onChange`。
237
+ - `selected` 是 0 起的段下标;`range` 按数值大小返回 `[低, 高]`,`rangeRatios` 是同一区间在色带上的显示比例(反向值域时仍按左 / 下到右 / 上排序)。
238
+
239
+ ### 完全自定义:`render(context)`
240
+
241
+ `render` 返回 DOM 节点、`DocumentFragment` 或 HTML 字符串即可替换整个内置正文;也可以不返回,直接往 `context.body` 追加节点。内置标题自动隐藏,外层定位、主题变量与 `interactive` 控制的 `pointer-events` 仍由图例根节点负责。
242
+
243
+ ```js
244
+ view.updateOptions({
245
+ visualMap: {
246
+ type: 'piecewise',
247
+ interactive: true,
248
+ selectedMode: 'multiple',
249
+ pieces: [
250
+ { lt: 60, label: '低' },
251
+ { gte: 60, lt: 85, label: '中' },
252
+ { gte: 85, label: '高' }
253
+ ],
254
+ render: ({ body, pieces, selected, setSelection }) => {
255
+ const wrap = document.createElement('div')
256
+ const active = new Set(selected)
257
+ for (const [index, piece] of pieces.entries()) {
258
+ const button = document.createElement('button')
259
+ button.type = 'button'
260
+ button.textContent = piece.label
261
+ button.style.background = piece.color
262
+ button.addEventListener('click', () => {
263
+ if (active.has(index)) active.delete(index)
264
+ else active.add(index)
265
+ setSelection([...active]) // 触发 onChange,source = 'api'
266
+ })
267
+ wrap.append(button)
268
+ }
269
+ body.replaceChildren(wrap)
270
+ },
271
+ onChange: ({ selected }) => syncLegendFilter(selected)
272
+ }
273
+ })
274
+ ```
275
+
276
+ 自定义上下文里的可用字段与入口:
277
+
278
+ - 状态快照:`type` / `target` / `field` / `domain` / `colors` / `stops` / `opacity` / `orient` / `title` / `unit` / `ticks` / `pieces` / `selectedMode` / `calculable` / `selected` / `range` / `rangeRatios`。
279
+ - DOM:`root` / `body` / `titleElement`。
280
+ - 数值:`valueAt(ratio)` 把 0 ~ 1 的色带位置换成值,`formatValue(value)` 沿用图例的 `precision` / `formatter` / `unit`。
281
+ - 交互:`setSelection(indexesOrMap)`、`setRange([low, high])`、`clearSelection()`(分段清空选择 / 连续尺恢复完整区间)、`emitChange(reason?)`;前三个返回“是否真的发生变化”。
282
+ - `render` 在类型、值域、色带、分段、标题等渲染签名变化时重新调用,不会每次点选都重建你的 DOM。自定义内容应在自己的事件处理里更新按钮态;需要和外部面板同步就听 `onChange`,不要把每次选择都当成一次新的 `render`。
283
+
284
+ ### 读数与外部扩展
285
+
286
+ ```js
287
+ const element = view.getVisualMapElement() // 没配 visualMap 时是 null
288
+ const readout = view.getVisualMap()
289
+ // {
290
+ // type: 'ruler' | 'piecewise', target, field, domain, colors, orient, title, unit,
291
+ // interactive, selectedMode, calculable, selected, range, rangeRatios,
292
+ // ticks: [{ value, ratio, label }], pieces: [{ low, high, label, color, ratio }]
293
+ // }
294
+ ```
295
+
296
+ `getVisualMap()` 返回当前读数的一份独立副本,每次调用会先把挂起的重画同步落下来,所以数据更新后可以立刻读到新值域与选择。`getVisualMapElement()` 用于挂自己的外观或额外监听;默认根节点 `pointer-events: none`,配置 `interactive: true` 后自动恢复,内置分段按钮与区间手柄已经可用,不必再手写一套。若要接业务筛选,优先用 `onChange`,而不是从 DOM 猜当前选择。
297
+
80
298
  ## 动画效果(effects)
81
299
 
82
300
  线路上的图标跑动(ECharts 的 `lines.effect` 那一类)与节点上的扩散涟漪(`effectScatter` 那一类)。**配置本身就是开关**:没配就是关的;
@@ -608,7 +608,7 @@ createGisView(container, {
608
608
  | `lineStyle` | `{ color?, width?, opacity? }` | `#8fb2d8` / `1` / `0.55` | 线路的颜色、线宽与透明度,`opacity: 0` 就是不画线 |
609
609
  | `pointRules` | `GisViewMinimapPointRule[]` | 无 | 小图自己的节点规则:按属性给每个节点定颜色 / 半径 / 形状(`'circle'` / `'square'`)/ 层级,条件语言与主图的 `pointRules` 完全一样;规则里没写的字段回落到 `pointStyle` |
610
610
  | `lineRules` | `GisViewMinimapLineRule[]` | 无 | 小图自己的线路规则:颜色 / 线宽 / 透明度 / 层级,规则里没写的字段回落到 `lineStyle` |
611
- | `heatmap` | `GisViewHeatmap \| false` | 沿用主图 | 小图自己的热力图:不写就沿用主图的 `options.heatmap`(同一份色带与自动值域),写对象就单独换一份,`false` 表示小图不按热力着色(主图照旧),见「热力图」 |
611
+ | `heatmap` | `GisViewHeatmap \| false` | 沿用主图 | 小图自己的热力图:不写就沿用主图的 `options.heatmap`(同一份色带、自动值域与 `visualMap` 选区),写对象就单独换一份且不跟随主图图例,`false` 表示小图不按热力着色(主图照旧)。`visualMap.outOfRange: 'network'` 的网架隐藏不受这一项影响,显式独立热力配置也会同步,见「热力图」 |
612
612
  | `inheritRules` | `boolean` | `true` | 没配自己的规则时是否沿用主图样式:节点取主图规则的颜色 / 描边色 / 层级,并沿主图最终解析出的尺寸与图标(`override` 规则 > 点自身 > 普通规则 > `pointStyle`,圆、方、图片、SVG 与自定义图元都认);沿用尺寸 / 线宽时还会乘一次**小图与主图 CSS 视口宽高的较小比例**,只跟两边视口尺寸有关,不随主图相机倍数变化,小图比主图小时节点与线路不会仍顶着和主图一样的尺寸。线路沿用颜色 / 透明度与样式链线宽。显式 `false` 表示小图只用自己那份 `pointStyle` / `lineStyle` |
613
613
  | `levels` | `number[]` | 无 | 只画这些层级的节点:层级取规则的 `zIndex`(没写算第 0 层),不配表示全部画;「每一层显示什么级别」就是它 —— 按电压写了 `zIndex` 之后,只看 500kV 以上就写 `levels: [500, 1000]` |
614
614
  | `cascade` | `false \| 'binding' \| 'coordinate' \| 'both'` | `'binding'` | 被 `levels` 挡掉的节点,连在它们身上的线路怎么跟着隐藏:`binding` 按 `from` / `to` 绑定、`coordinate` 按端点坐标(端点正好落在被挡掉的节点上)、`both` 两者都算、`false` 不联动。与主视图筛选的 `cascade` 是同一套说法 —— 主视图筛掉的要素本来就带联动,这里管的是小图**自己**挡掉的那部分;只看某几层时不会留下一堆凭空断掉的线 |
@@ -677,6 +677,8 @@ console.log(minimapElement?.parentElement === minimapHost) // true
677
677
 
678
678
  交互与开销:**在框上按下直接拖框**(世界位移 = 屏幕位移 ÷ 小图比例),**空白处单击不改变视角**;未手动缩放且 `dragAction` 为 `pan` 时,按住超过 `longPressDelay` 才进入「跟着拖」;`dragAction: 'box'` 或手动缩放时,空白处按住即按对应动作开始,拖动过程中始终跟着光标;光标停在小图上滚动滚轮按光标位置缩放主图(`wheelZoom: false` 关掉)。每个有位移的手势结束时可通过 `onInteraction` 收到一条通知,不必自己在小图 DOM 上再挂一套指针监听。底图画的是筛选之后真正画出来的线,通道骨架与回线都在内(直接问引擎要几何,不用自己再推一遍通道偏移),画法是先铺线、再把节点整层盖上去 —— 节点层级高于线路,密集底图上的节点不会被线咬掉。底图只在数据或筛选结果变化时重画(节点直接写进像素缓冲,十万级也只在重建时花一次时间),相机变化只改视口框的 `transform`,所以拖动小图不掉帧。小图还会缓存**上一次的节点像素结果**与**数据范围**:一次改动排在前后两帧各重画一次(引擎重排之后要再取一次几何),第二次与第一次的数据、范围、尺寸完全一样时直接复用那份像素,十万点下第二次重画从 ~20ms 降到 0.3ms(配了 `pointMapZoomScale` / `lineMapZoomScale`(或存在沿用主图线宽的线路)时把「主视图倍数 / 两边视口比例」也并进缓存的键,倍数变了才重铺);打开 `extentFollowCamera` 后 `extent: auto` 的取舍才跟相机有关,相机挪进 / 挪出那份小范围时才补一次重画(每帧只做几次比较,判断用的范围是缓存的)。小图拖框 / 小图滚轮带来的相机变化不算主画布上的交互:配了 `hideOnInteract` 的图层不会在这时被收起来,拖动结束还会补一次真实排版 —— 不会出现「动一下鹰眼图,主视图标签全没了」。真在主画布上拖动过之后、两层标签同时收着时,收尾计时器也照样挂上 —— 覆盖层停手的收尾是无条件的,不会因为「两层都在收着、当下没东西可排」把恢复一起跳过。
679
679
 
680
+ `visualMap.outOfRange: 'network'` 的隐藏也作用于鹰眼图:继承主图热力时,图例选区与网架隐藏都按主图状态走;显式写了 `minimap.heatmap` 时小图的**取色**仍独立,但落选节点与线路是否隐藏仍同步主图。小图只做显示门控,不改数据、筛选结果与通道声明。
681
+
680
682
  线路层另有自己的离屏 canvas 与缓存:缓存键包含画布尺寸、范围、投影、小图倍率与线路样式,命中时整层直接合成,不再重复投影与描边;`collectMinimapData()` 复制路径时顺路算出每条线的包围盒,完全落在小图视野外的线不建路径。只有布局尺寸实际变化时才重设 canvas 尺寸,单纯补画或移动视口框不会清空这份缓存。
681
683
 
682
684
  `zoomable` 有三种互斥的模式:`false`(默认)只看固定范围,滚轮缩放主图;`true` 手动缩放小图自身(滚轮以光标为锚点,Ctrl / ⌘ + 滚轮仍留给主图);`'follow'` 让整个小图的显示范围跟随主图倍数变化(滚轮仍直接缩放主图,`followZoomScale` 是主图相对基准倍数的指数,`0` 表示不跟随、`1` 同倍率、`0.5` 是主图放大 4 倍时小图放大 2 倍;`zoom` 在这里是基准倍数,结果仍夹在 `minZoom` ~ `maxZoom`)。跟随模式下 `setMinimapZoom` / `zoomMinimapBy` 不抢控制权,倍数始终由主图决定。
@@ -370,7 +370,7 @@ const focusSelectionPlugin = (view, context) => {
370
370
  const view = createGisView(container, { points, lines, plugins: [focusSelectionPlugin] })
371
371
  ```
372
372
 
373
- - 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(图例、比例尺、右键菜单、URL 同步都属于这一类)。
373
+ - 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(比例尺、右键菜单、URL 同步都属于这一类)。热力图图例已由 `options.visualMap` 内置,分段点选、区间拖动与 `render(context)` 完全自定义也在同一配置里;选区会直接门控同 `target` / `field` 的热力上色、光晕与密度,默认继承主图热力的鹰眼图也同步,额外业务联动再听 `onChange`。只有组件之外的独立面板 / 手势才需要写插件。
374
374
  - 第二个参数是 `{ apiVersion: 1, view, signal, onCleanup, on, registerCommand }`,对象已冻结。`view` 与第一个参数相同,`apiVersion` 用来判断插件写法是否兼容;后续只做加字段的向后兼容扩展。
375
375
  - `signal` 是这个插件自己的 `AbortSignal`,卸载或实例销毁时先中止;`fetch` / 定时任务 / DOM 监听可以直接接它(`addEventListener(..., { signal })`)。
376
376
  - `onCleanup(fn)` 登记一个自动清理函数,卸载时按登记的倒序执行;返回的函数用于提前执行并摘掉这一项,重复调用只执行一次。
@@ -433,7 +433,7 @@ dispose() // 幂等注销;同名已被后来
433
433
 
434
434
  | 扩展点 | 适合做什么 | 作用域 | 清理方式 |
435
435
  | --- | --- | --- | --- |
436
- | `plugins` / `view.use(plugin)` | 视图级行为:快捷键、URL 同步、图例、业务面板,组合已有实例 API | 单个视图实例 | `options.plugins` 随 `view.destroy()` 按相反顺序清理;`view.use()` 返回幂等卸载函数。`context.signal` / `onCleanup` / `on` / `registerCommand` 随卸载自动回收,旧 `destroy` 仍兼容 |
436
+ | `plugins` / `view.use(plugin)` | 视图级行为:快捷键、URL 同步、业务面板与组件之外的自定义图例,组合已有实例 API;热力色带、分段点选、区间拖动与自定义渲染优先用 `options.visualMap` | 单个视图实例 | `options.plugins` 随 `view.destroy()` 按相反顺序清理;`view.use()` 返回幂等卸载函数。`context.signal` / `onCleanup` / `on` / `registerCommand` 随卸载自动回收,旧 `destroy` 仍兼容 |
437
437
  | `view.registerSymbol(name, painter)` | 决定「点怎么画」:品牌图标、告警形状、状态符号等 | 模块级符号表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;不会随 `view.destroy()` 自动注销 |
438
438
  | `view.registerOperator(name, test)` | 给规则 / 筛选 / 标签增加条件动词,例如 `mod2`、`evenIndex` | 模块级条件运算符表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;注销后旧条件按「不成立」处理 |
439
439
  | `view.addWebGLLayer(layer)` | 直接拿组件的 WebGL2 上下文画自定义标记、特效或调试几何;层画在内置内容之后,不参与拾取与数据导出 | 单个视图实例 | 返回卸载函数;也可用 `updateWebGLLayer` / `removeWebGLLayer` / `clearWebGLLayers` 维护,画布外还可通过 `view.gl` 直接取上下文 |
@@ -52,6 +52,7 @@
52
52
  | `getBounds()` | 数据范围(没有点时回退到背景 GeoJSON 的 `rawBounds`,也就是 `aspectScale` 之前的真实经纬度范围);每次返回独立副本,重复调用命中缓存 |
53
53
  | `getViewRect()` | 当前可视范围的世界坐标矩形;自建缩略图 / 坐标读数用它换算视口框,内置鹰眼图见「鹰眼图」 |
54
54
  | `getMinimapElement()` | 鹰眼图根节点(没配 `minimap` 时为 `null`):接整图拖动或自定义手势;配合 `minimap.container` 挂到外部宿主时拿到的仍是同一根节点 |
55
+ | `getVisualMapElement()` / `getVisualMap()` | 内置图例的根节点 / 当前读数:根节点在没配 `visualMap` 或 `enabled: false` 时为 `null`;读数含 `type` / `target` / `field` / `domain` / `colors`、交互态 `interactive` / `selectedMode` / `calculable` / `selected` / `range` / `rangeRatios` 与 `ticks` / `pieces`,没配时为 `null`。分段点选 / 区间拖动走 `visualMap.onChange`,`render(context)` 可完全自定义;只改图例不会触发引擎场景或标签重排,见「图例(visualMap)」 |
55
56
  | `getMinimapZoom()` / `setMinimapZoom(zoom)` / `zoomMinimapBy(factor, origin?)` | 读 / 写鹰眼图自身的倍数:`set` 夹在 `minimap.minZoom` ~ `maxZoom` 之间,`zoomMinimapBy` 以画布 CSS 像素 `origin` 为锚点缩放(缺省小图中心),没配 `minimap` 时是空操作 |
56
57
  | `getPoint(ref)` | 按 id / 下标 / `{ id }` / `{ index }` 取一个节点:拾取载荷(`item` / `x` / `y` / `layer` / `layerRank`)之外再给一份屏幕半径 `radius`;找不到返回 `null`。自建读数面板、右键菜单用它,省掉「自己遍历 `getData()` + 算投影」那一段 |
57
58
  | `getPointStyle(index)` / `getLineStyle(index)` | 节点 / 线路解析之后的样式快照:节点的 `size` / `symbol` 取样式链(`override` 规则 > 自身 > 普通规则 > 全局),线路的 `width` / `color` / `opacity` / `zIndex` 同源;不含悬停 / 选中样式、整体倍率与热力倍率,下标不存在时返回 `null`。合并节点与鹰眼图沿用主图尺寸时走这一份 |
@@ -213,7 +214,7 @@
213
214
 
214
215
  返回 `this` 的方法都可以链式调用;返回其它值的方法(`get*` / `list*` / `export*` / `search` / `pick*` / `add*` / `remove*` / `copy*` / `merge*` / `locate*` 这些)在下面的「参数速查」里都标了返回类型。
215
216
 
216
- **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 281 个:276 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 105 个键,数据字段表列全 273 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
217
+ **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 283 个:278 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 107 个键,数据字段表列全 275 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
217
218
 
218
219
  - `node docs/steps/gen-api-index.mjs`:从 `create.js` 的实例定义、本文档的两张表与 `types.d.ts` 生成 `docs/demos/api-index.js`(`API_SUMMARY` / `API_SECTIONS` / `API_OPTIONS`),demo 的「实例 API」面板读的就是它;带 `--check` 时只校验有没有过期。
219
220
  - `node docs/steps/check-doc-coverage.mjs`:10 项双向对账 —— 实例成员 × 文档 / × 参数速查 / × 类型、参数速查与实例方法主表各自反查过期条目、类型反查过期声明、数据字段 × 文档、options 键 × 文档 / options 表反查过期字段、demo 清单是否最新。加一个实例成员、改一个 options 键而没同步文档时,这个脚本会直接报出来。
@@ -1,6 +1,6 @@
1
1
  # 17 参数速查(逐个方法)
2
2
 
3
- > 281 个成员逐个列全:参数、默认值与返回。
3
+ > 283 个成员逐个列全:参数、默认值与返回。
4
4
 
5
5
  ### 参数速查(逐个方法)
6
6
 
@@ -83,6 +83,8 @@
83
83
  | `getViewRect()` | 无 | `GisViewBounds` |
84
84
  | `isLayoutReady()` | 无 | `boolean` |
85
85
  | `getMinimapElement()` | 无 | `HTMLElement \| null` 鹰眼图根节点(没配 `minimap` 时为 `null`);外部整图拖动 / 自定义手势用它,配合 `minimap.container` 可挂到外部宿主 |
86
+ | `getVisualMapElement()` | 无 | `HTMLElement \| null` 内置图例根节点(没配 `visualMap` 或 `enabled: false` 时为 `null`);挂外部事件或自定义样式从这里拿。`interactive: true` 时根节点已可直接点选 / 拖动,内置交互优先用 `onChange` |
87
+ | `getVisualMap()` | 无 | `GisViewVisualMapReadout \| null` 当前图例读数:`type` / `target` / `field` / `domain` / `colors` / `orient` / `title` / `unit`、交互态 `interactive` / `selectedMode` / `calculable` / `selected` / `range` / `rangeRatios` 与 `ticks` / `pieces`;没配时为 `null`,调用前会先落下一帧挂起的重画 |
86
88
  | `getMinimapZoom()` | 无 | `number` |
87
89
  | `setMinimapZoom(zoom)` | `zoom: number` 鹰眼图的目标倍数(被 `minimap.minZoom` / `maxZoom` 夹住) | `this` |
88
90
  | `zoomMinimapBy(factor, origin?)` | `factor: number` 倍数;`origin?: [x, y]` 以小图里的哪个点为中心(缺省小图中心) | `this` |
@@ -290,6 +290,14 @@ const layoutReady = view.isLayoutReady()
290
290
  // getMinimapElement():拿鹰眼图根节点做整图拖动 / 自定义手势(没配 minimap 时为 null)
291
291
  const minimapElement = view.getMinimapElement()
292
292
 
293
+ // getVisualMapElement() / getVisualMap():内置图例的根节点与当前读数(没配 visualMap 时为 null)
294
+ const visualMapElement = view.getVisualMapElement()
295
+ const visualMap = view.getVisualMap()
296
+ // { type, target, field, domain, colors, interactive, selectedMode, calculable,
297
+ // selected, range, rangeRatios, ticks / pieces, ... }
298
+ // 分段点选 / 区间拖动在配置里打开 interactive,并通过 visualMap.onChange 接收结果;
299
+ // 完全自定义用 render(context),不必从 DOM 反推当前选择
300
+
293
301
  // getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放
294
302
  view.setMinimapZoom(4)
295
303
  view.zoomMinimapBy(1.25)
@@ -3382,8 +3382,8 @@
3382
3382
  await wait(150)
3383
3383
  const apiItems = () => [...document.querySelectorAll('.demo-api-list:not(.demo-api-list--options) .demo-api-item')]
3384
3384
  const optionItems = () => [...document.querySelectorAll('.demo-api-list--options .demo-api-item')]
3385
- check('实例方法速查:整份成员清单按分组列全(16 组 / 281 个成员)',
3386
- Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 281,
3385
+ check('实例方法速查:整份成员清单按分组列全(16 组 / 283 个成员)',
3386
+ Boolean(ready) && document.querySelectorAll('.demo-api-group').length === 16 && apiItems().length === 283,
3387
3387
  `${document.querySelectorAll('.demo-api-group').length} 组 / ${apiItems().length} 个成员`)
3388
3388
  const apiNames = apiItems().map(node => node.querySelector('b')?.textContent)
3389
3389
  const roundNames = ['registerSymbol', 'unregisterSymbol', 'listSymbols']
@@ -3402,7 +3402,7 @@
3402
3402
  check('实例方法速查:这一轮拆出来的方法也在清单里(addChannelGroupLines / joinChannelGroupLines / endAddChannel)',
3403
3403
  roundNames1597.every(name => apiNames.includes(name)),
3404
3404
  roundNames1597.filter(name => !apiNames.includes(name)).join(' / ') || '三个都在')
3405
- check('实例方法速查:options 折叠清单列全 105 个键', optionItems().length === 105, `${optionItems().length} 个键`)
3405
+ check('实例方法速查:options 折叠清单列全 107 个键', optionItems().length === 107, `${optionItems().length} 个键`)
3406
3406
  const apiSearch = document.querySelector('.demo-api-search input[type="search"]')
3407
3407
  apiSearch.value = 'focusOn'
3408
3408
  apiSearch.dispatchEvent(new Event('input', { bubbles: true }))
@@ -535,8 +535,8 @@
535
535
  if (check('API 速查:挂上了 __gisApi 钩子(数据来自 api-index.js,没钩子多半是 file:// 打开的)', Boolean(apiHook))) {
536
536
  const apiDoc = apiFrame.contentDocument
537
537
  const summary = apiHook.summary
538
- check('API 速查:清单来自 api-index(281 个成员 / 276 个方法 / 5 个属性 / 105 个 options / 16 个分组)',
539
- summary.members === 281 && summary.methods === 276 && summary.properties === 5 && summary.options === 105 && summary.sections === 16,
538
+ check('API 速查:清单来自 api-index(283 个成员 / 278 个方法 / 5 个属性 / 107 个 options / 16 个分组)',
539
+ summary.members === 283 && summary.methods === 278 && summary.properties === 5 && summary.options === 107 && summary.sections === 16,
540
540
  JSON.stringify(summary))
541
541
  const topJumps = [...apiDoc.querySelectorAll('.doc-jump')]
542
542
  const jumpTargets = topJumps.map(node => `${node.dataset.docJump}:${node.getAttribute('href')}`)
@@ -574,8 +574,8 @@
574
574
  `${apiDoc.querySelectorAll('.api-member').length} 张卡片 / 属性 ${apiDoc.querySelectorAll('.api-member[data-kind="property"]').length} 张`)
575
575
  const noExample = [...apiDoc.querySelectorAll('.api-member')].filter(node => Number(node.dataset.examples) < 1)
576
576
  check('API 速查:每个成员都带至少一段示例(不出现空卡片)', noExample.length === 0,
577
- noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '281 张卡片都有示例')
578
- check('API 速查:105 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
577
+ noExample.length ? noExample.map(node => node.dataset.name).join(' / ') : '283 张卡片都有示例')
578
+ check('API 速查:107 个 options 都列全(名字 / 类型 / 默认值 / 说明四件套)',
579
579
  apiDoc.querySelectorAll('.api-option').length === summary.options && [...apiDoc.querySelectorAll('.api-option')].every(node => node.querySelector('.name code') && node.querySelector('.type') && node.querySelector('.def') && node.querySelector('.doc')),
580
580
  `${apiDoc.querySelectorAll('.api-option').length} 个`)
581
581
  const statCards = [...apiDoc.querySelectorAll('#stats .api-stat')]