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
@@ -344,11 +344,11 @@ disposeBackendDto() // true:只在当前仍是自己的 adapter
344
344
 
345
345
  ## 插件
346
346
 
347
- `plugins` 是给「跨项目复用的视图级逻辑」准备的最小挂载点:每个插件是一个 `(view, context) => hook` 的函数,视图创建完成(数据、覆盖层、鹰眼图都就绪)之后按数组顺序执行一次;返回值里如果有 `destroy` 函数,会在实例 `destroy()` 时按相反顺序回调:
347
+ `plugins` 是给「跨项目复用的视图级逻辑」准备的最小挂载点:每个插件是一个 `(view, context) => hook` 的函数,视图创建完成(数据、覆盖层、鹰眼图都就绪)之后按数组顺序执行一次。插件成功初始化后进入实例生命周期;第二个参数的生命周期方法登记的事件、命令与清理函数会自动收回,旧写法返回的 `destroy` 仍然兼容:
348
348
 
349
349
  ```js
350
350
  // 按 F 聚焦当前选中要素:不改组件,只组合已有 API,换项目可以直接复用
351
- const focusSelectionPlugin = view => {
351
+ const focusSelectionPlugin = (view, context) => {
352
352
  const onKeydown = event => {
353
353
  if (event.key !== 'f' && event.key !== 'F') {
354
354
  return
@@ -358,33 +358,46 @@ const focusSelectionPlugin = view => {
358
358
  view.focusOn(selection, { zoom: true, padding: 80 })
359
359
  }
360
360
  }
361
- window.addEventListener('keydown', onKeydown)
362
- return { destroy: () => window.removeEventListener('keydown', onKeydown) }
361
+ // signal 中止时浏览器自动移除这条 DOM 监听,不需要再写 destroy
362
+ window.addEventListener('keydown', onKeydown, { signal: context.signal })
363
+ context.on('selectChange', payload => console.log('选中变了:', payload.count))
364
+ context.registerCommand('focusSelection', () => {
365
+ const selection = view.getSelections()
366
+ return view.focusOn(selection, { zoom: true, padding: 80 })
367
+ })
363
368
  }
364
369
 
365
370
  const view = createGisView(container, { points, lines, plugins: [focusSelectionPlugin] })
366
371
  ```
367
372
 
368
- - 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(图例、比例尺、右键菜单、URL 同步都属于这一类)。
369
- - 第二个参数是 `{ apiVersion: 1, view }`:`view` 与第一个参数相同,`apiVersion` 用来判断插件写法是否兼容;当前只做加字段的向后兼容扩展,旧的 `view => hook` 写法和新写法可以同时使用。
370
- - 按数组顺序执行,后一个能看见前一个留下的状态;返回 `{ destroy }` 才会被回收,只做一次性初始化可以不返回。
371
- - 单个插件初始化 / 清理抛错不会打断其它插件,错误走 `onError` 回报;实例 `destroy()` 之后插件列表清空。
373
+ - 插件拿到的就是 `createGisView` 返回的实例,可以用全部实例方法,也能用 `el` / `canvas` / `gl` 自己挂 DOM(比例尺、右键菜单、URL 同步都属于这一类)。热力图图例已由 `options.visualMap` 内置,分段点选、区间拖动与 `render(context)` 完全自定义也在同一配置里;选区会直接门控同 `target` / `field` 的热力上色、光晕与密度,默认继承主图热力的鹰眼图也同步,额外业务联动再听 `onChange`。只有组件之外的独立面板 / 手势才需要写插件。
374
+ - 第二个参数是 `{ apiVersion: 1, view, signal, onCleanup, on, registerCommand }`,对象已冻结。`view` 与第一个参数相同,`apiVersion` 用来判断插件写法是否兼容;后续只做加字段的向后兼容扩展。
375
+ - `signal` 是这个插件自己的 `AbortSignal`,卸载或实例销毁时先中止;`fetch` / 定时任务 / DOM 监听可以直接接它(`addEventListener(..., { signal })`)。
376
+ - `onCleanup(fn)` 登记一个自动清理函数,卸载时按登记的倒序执行;返回的函数用于提前执行并摘掉这一项,重复调用只执行一次。
377
+ - `on(name, handler)` 等价于 `view.on()` 加自动退订;`registerCommand(name, handler)` 等价于 `view.registerCommand()` 加自动注销。两者返回的也是幂等取消函数。
378
+ - 成功初始化就进入视图生命周期:插件可以先把 `context` 存起来,之后再按用户操作登记监听或清理项,实例 `destroy()` 时仍会回收。
379
+ - 单个插件初始化 / 清理抛错不会打断其它插件,错误走 `onError` 回报;初始化到一半抛错时,已经登记的 signal、事件、命令与清理函数会先收回。
380
+ - 旧写法返回的 `{ destroy }` 仍会在插件卸载时调用:先中止 `signal`,再跑 `destroy`,最后按倒序跑 `context` 自动登记的清理;实例 `destroy()` 之后插件列表清空。
381
+ - 按数组顺序执行,后一个能看见前一个留下的状态;只做一次性初始化可以不返回值,但只要初始化成功,生命周期仍会被实例持有。
372
382
  - 改配置请走 `updateOptions()`,不要在插件里直接改引擎内部对象 —— 组件只承诺公开 API 的兼容性。
373
383
 
374
- 创建之后才决定要挂的功能走 `view.use(plugin)`:写法与 `plugins` 里的一项完全一致(同样收到 `(view, context)`),调用即执行,返回一个卸载函数 —— 调它就把这个插件自己的 `destroy` 清掉(重复调用只清一次,实例销毁之后再调也安全);插件初始化抛错同样只走 `onError`:
384
+ 创建之后才决定要挂的功能走 `view.use(plugin)`:写法与 `plugins` 里的一项完全一致(同样收到 `(view, context)`),调用即执行,返回一个幂等卸载函数 —— 调它中止 `signal`、跑旧写法的 `destroy`,再执行 `context` 自动登记的清理(实例销毁之后再调也安全);插件初始化抛错同样只走 `onError`:
375
385
 
376
386
  ```js
377
- // 懒加载的功能接进来:面板打开时才挂「选中变化计数」,面板关掉就卸掉
378
- const dispose = view.use(instance => {
379
- const off = instance.on('selectChange', payload => console.log('选中变了:', payload.count))
380
- return { destroy: () => off() } // 与 options.plugins 一样在 destroy() 时回调
387
+ // 懒加载的功能接进来:面板打开时才挂监听与命令,面板关掉就全部自动收回
388
+ const dispose = view.use((instance, context) => {
389
+ context.on('selectChange', payload => console.log('选中变了:', payload.count))
390
+ context.registerCommand('toggleInspector', () => {
391
+ instance.updateOptions({ tooltip: false })
392
+ })
393
+ // 不需要 return { destroy };只有非 context 管理的资源才需要旧写法自己清理
381
394
  })
382
395
 
383
- dispose() // 卸载:跑上面那个 destroy,从实例的清理列表里摘掉
396
+ dispose() // 卸载:中止 signal、退订事件、注销命令并执行 onCleanup
384
397
  ```
385
398
 
386
- - `options.plugins` 适合「创建时就该有」的插件,`use()` 适合按需加载 / 用户操作之后才接上的那一类;两者同一条执行与清理口径,销毁时一起按挂载的相反顺序回调。
387
- - 返回值是一个幂等的卸载函数(没有返回 `destroy` 的插件也照样返回它);`destroy()` 之后调用不会执行任何东西。
399
+ - `options.plugins` 适合「创建时就该有」的插件,`use()` 适合按需加载 / 用户操作之后才接上的那一类;两者同一条执行与清理口径,销毁时一起按挂载的相反顺序清理。
400
+ - 返回值是一个幂等的卸载函数(没有返回 `destroy`、也没有登记清理项的插件也照样返回它);`destroy()` 之后调用不会执行任何东西,`context` 上再登记资源会立即走到期清理或忽略。
388
401
  - 与插件并列的另一条扩展点是**自定义图元**:插件加的是「视图级行为」(快捷键、面板、URL 同步…),自定义图元加的是「点怎么画」—— 注册一支画笔 `view.registerSymbol('三角', painter)`,或在数据里写 `{ type: 'custom', draw }`,见「自定义图元」一节。
389
402
  - 第三条扩展点是**自定义条件运算符**:插件加行为、图元加画法,运算符加的是「条件语言里的判定动词」—— `view.registerOperator('mod2', value => current => …)` 之后规则 / 筛选 / 标签的 `when` 都能写 `{ vn_kv: { mod2: true } }`,见「条件语言 → 自定义运算符」。
390
403
 
@@ -420,7 +433,7 @@ dispose() // 幂等注销;同名已被后来
420
433
 
421
434
  | 扩展点 | 适合做什么 | 作用域 | 清理方式 |
422
435
  | --- | --- | --- | --- |
423
- | `plugins` / `view.use(plugin)` | 视图级行为:快捷键、URL 同步、图例、业务面板,组合已有实例 API | 单个视图实例 | `options.plugins` 随 `view.destroy()` 按相反顺序回调;`view.use()` 返回幂等卸载函数,调用即执行插件的 `destroy` |
436
+ | `plugins` / `view.use(plugin)` | 视图级行为:快捷键、URL 同步、业务面板与组件之外的自定义图例,组合已有实例 API;热力色带、分段点选、区间拖动与自定义渲染优先用 `options.visualMap` | 单个视图实例 | `options.plugins` 随 `view.destroy()` 按相反顺序清理;`view.use()` 返回幂等卸载函数。`context.signal` / `onCleanup` / `on` / `registerCommand` 随卸载自动回收,旧 `destroy` 仍兼容 |
424
437
  | `view.registerSymbol(name, painter)` | 决定「点怎么画」:品牌图标、告警形状、状态符号等 | 模块级符号表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;不会随 `view.destroy()` 自动注销 |
425
438
  | `view.registerOperator(name, test)` | 给规则 / 筛选 / 标签增加条件动词,例如 `mod2`、`evenIndex` | 模块级条件运算符表,同页所有实例共享 | 返回幂等注销函数,只删除自己这次注册;注销后旧条件按「不成立」处理 |
426
439
  | `view.addWebGLLayer(layer)` | 直接拿组件的 WebGL2 上下文画自定义标记、特效或调试几何;层画在内置内容之后,不参与拾取与数据导出 | 单个视图实例 | 返回卸载函数;也可用 `updateWebGLLayer` / `removeWebGLLayer` / `clearWebGLLayers` 维护,画布外还可通过 `view.gl` 直接取上下文 |
@@ -30,6 +30,10 @@
30
30
  | `updateBackground(background)` | 替换背景层 |
31
31
  | `getResolvedBackground()` | 引擎解析后的背景层:`{ color, image: { quad, texture, source, opacity }, geoJson: { regions, labels, bounds, rawBounds, fillBuckets, strokeGroups, pointGroups } }`,自建缩略图 / 图例 / 取真实范围都用它 |
32
32
  | `getBackgroundLabels()` | 背景 GeoJSON 的区域文字(世界坐标 + 字号 / 颜色 / 偏移),自建覆盖层用 |
33
+ | `beginBackgroundEdit(options?)` | 进入内置背景编辑:移动、四角 / 八向缩放、可选旋转、编辑框与同帧刷新由组件接管;图片写回 `image.corners`,GeoJSON 写回 `geoJsonStyle.bounds` + `rotation`,其它背景字段保留。自动打开 `backgroundEditing`,没有可编辑背景时只报 `onWarning` |
34
+ | `updateBackgroundEdit(patch)` | 运行中改编辑器配置或背景几何:宽高锁 / 旋转点 / 吸附角 / 样式 / `center` / `width` / `height` / `angle` 都可增量更新;返回实例 |
35
+ | `endBackgroundEdit()` | 退出内置编辑、移除覆盖层并恢复进入前的 `backgroundEditing`;已编辑的背景保留 |
36
+ | `getBackgroundEdit()` | 当前编辑读数(类型 / 中心 / 宽高 / 角度 / 手柄数 / 配置 / 样式);未开启时 `null` |
33
37
  | `fitView(padding?)` / `resetView()` | 适应数据范围 / 回到初始视图 |
34
38
  | `applyInitialView()` | 按 `options.initialView` 重新落位(缺省就是适应数据范围),首次加载引擎会自动执行一次;换数据之后想重新套用同一份配置就手动调它 |
35
39
  | `zoomIn(step?)` / `zoomOut(step?)` / `zoomBy(factor, origin?)` | 相对缩放,`origin` 为画布 CSS 像素锚点(缺省视口中心) |
@@ -48,15 +52,17 @@
48
52
  | `getBounds()` | 数据范围(没有点时回退到背景 GeoJSON 的 `rawBounds`,也就是 `aspectScale` 之前的真实经纬度范围);每次返回独立副本,重复调用命中缓存 |
49
53
  | `getViewRect()` | 当前可视范围的世界坐标矩形;自建缩略图 / 坐标读数用它换算视口框,内置鹰眼图见「鹰眼图」 |
50
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)」 |
51
56
  | `getMinimapZoom()` / `setMinimapZoom(zoom)` / `zoomMinimapBy(factor, origin?)` | 读 / 写鹰眼图自身的倍数:`set` 夹在 `minimap.minZoom` ~ `maxZoom` 之间,`zoomMinimapBy` 以画布 CSS 像素 `origin` 为锚点缩放(缺省小图中心),没配 `minimap` 时是空操作 |
52
57
  | `getPoint(ref)` | 按 id / 下标 / `{ id }` / `{ index }` 取一个节点:拾取载荷(`item` / `x` / `y` / `layer` / `layerRank`)之外再给一份屏幕半径 `radius`;找不到返回 `null`。自建读数面板、右键菜单用它,省掉「自己遍历 `getData()` + 算投影」那一段 |
53
- | `getPointStyle(index)` / `getLineStyle(index)` | 节点 / 线路解析之后的样式快照:节点的 `size` / `symbol` 取样式链(自身 > 规则 > 全局),线路的 `width` / `color` / `opacity` / `zIndex` 同源;不含悬停 / 选中样式、整体倍率与热力倍率,下标不存在时返回 `null`。合并节点与鹰眼图沿用主图尺寸时走这一份 |
58
+ | `getPointStyle(index)` / `getLineStyle(index)` | 节点 / 线路解析之后的样式快照:节点的 `size` / `symbol` 取样式链(`override` 规则 > 自身 > 普通规则 > 全局),线路的 `width` / `color` / `opacity` / `zIndex` 同源;不含悬停 / 选中样式、整体倍率与热力倍率,下标不存在时返回 `null`。合并节点与鹰眼图沿用主图尺寸时走这一份 |
54
59
  | `getLine(ref)` | 取一条线路:拾取载荷 + `path` / `bounds` / `color` / `width`(与 `listLines()` 的一项同一份口径),通道回线还带 `channelId` / `groupId` / `channelIndex`;找不到返回 `null` |
55
60
  | `getChannel(ref)` | 取一条通道:数据那一份(`index` / `item` / `id`)+ 成员表(`lineIndexes` / `skeleton` / `offsets` / `paths`)+ 分组上下文 `context`(几组 / 当前哪组 / 每组拐点数)、`from` / `to` / `anchored` / `spacing`;找不到返回 `null` |
56
61
  | `listPoints(options?)` | 按条件列节点快照 `{ index, item, x, y, radius, layerRank, screen? }`:选项见 `GisViewListOptions`(`indexes` / `inView` / `limit` / `withScreen` / `visibleOnly`),可见判定与 `isPointVisible` 是同一份 |
57
62
  | `getClusters()` | 当前点聚合的展示组 `GisViewCluster[]`:按数据顺序返回,含 `count: 1` 的单成员与 `count > 1` 的聚合圆;每项带 `id` / `pointIndexes` / `pointIds` / 世界坐标 `coordinate` / 当前屏幕坐标 `screen` / 半径 `radius` / 包围盒 `bounds`。原始 `points` 与 `getData()` 不变,聚合关掉时返回空表 |
58
63
  | `getPointCluster()` | 当前点聚合配置的归一化读数 `GisViewPointClusterReadout \| null`:`{ enabled, radius, minPoints, maxZoom, active }`,`active` 说明当前缩放档是否仍在聚合(超过 `maxZoom` 后为 `false`);引擎未就绪时为 `null` |
59
64
  | `getPointSpacing()` | 当前可见节点的最近正距离与最远距离 `GisViewPointSpacing`:`minDistance` / `maxDistance` 与 `nearest` / `farthest.distance` 都是当前相机比例下的 CSS 像素,和 `fitSpacing` 同一套口径;`nearest` / `farthest` 各带两端节点 `{ id, index, item }`。筛选 / 隐藏后的节点不参与;少于两个可见节点时为 `null`,全部重合时最近为 `null`、最远为 `0` |
65
+ | `getPointsSpacing(refs)` | 指定一组节点的整体最近正距离与最远距离 `GisViewPointSpacing`,口径与 `getPointSpacing()` 相同:`refs` 可传单个或数组的 id / 下标 / `{ id }` / `{ index }`,重复引用去重、非法引用忽略;只统计显式传入的节点,筛选 / 隐藏不影响。少于两个有效节点时两列为 `null`,全部重合时最近为 `null`、最远为 `0` |
60
66
  | `listLines(options?)` | 按条件列线路快照 `{ index, item, path, bounds, color, width, channelId?, groupId?, channelIndex?, screen? }`:选项与 `listPoints` 同一套;通道回线给的是骨架推导出来的那条折线(跟图上画的一致) |
61
67
  | `getLineChannel(ref)` | 这条线路属于哪条通道:`getLineChannel('line-a')`(id)/ `getLineChannel(12)`(`lines` 下标)给 `{ lineIndex, channelId, channelIndex, channel, groupId, group }`,不在通道里返回 `null`。点了一条回线想知道它归哪条通道 / 哪一组时用它,不必自己扫通道定义 |
62
68
  | `getElementBounds(keys)` | 一批要素 `GisViewFeatureKey[]`(`{ type: 'point' \| 'line', index }`,`getSelections()` 的结果可以直接传)的世界包围盒 `{ minX, minY, maxX, maxY }`;一个都没对上返回 `null`(数据已经换过) |
@@ -88,6 +94,7 @@
88
94
  | `getSelectionData()` | 选中集一次取全:读数(`ids` / `count` / `pointCount` / `lineCount` / `channelCount`)加要素本身(`points` / `lines` / `channels`(按通道去重,与 `getData()` 同一批对象)),另有 `items`(与 `getSelections()` 同一份,通道回线不带通道字段)、`pointIndexes` / `lineIndexes` 与 `bounds`(世界包围盒,空选中时 `null`),见「接口选中(selectFeatures)与读回选中」 |
89
95
  | `getCascadeHighlight()` | 当前点击级联高亮的读数:`{ origin, scope, points, lines, pointIndexes, lineIndexes }`;没有高亮或 `cascadeHighlight` 关着时返回 `null`。它只读、不改选中集,见「点击级联高亮」 |
90
96
  | `selectInBox(box, options?)` | 按画布 CSS 像素矩形框选,等价于 Shift + 拖动(跟随 `shortcuts.boxSelect`);`options.selectionKinds` 可只覆盖这一次的节点 / 线路 / 通道类型过滤,也支持 `'lineEndpoints'` / `'channelEndpoints'` 要求两端节点都在框内。每次框选操作都发 `onSelect`(空框的载荷为 `null`),选中集实际变化时再发 `onSelectChange`,顺序 `onSelect → onSelectChange`(`cause: 'boxSelect'`);通道回线的分组字段与鼠标框选同一份 |
97
+ | `clearSelectionBox()` / `getSelectionBox()` | 收起当前保留的框选矩形 / 读当前矩形:`clearSelectionBox()` 只动框,不改变选中集;`getSelectionBox()` 返回画布 CSS 像素的 `{ x, y, width, height }` 副本,没有框时返回 `null`。保留框由 `selectionBoxPersistent` 打开,见「框选」 |
91
98
  | `beginCopyPoint(options?)` | 开始复制一个节点:给 `id` / `index` 直接出预览(`commit: true` 出预览即落地,`follow: true` 跟随鼠标,`autoCommit: false` 停在预览态),不给来源就是「先点按钮再选节点」模式,见「节点复制」 |
92
99
  | `commitPointCopy()` / `cancelPointCopy()` | 落地 / 取消预览中的复制(返回 `end` / `cancel` 载荷) |
93
100
  | `updateCopyPoint(patch)` | 改预览节点的数据(等价于回调里的 `payload.update`),返回改完的节点 |
@@ -156,7 +163,7 @@
156
163
  | `removeChannel(channelId)` | 移除整条通道:它名下的回线一条条摘掉,返回 `{ ok, channelId, lineIds, lines, removedChannel }` |
157
164
  | `updatePoint(id, patch)` / `updateLine(id, patch)` / `updateChannel(id, patch)` | 定向更新一个节点 / 线路 / 通道并重渲染:单项字段(尺寸、图标、颜色、线宽、通道宽度 / 名称等)直接写补丁;只重排受影响的那一小片(节点 → 绑定线与锚定通道(尺寸 / 图标换过时,锚定通道的宽度一起按新尺寸重排,见「通道与多回线」),线路 → 这一条,通道 → 它的分组与成员),其余要素与缓存一个字节都不动;节点的 `visible: false` / 改回来会走一次整份重排(这一条要连绑定线、锚定通道与拾取索引一起退场 / 排回去,定向更新那条路落不下;同一个 `batch()` 里改一批只重排一次);`updateLine` 的补丁里还能写 `groupId` / `groupIndex`(可配 `channelId`)顺带把这条回线挪到目标分组,`updateChannel` 写 `groups` / `lineIds` 会顺带改分组并补一条 `channelGroups` 变更(见「通道分组」);`updateChannel` 还能给第三段 `linePatches`(一条或一批 `{ id \| index, ...字段 }`)把「通道声明 + 成员回线各自的字段 / 分组」一次下发 —— 声明先落、再逐条走 `updateLine`,整次调用只发一条 `batch` 数据变更、历史里只算一步(见「通道分组」与「逐方法示例」);第三段里认不出的成员 id 带得起定义(`from` / `to` 两端可解析、可用的 `path`、或通道自己声明了两端)会当场建一条回线并挂进通道,给不出定义才跳过点名;`updateLine` 的**拐点**写在 `path` 上(整条折线,含首尾端点 —— 只写中间拐点用 `setLineWaypoints(id, coordinates)`),通道里的回线几何由通道骨架推导(`path` 写了也会被覆盖),改它走 `setLineWaypoints` / `setChannelPath` |
158
165
 
159
- | `setFeatureVisible(kind, refs, visible)` / `hideFeatures(kind, refs)` / `showFeatures(kind, refs)` | 统一隐藏 / 显示节点、线路或通道:只改数据里的 `visible`,不删要素,分组 / 拐点 / 编辑状态都保留。`kind` 收 `point` / `line` / `channel`,`refs` 收单个或一批 id / 下标 / `{ id }` / `{ index }`,也认 `'selection'`(通道按选中回线的归属取整条);`hideFeatures` / `showFeatures` 是 `false` / `true` 的快捷写法。整次调用收成一条历史与一条 `batch` 数据变更,返回 `{ ok, reason, kind, visible, indexes, ids, items, skipped }`;认不出的引用逐条进 `skipped`,不影响其余条目 |
166
+ | `setFeatureVisible(kind, refs, visible)` / `hideFeatures(kind, refs)` / `showFeatures(kind, refs)` | 统一隐藏 / 显示节点、线路、通道或通道分组:只改数据里的 `visible`,不删要素,分组 / 拐点 / 编辑状态都保留。`kind` 收 `point` / `line` / `channel` / `group`;`refs` 收单个或一批 id / 下标 / `{ id }` / `{ index }`,也认 `'selection'`(通道按选中回线的归属取整条,分组按归属取整组)。分组的引用还可写 `{ channelId \| channelIndex, groupId \| groupIndex }`、`{ id }`,或全局唯一的裸分组 id。`hideFeatures` / `showFeatures` 是 `false` / `true` 的快捷写法。整次调用收成一条历史与一条 `batch` 数据变更,返回 `{ ok, reason, kind, visible, indexes, ids, items, groups, skipped }`:前三种 `indexes` 是数据下标、`ids` 是要素 id;`group` 的 `indexes` 是通道下标、`ids` 是分组 id、`groups` 是分组快照。认不出的引用逐条进 `skipped`,不影响其余条目,见「节点、线路、通道与分组的批量显隐」 |
160
167
  | `movePoint(id, coordinate)` / `movePoint(id, x, y)` | 挪一个节点:`{ x, y }` / `[x, y]` / 两个数字都认;绑在它身上的线路端点与以它为锚点的通道骨架跟着重排(与拖动落地同一条路) |
161
168
  | `setLineWaypoints(id, coordinates)` | 整份换一条线路的拐点:**只写中间拐点**(不含首尾两端;传单个坐标 = 只留一个拐点,空数组 = 拉直)。两端按节点 / `start` / `end` 补齐(绑了 `from` / `to` 时两端始终按节点坐标走),存回 `lines[].path` 的是「首尾端点 + 中间顶点」那一份;只插一个用 `addWaypoint(id, coordinate)`,删一个用 `removeWaypoint(id, pathIndex)`;通道里的回线改的是它所在分组的骨架(与 `setChannelPath` 同一份声明) |
162
169
  | `getLineDecorations(id)` / `setLineDecorations(id, list)` / `addLineDecoration(id, decoration)` / `updateLineDecoration(id, index, patch)` / `removeLineDecoration(id, index)` | 线路上标记(图片 / SVG 装饰)的读写:`get` 给副本(要素没找到给空数组),`set` 整份换(空数组 = 全摘掉),`add` 追加在末尾并返回加进去的那一条,`update` / `remove` 按 `decorations` 的顺序数(返回改动的那一条,要素或下标找不到时给 `null` 并记一条 warning);四个写方法各只发一条数据变更、只落一步历史 |
@@ -184,7 +191,7 @@
184
191
  | `registerOperator(name, test)` | 注册一个自定义条件运算符:`test(value)` 返回逐值判定函数,规则 / 筛选 / 标签的 `when` 里就是一个新动词(见「条件语言 → 自定义运算符」);注册之后当场重编译,返回幂等的注销函数 |
185
192
  | `unregisterOperator(name)` | 注销一个自定义运算符(返回它之前是否注册着):还在用这个名字的条件按认不出来处理(不成立,一个要素都不命中) |
186
193
  | `listOperators()` | 现在能用哪些运算符:内置 14 个在前,自定义按注册顺序跟在后面 |
187
- | `use(plugin)` | 运行时挂一个插件(写法与 `options.plugins` 里的一项一致):调用即执行,返回幂等的卸载函数(跑插件自己的 `destroy`),见「插件」 |
194
+ | `use(plugin)` | 运行时挂一个插件(写法与 `options.plugins` 里的一项一致):调用即执行,返回幂等的卸载函数(中止 `signal`、跑旧写法的 `destroy`、执行 context 自动清理),见「插件」 |
188
195
  | `registerCommand(name, handler)` | 注册一个实例命令:`handler(payload, context)` 同步执行,`context` 是冻结的 `{ view, name }`;同名覆盖,返回幂等的注销函数,旧卸载函数不会误删后来注册的同名命令,见「实例命令」 |
189
196
  | `executeCommand(name, payload?)` | 执行实例命令:成功给 `{ ok: true, name, value }`;名字非法 / 未注册 / handler 抛错给 `{ ok: false, name, reason }`,见「实例命令」 |
190
197
  | `listCommands()` | 当前注册着哪些实例命令(按注册顺序;同名覆盖不改变原来的位置) |
@@ -207,7 +214,7 @@
207
214
 
208
215
  返回 `this` 的方法都可以链式调用;返回其它值的方法(`get*` / `list*` / `export*` / `search` / `pick*` / `add*` / `remove*` / `copy*` / `merge*` / `locate*` 这些)在下面的「参数速查」里都标了返回类型。
209
216
 
210
- **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 274 个:269 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 100 个键,数据字段表列全 266 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
217
+ **怎么确认「组件到底有哪些属性和方法」**:上面那张表按功能分组介绍,下面的「参数速查」把实例的**每个成员逐个列全**(当前 283 个:278 个方法 + `el` / `canvas` / `gl` / `cameraAnimating` / `dataVersion` 五个属性),`options` 表列全 107 个键,数据字段表列全 275 个字段。这三份清单不靠手抄 —— 它们直接对着源码与类型对账:
211
218
 
212
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` 时只校验有没有过期。
213
220
  - `node docs/steps/check-doc-coverage.mjs`:10 项双向对账 —— 实例成员 × 文档 / × 参数速查 / × 类型、参数速查与实例方法主表各自反查过期条目、类型反查过期声明、数据字段 × 文档、options 键 × 文档 / options 表反查过期字段、demo 清单是否最新。加一个实例成员、改一个 options 键而没同步文档时,这个脚本会直接报出来。
@@ -1,6 +1,6 @@
1
1
  # 17 参数速查(逐个方法)
2
2
 
3
- > 274 个成员逐个列全:参数、默认值与返回。
3
+ > 283 个成员逐个列全:参数、默认值与返回。
4
4
 
5
5
  ### 参数速查(逐个方法)
6
6
 
@@ -30,14 +30,18 @@
30
30
  | `getData()` | 无 | `GisViewData` |
31
31
  | `updateData(points, lines, channels?)` | 整份换数据:`points?: GisViewPoint[]` 新的节点数组(不传就不动);`lines?: GisViewLine[]` 新的线路数组(不传就不动);`channels?: GisViewChannel[]` 给了就连通道声明一起换(切场景一次换完、只发一条 `dataChange`)。也收单对象写法 `updateData({ points, lines, channels })`(只换给出的那几列;只换通道声明写 `updateData({ channels })`);接口字段先在调用方映射成这三列,见「动态换数据与加载完成」 | `this` |
32
32
  | `importData(input, options = {})` | `input: GisViewDocumentInput`:标准文档(`format: 'gis-view'`)/ 与创建入参同构的数据对象(`points` / `lines` / `channels` / `labelStates` / `view` / `filter`,给哪列出哪列)/ 节点数组;`options` 可以把某一列(`points` / `lines` / `channels` / `labelStates` / `view` / `filter`)显式点上,`checkKeys: false` 不点名多余的顶层键 | `GisViewImportResult` |
33
- | `updatePoint(id, patch)` | `id: string \| number` 节点 id;`patch: Partial<GisViewPoint>` 要合并的字段(写 `x` / `y` 就是移动,写 `null` 摘掉这一项;写 `visible: false` 把这一条从图上收起、写 `visible: true` 排回来 —— 这一条要连绑定线 / 锚定通道一起重排,因此走整份重推一次,`batch()` 里一批只推一次) | `this` |
34
- | `updateLine(id, patch)` | `id` 线路 id;`patch: GisViewLinePatch` 要合并的字段(`path` / `from` / `to` / `color` / `width` / `labels`… 都能改),还能写三个「分组指令」:`groupId`(或 `groupIndex`,可配 `channelId`)把这条回线挪到目标分组 —— `groupId: 'newGroup'` 单独拆一组、其余按现有分组 id 并组,走的是 `moveChannelLine` 那一套;三个字段只是指令,不会写进线路数据、不会出现在 `updateLine` 的载荷里,挪组成功会先发一条 `channelGroups` 变更(只给指令时这次调用就只发它);**拐点**写在 `path` 上(整条折线,含首尾端点 —— 只写中间拐点用 `setLineWaypoints(id, 中间拐点)`),通道里的回线几何由通道骨架推导(`path` 写了也会被覆盖,改它走 `setLineWaypoints` / `setChannelPath`) | `this` |
33
+ | `updatePoint(id, patch)` | `id: string \| number` 节点 id;`patch: Partial<GisViewPoint>` 要合并的字段(写 `x` / `y` 就是移动,写 `color` / `size` / `symbol` / `strokeColor` / `strokeWidth` 就是改这一条的样式,写 `null` 摘掉这一项;写 `visible: false` 把这一条从图上收起、写 `visible: true` 排回来 —— 这一条要连绑定线 / 锚定通道一起重排,因此走整份重推一次,`batch()` 里一批只推一次)。命中的 `override: true` 规则会压住同字段的逐条补丁 | `this` |
34
+ | `updateLine(id, patch)` | `id` 线路 id;`patch: GisViewLinePatch` 要合并的字段(`path` / `from` / `to` / `color` / `width` / `opacity` / `lineType` / `decorations` / `labels`… 都能改),还能写三个「分组指令」:`groupId`(或 `groupIndex`,可配 `channelId`)把这条回线挪到目标分组 —— `groupId: 'newGroup'` 单独拆一组、其余按现有分组 id 并组,走的是 `moveChannelLine` 那一套;三个字段只是指令,不会写进线路数据、不会出现在 `updateLine` 的载荷里,挪组成功会先发一条 `channelGroups` 变更(只给指令时这次调用就只发它);**拐点**写在 `path` 上(整条折线,含首尾端点 —— 只写中间拐点用 `setLineWaypoints(id, 中间拐点)`),通道里的回线几何由通道骨架推导(`path` 写了也会被覆盖,改它走 `setLineWaypoints` / `setChannelPath`)。命中的 `override: true` 规则会压住同字段的逐条补丁 | `this` |
35
35
  | `updateChannel(id, patch, linePatches?, lineIds?)` | `id` 通道 id(也认下标);`patch: Partial<GisViewChannel>` 要合并的通道声明字段(`lineIds` / `groups` / `path` / `width` / `nodeWidth`…)。补丁里的 `lineIds` 是这条通道成员的**整表** —— 增删线路就是改它:写回声明前与 `groups` 调和(不在表里的成员从各组剔掉、空组去掉,新成员补进第一组,组剔完回「整条通道一组」);写完把引擎算出的最终声明(`{ id, lineIds, path }`)写回数据副本,并补一条 `channelGroups` 变更(排在 `updateChannel` 那条之前)。成员表里认不出的 id、找不到的通道、没给补丁的调用各报一句 `onWarning`。`linePatches?: GisViewChannelLinePatch \| GisViewChannelLinePatch[]` 第三段:要一起下发的**成员回线补丁**,一条或一批 `{ id \| index, ...字段 }`(定位与 `updateLine` 的 `id` 同一套解析;其余字段与 `updateLine` 的补丁完全同一套,含 `groupId` / `groupIndex` / `channelId` 三个分组指令)—— 声明先落、再逐条走 `updateLine`,整次调用只发一条 `batch` 数据变更、历史里只算一步;它只改成员回线自己的样式 / 信息与分组归属,不靠它改成员表;不属于这条通道的成员表(`lineIds` + 各组 `lineIds`)的条目跳过并报一句 `onWarning`(同一个条目只提醒一次),认不出来的条目也点名;第三段里认不出的成员 id 带得起定义(`from` / `to` 两端可解析、可用的 `path`、或通道自己声明了两端)会当场建一条回线并挂进通道(走 `addLines` 那条路),给不出定义才跳过。`lineIds?: string \| number \| Array<string \| number>` **第四段**:单独成参数的**成员整表**(与第三段必须成对给)—— 表里有、数据里没有的当场建,表里有的逐条改,表里没有了的旧成员直接从数据里删掉(走 `removeLines` 同一条路,视图跟着少;要只移出、留数据用 `removeChannelLines`);空表不当清空用、只给 `lineIds` 不给线路数组时报一句什么都不做;与第二段的 `patch.lineIds` 同时写且不一样时以第四段为准(报一句)。成员表缩小时,被移出通道、又不在别的通道上的回线各报一句 `onWarning`(第二段的 `patch.lineIds` 走这条:数据留着;它仍然照常绘制,要删用 `removeLines`)。不传第四段时只改 id 匹配的成员、数量不动 | `this` |
36
36
  | `patchPoints(entries, patch?)` | `entries: GisViewUpdateItemEntry<GisViewPoint> \| 数组` 要改的节点(写 `{ id: 'a', name: 'A' }` 用 id 定位、`{ index: 3, x: 10 }` 用下标定位,其余字段就是这一条的补丁);`patch?: Partial<GisViewPoint>` 可选:对上面每一批定位统一补一份字段(`patchPoints(['a','b'], { status: 1 })`) | `GisViewUpdateItemsOutcome<GisViewPoint>` |
37
37
  | `patchLines(entries, patch?)` | 同 `patchPoints`,定位的是线路 | `GisViewUpdateItemsOutcome<GisViewLine>` |
38
38
  | `updateBackground(background?)` | `background?: GisViewBackground` 新的背景层(底色 / 图片 / GeoJSON);不传等于清掉 | `this` |
39
39
  | `getResolvedBackground()` | 无 | `GisViewResolvedBackground` |
40
40
  | `getBackgroundLabels()` | 无 | `GisViewResolvedBackgroundLabel[] \| null` |
41
+ | `beginBackgroundEdit(options = {})` | `options?: GisViewBackgroundEditorOptions`:`aspectLocked`(缺省 `true`,四角等比;`false` 出八向)、`rotation`(缺省 `false`,为 `true` 时多一个旋转点)、`snapAngle`(Shift 吸附角,缺省 15)、`minSize`(缺省 12)、`rotationDistance`(缺省 30)、`fitView` / `fitPadding`、`showHint`、`style`、`onChange` / `onEnd`。自动打开 `backgroundEditing`;没有可编辑背景时不做事并报 `onWarning` | `this` |
42
+ | `updateBackgroundEdit(patch)` | `patch: GisViewBackgroundEditorPatch`:配置 `aspectLocked` / `rotation`(布尔切旋转点,数字等价 `angle`)/ `snapAngle` / `minSize` / `rotationDistance` / `fitView` / `fitPadding` / `showHint` / `style` / `onChange` / `onEnd`,几何 `center` / `width` / `height` / `angle`;返回实例 | `this` |
43
+ | `endBackgroundEdit()` | 无 | `this` |
44
+ | `getBackgroundEdit()` | 无 | `GisViewBackgroundEditorState \| null` |
41
45
  | `batch(fn, options?)` | `fn: () => T` 这一批里要做的增删改 / 分组操作(内部调 `patchPoints` / `patchLines` / `updateChannel`、`removePoints` / `removeLines` / `removeChannel`、`splitChannelGroups` / `removeChannelGroup` 等都会收成一条);`options.label?: string` 给这一步历史起名 | `fn` 的返回值 |
42
46
  | `resize()` | 无(改过祖先 `transform: scale` / 容器尺寸后手动同步一次;CSS `zoom` 会自动感知) | `this` |
43
47
  | `refresh(options?)` | `options?: { reflow?: boolean }` 传 `false` 只重画一帧(跳过整份重解析:数据没变、只想重画时用);不传或 `true` 走整份重建 | `this` |
@@ -79,6 +83,8 @@
79
83
  | `getViewRect()` | 无 | `GisViewBounds` |
80
84
  | `isLayoutReady()` | 无 | `boolean` |
81
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`,调用前会先落下一帧挂起的重画 |
82
88
  | `getMinimapZoom()` | 无 | `number` |
83
89
  | `setMinimapZoom(zoom)` | `zoom: number` 鹰眼图的目标倍数(被 `minimap.minZoom` / `maxZoom` 夹住) | `this` |
84
90
  | `zoomMinimapBy(factor, origin?)` | `factor: number` 倍数;`origin?: [x, y]` 以小图里的哪个点为中心(缺省小图中心) | `this` |
@@ -121,17 +127,18 @@
121
127
  | `getContextAt(x, y, options?)` | `x` / `y` 画布 CSS 像素;`options?: GisViewContextAtOptions`:`selection: false` 不整理选中集、`lngLat: false` 不换算经纬度(dragover / 自绘十字线这类每帧来的场合用它省分配) | `GisViewContextAtResult`;给了选项时是 `GisViewContextAtLightResult`(没要的那几项不再生成) |
122
128
  | `nearestPointsAt(x, y, options?)` | `x` / `y` 画布 CSS 像素;`options?: GisViewNearestOptions`:`limit`(最多几个,缺省 1)、`maxDistance`(**屏幕像素**上限,缺省不限)、`withScreen: true` 补屏幕坐标 | `GisViewNearestPoint[]`,按距离从近到远:`{ index, item, x, y, radius, distance, distancePx, screen? }`;内部走点网格,十万级数据也只碰光标附近的单元格 |
123
129
  | `nearestLineAt(x, y, options?)` | `x` / `y` 画布 CSS 像素;`options?: GisViewNearestLineOptions`:`maxDistance`(**屏幕像素**,缺省 40,超出算没命中)、`editableOnly: true` 只看允许加拐点的线、`withScreen: true` 补屏幕坐标 | `GisViewNearestLine \| null`:`{ index, item, path, distance, distancePx, position, channelId?, groupId?, channelIndex?, screen? }`,`position` 是落点投到线上的沿线比例(0~1),加拐点 / 挂装饰直接当 `position` 用;附近没线返回 `null` |
124
- | `setDropPreview(preview)` | `preview: GisViewDropPreviewInput \| null`:`kind`(`'point'` / `'line'` / `'channel'` / `'waypoint'` / `'decoration'` / `'label'`,缺省按 `item` 推断)、`screen`(画布 CSS 像素)或 `world`(世界坐标)至少给一个、`item` 是**准备落地的那一份字段**(与 `addPoint` / `addLine` 同一份)、`from`(line / channel 的起点);传 `null` 清掉预览 | `GisViewDropPreview \| null` 解析后的读数 `{ kind, screen, world, size, styleSize, radius, color, strokeColor, strokeWidth, opacity, symbol, width, dashed }`:走的是与真实要素完全相同的样式链(规则 -> 全局样式 -> 这一份字段),`styleSize` 与落地后 `getPointStyle().size` 是同一个数(`size` 是这一刻画出来的宽度,不悬停 / 不选中时两者相同) |
130
+ | `setDropPreview(preview)` | `preview: GisViewDropPreviewInput \| null`:`kind`(`'point'` / `'line'` / `'channel'` / `'waypoint'` / `'decoration'` / `'label'`,缺省按 `item` 推断)、`screen`(画布 CSS 像素)或 `world`(世界坐标)至少给一个、`item` 是**准备落地的那一份字段**(与 `addPoint` / `addLine` 同一份)、`from`(line / channel 的起点);传 `null` 清掉预览 | `GisViewDropPreview \| null` 解析后的读数 `{ kind, screen, world, size, styleSize, radius, color, strokeColor, strokeWidth, opacity, symbol, width, dashed }`:走的是与真实要素完全相同的样式链(`override` 规则 > 这一份字段 > 普通规则 > 全局样式),`styleSize` 与落地后 `getPointStyle().size` 是同一个数(`size` 是这一刻画出来的宽度,不悬停 / 不选中时两者相同) |
125
131
  | `getDropPreview()` | 无 | `GisViewDropPreview \| null`:当前拖拽落点预览的读数,没有预览时 `null` |
126
132
  | `getPoint(ref)` | `ref: GisViewFeatureRef` 节点定位:id、下标、`{ id }` / `{ index }` | `GisViewPointInfo \| null` |
127
- | `getPointStyle(index)` | `index: number` 节点下标 | `GisViewPointStyleSnapshot \| null`:节点解析后的样式快照 `{ index, size, symbol, zIndex }`,`size` / `symbol` 取样式链(节点自身 > 规则 > `pointStyle`)定下来的那一份,含 `sizeScale`、不含悬停 / 选中样式与热力倍率;落点预览的读数与它同源 |
128
- | `getLineStyle(index)` | `index: number` 线路在 `lines` 里的下标 | `GisViewLineStyleSnapshot \| null`:`{ index, width, color, opacity, zIndex }`;`width` 取样式链(线路自身 > 规则 > `lineStyle`)定下来的原始线宽,不含 `lineWidthScale` / 缩放联动 / 热力倍率,`color` 是解析后的 RGBA(0 ~ 1)。局部更新与线路窄样式快路径之后读到的都是最新值;下标不存在时 `null` |
133
+ | `getPointStyle(index)` | `index: number` 节点下标 | `GisViewPointStyleSnapshot \| null`:节点解析后的样式快照 `{ index, size, symbol, zIndex }`,`size` / `symbol` 取样式链(`override` 规则 > 节点自身 > 普通规则 > `pointStyle`)定下来的那一份,含 `sizeScale`、不含悬停 / 选中样式与热力倍率;落点预览的读数与它同源 |
134
+ | `getLineStyle(index)` | `index: number` 线路在 `lines` 里的下标 | `GisViewLineStyleSnapshot \| null`:`{ index, width, color, opacity, zIndex }`;`width` 取样式链(`override` 规则 > 线路自身 > 普通规则 > `lineStyle`)定下来的原始线宽,不含 `lineWidthScale` / 缩放联动 / 热力倍率,`color` 是解析后的 RGBA(0 ~ 1)。局部更新与线路窄样式快路径之后读到的都是最新值;下标不存在时 `null` |
129
135
  | `getLine(ref)` | `ref` 线路定位(同上) | `GisViewLineInfo \| null` |
130
136
  | `getChannel(ref)` | `ref` 通道定位:通道 id 或下标 | `GisViewChannelSnapshot \| null` |
131
137
  | `listPoints(options?)` | `options?: GisViewListOptions`:`indexes`(只看这几个下标)、`inView: boolean`(只要当前视口里的)、`withScreen: boolean`(连屏幕坐标一起给)、`visibleOnly: boolean`(跳过隐藏的)、`limit: number`(最多几条) | `GisViewListedPoint[]` |
132
138
  | `getClusters()` | 无 | `GisViewCluster[]`。当前展示组(含 `count: 1` 的单成员),`screen` 按当前相机现算;聚合关掉时是空表 |
133
139
  | `getPointCluster()` | 无 | `GisViewPointClusterReadout \| null`。归一化配置 `{ enabled, radius, minPoints, maxZoom, active }`,超过 `maxZoom` 后 `active` 为 `false` |
134
140
  | `getPointSpacing()` | 无 | `GisViewPointSpacing`:`count` / `minDistance` / `maxDistance` / `nearest` / `farthest`。距离都是当前相机比例下的 CSS 像素,与 `fitSpacing` 同一套口径;最近 / 最远各带两端 `{ id, index, item }` 与同口径的 `distance`,筛选 / 隐藏后的节点不参与 |
141
+ | `getPointsSpacing(refs)` | `refs: GisViewFeatureRef \| GisViewFeatureRef[]`:一个或一组节点引用,支持 id / 下标 / `{ id }` / `{ index }`;重复引用去重,非法引用忽略 | `GisViewPointSpacing`:只统计显式传入的节点(筛选 / 隐藏不影响),`count` / `minDistance` / `maxDistance` / `nearest` / `farthest` 与 `getPointSpacing()` 同形,距离为当前相机比例下的 CSS 像素;少于两个有效节点时两列为 `null`,全部重合时最近为 `null`、最远为 `0` |
135
142
  | `listLines(options?)` | 同 `listPoints`(`inView` / `withScreen` / `limit`…) | `GisViewListedLine[]` |
136
143
  | `forEachPointInView(rect, visit)` | `rect: GisViewBounds` 世界坐标矩形(一般直接给 `getViewRect()` 的返回值);`visit: (index, item, x, y, radius) => void \| false` 逐个回调,返回 `false` 提前结束 | `void`。只遍历矩形里的可见节点(数据顺序、不建中间数组):与 `listPoints({ inView: true })` 同一份可见判定,但不生成清单 —— 放大看一小片时不会去碰整份数据 |
137
144
  | `getVisibleData()` | 无 | `GisViewData` |
@@ -141,7 +148,7 @@
141
148
  | `clearChangedChannels(refs?)` | `refs?: GisViewFeatureRef \| GisViewFeatureRef[]` 记成新基准的通道(id / 下标 / `{ id }` / `{ index }`,写法与 `removePoints` 一致);不传 / `null` 全部对账,空数组无操作 | `this` |
142
149
  | `getChangedLines(options?)` | `options?: GisViewChangedLineOptions`:`reasons?: GisViewChangedLineReason[]` 只返回 `path` / `endpoints` / `visibility` / `fields` 中的指定原因(`[]` = 空表) | `GisViewChangedLine[]`:相对上次线路对账变了哪些线路,每项给 `{ index, id, item, path, start, end, from, to, visible, pathChanged, endpointsChanged, visibilityChanged, fieldsChanged, fields, reasons }`;新加只补账、删除清账,读本身不改基准 |
143
150
  | `clearChangedLines(refs?)` | `refs?: GisViewFeatureRef \| GisViewFeatureRef[]` 记成新基准的线路(写法与 `removePoints` 一致);不传 / `null` 全部对账,空数组无操作 | `this` |
144
- | `setFeatureVisible(kind, refs, visible)` | `kind: GisViewVisibilityKind`(`'point'` / `'line'` / `'channel'`);`refs: GisViewVisibilityRefs` 单个 / 一批 id 或下标,也认 `'selection'`(通道按选中回线的归属取整条);`visible: boolean` | `GisViewSetVisibilityOutcome`:`{ ok, reason, kind, visible, indexes, ids, items, skipped }`;只改数据的 `visible`,整次调用一条历史、一条 `batch` 数据变更 |
151
+ | `setFeatureVisible(kind, refs, visible)` | `kind: GisViewVisibilityKind`(`'point'` / `'line'` / `'channel'` / `'group'`);`refs: GisViewVisibilityRefs` 单个 / 一批 id 或下标,也认 `'selection'`(通道按选中回线的归属取整条、分组按归属取整组)。分组引用支持 `{ channelId \| channelIndex, groupId \| groupIndex }`、`{ id }` 与全局唯一的裸分组 id;`visible: boolean` | `GisViewSetVisibilityOutcome`:`{ ok, reason, kind, visible, indexes, ids, items, groups, skipped }`。普通三类的 `indexes` 是数据下标、`ids` 是要素 id;`group` 的 `indexes` 是通道下标、`ids` 是分组 id、`groups` 是分组快照。只改数据的 `visible`,整次调用一条历史、一条 `batch` 数据变更 |
145
152
  | `hideFeatures(kind, refs)` | 同上,等价于 `setFeatureVisible(kind, refs, false)` | 同上 |
146
153
  | `showFeatures(kind, refs)` | 同上,等价于 `setFeatureVisible(kind, refs, true)`(隐藏后原样恢复) | 同上 |
147
154
  | `isPointVisible(index)` | `index: number` 节点下标 | `boolean`。在不在图上(被筛选 / `visible: false` 或发散渲染还没铺到的都算 `false`) |
@@ -161,6 +168,8 @@
161
168
  | `getSelectionData()` | 无 | `GisViewSelectionData`:`{ ids, count, pointCount, lineCount, channelCount, points, lines, channels, items, pointIndexes, lineIndexes, bounds }`(`bounds` 是世界包围盒,空选中时 `null`;`items` 的线路不带通道字段,`channels` 是去重后的所属通道) |
162
169
  | `getCascadeHighlight()` | 无 | `GisViewCascadeHighlightInfo \| null`:当前点击级联高亮的来源、范围与节点 / 线路读数 `{ origin, scope, points, lines, pointIndexes, lineIndexes }`,不改变选中集 |
163
170
  | `selectInBox(box, options = {})` | `box: GisViewRect` **画布 CSS 像素**矩形 `{ x, y, width, height }`(与手动框选同一套坐标);`options?: GisViewSelectInBoxOptions`:`selectionKinds` 临时覆盖这一次保留哪几类(`'point'` / `'line'` / `'channel'` / `'lineEndpoints'` / `'channelEndpoints'`、数组、`'all'` / `true` / `false` / `null`,不写沿用全局 `selectionKinds`),`originalEvent` 原样带进 `onSelect`。空框不会选中任何要素 | `this`(每次框选都发 `onSelect`,空框为 `null`;集合变化时再发 `onSelectChange`,顺序 `onSelect → onSelectChange`,`cause` 为 `boxSelect`) |
171
+ | `clearSelectionBox()` | 无:收起当前保留的框选矩形;只动矩形,不改变选中集,也不发选中回调 | `this` |
172
+ | `getSelectionBox()` | 无 | `GisViewRect \| null`:保留框的画布 CSS 像素矩形(已归整为左上角 + 宽高),没有时为 `null` |
164
173
  | `beginCopyPoint(options = {})` | `options: GisViewCopyStartOptions`:`id` / `index` / `item` 三选一给来源,都不给就进入「下一次点选节点」模式;`coordinate` / `screen` 是落点(缺省按 `copy.offset`);`follow` 让预览跟指针;`autoCommit` 覆盖 `copy.autoCommit`;`commit: true` 出预览即落地;`via` / `originalEvent` 原样带回。必须配置 `copy`(`enabled` 不为 `false`),否则返回 `null`。预览节点插在 `points` 末尾且不带 id,不会把绑定源 id 的线路 / 通道带过去 | `GisViewPointCopyPayload \| null`:`phase: 'start'` 的预览载荷;来源认不出来时返回 `null` |
165
174
  | `commitPointCopy()` | 无:把当前复制预览落地,先发 `commit`、落地后发 `end`;`commit` 阶段载荷里的 `cancel()` 可以拦住这次复制 | `GisViewPointCopyPayload \| null`:成功的 `end` 载荷;没有预览或已被取消时为 `null` |
166
175
  | `cancelPointCopy()` | 无:撤掉预览并补一条 `cancel`,数据与历史不动 | `GisViewPointCopyPayload \| null`:取消载荷;没有预览时为 `null` |
@@ -333,7 +342,7 @@
333
342
  | `on(name, handler)` | `name: GisViewEventName \| '*'` 事件名(`'dataChange'` / `'labelClick'`… 或 `'*'` 订阅全部);`handler: (payload, name?) => void` 回调,载荷字段见「事件」一节的表 | `() => void` 取消订阅 |
334
343
  | `once(name, handler)` | 与 `on` 同一套事件名与载荷;只触发一次,回调跑完自动摘掉。返回的取消函数可提前撤销;即使提前取消,重复调用也安全 | `() => void` 取消订阅 |
335
344
  | `off(name, handler?)` | `name` 事件名(也认 `'*'`);`handler?` 只摘掉这一个回调,不传 = 摘掉这个事件的全部订阅 | `number` 摘掉几条(没摘到返回 `0`) |
336
- | `use(plugin)` | `plugin: GisViewPlugin`(`(view, context) => hook`,与 `options.plugins` 的写法一致):立即执行一次,返回卸载函数 —— 调它跑插件自己的 `destroy`(重复调用只清一次,实例销毁之后也安全) | `() => void` 卸载 |
345
+ | `use(plugin)` | `plugin: GisViewPlugin`(`(view, context) => hook`,与 `options.plugins` 的写法一致):立即执行一次,返回卸载函数 —— 调它中止 `signal`、跑旧写法的 `destroy`,再执行 `context.onCleanup` / `on` / `registerCommand` 登记的清理(重复调用只清一次,实例销毁之后也安全) | `() => void` 卸载 |
337
346
  | `registerCommand(name, handler)` | `name: string` 命令名(会 `trim`,同名覆盖);`handler: GisViewCommandHandler<Payload, Value>` 同步处理函数 `(payload, context) => Value`,`context` 是冻结的 `{ view, name }`。名字为空 / handler 不是函数只报 warning、不注册;视图销毁后注册不生效 | `() => void` 幂等注销;同名已被后来者覆盖时不会误删新命令 |
338
347
  | `executeCommand(name, payload?)` | `name: string` 命令名;`payload?: unknown` 原样交给 handler | `GisViewCommandResult<Value>`:`{ ok: true, name, value }` / `{ ok: false, name, reason }`(未注册 / 名字非法 / handler 抛错) |
339
348
  | `listCommands()` | 无 | `string[]` 当前注册的命令名(按注册顺序;同名覆盖不改变位置) |
@@ -23,6 +23,13 @@ const view = createGisView('#map', {
23
23
  view.updateOptions({ showGrid: false, zoomStep: 1.6 })
24
24
  view.updateOptions({ points: nextPoints, lines: nextLines }, true)
25
25
 
26
+ // networkVisible / networkOpacity:只动网架展示,背景、网格和 WebGL 扩展层不受影响
27
+ view.updateOptions({ networkVisible: false }) // 一键完全隐藏网架,先调背景
28
+ view.updateOptions({ networkVisible: true, networkOpacity: 0.35 }) // 恢复并压到 35%
29
+ view.updateOptions({ networkOpacity: 1 }) // 回到逐要素原始不透明度
30
+ view.updateOptions({ backgroundEditing: true }) // 对底图时锁住网架交互,滚轮仍整体缩放
31
+ view.updateOptions({ backgroundEditing: false }) // 调好背景后恢复网架浏览
32
+
26
33
  // denseMode:大数据展示模式('auto' 缺省 / 'on' / 'off',或对象写法换三档阈值)
27
34
  view.updateOptions({ denseMode: 'on' }) // 临时强制进入密集档(标签避让 / 截断,动效暂停)
28
35
  view.updateOptions({ denseMode: { mode: 'auto', visibleThreshold: 3000, densityThreshold: 2500, pointThreshold: 60000 } })
@@ -60,10 +67,14 @@ const imported = view.importData(doc, { checkKeys: false }) // → { ok, reaso
60
67
  // updatePoint(id, patch):合并一个节点的字段;visible 翻面会连绑定线 / 锚定通道一起重排
61
68
  view.updatePoint('a', { x: 120, y: 40 })
62
69
  view.updatePoint('a', { name: '甲站(改)', status: 2 })
70
+ view.updatePoint('a', { color: '#ff3b30', size: 18, symbol: '/marker.svg', strokeColor: '#fff', strokeWidth: 2 }) // 改这一条的颜色 / 尺寸 / 图标
71
+ view.updatePoint('a', { symbol: { type: 'svg', path: 'M12 1 L23 12 L12 23 L1 12 Z', viewBox: [0, 0, 24, 24] } })
72
+ view.updatePoint('a', { color: null, size: null, symbol: null }) // 摘掉逐条覆盖,回落到普通规则 / pointStyle
63
73
  view.updatePoint('a', { visible: false }) // 从图上收起;写回 true 排回来
64
74
 
65
75
  // updateLine(id, patch):合并一条线路的字段;还能写 groupId / groupIndex(可配 channelId)顺带挪组
66
76
  view.updateLine('l1', { color: '#f00', width: 3 })
77
+ view.updateLine('l1', { color: '#1e88e5', width: 5, opacity: 0.8, lineType: 'dashed' }) // 逐条改颜色 / 线宽 / 透明度 / 线型
67
78
  view.updateLine('l1', { path: [[0, 0], [200, 120], [400, 200]] }) // 拐点写 path:整条折线(含首尾端点)
68
79
  view.updateLine('l1', { name: '一线', from: 'a', to: 'b' }) // 属性与端点一起改,只发一条 updateLines
69
80
  view.updateLine('l1', { groupId: 'newGroup' }) // 单独拆一组(只给指令时只发 channelGroups)
@@ -91,30 +102,69 @@ view.updateChannel('c1', { name: '东干线' }, [
91
102
  view.updateChannel('c1', {}, [{ id: 'l1', width: 4 }]) // 只传线路数组:只改成员回线的字段,数量不动
92
103
 
93
104
  // setFeatureVisible(kind, refs, visible) / hideFeatures(kind, refs) / showFeatures(kind, refs):只改 visible,不删数据
94
- const hiddenChannel = view.hideFeatures('channel', 'c1') // 'point' / 'line' / 'channel',refs 单个 / 数组 / 'selection'
105
+ const hiddenChannel = view.hideFeatures('channel', 'c1') // 'point' / 'line' / 'channel' / 'group',refs 单个 / 数组 / 'selection'
95
106
  view.showFeatures('channel', 'c1') // 成员回线、分组、拐点原样回来
96
107
  view.setFeatureVisible('point', ['a', 'b'], false) // 一批节点一次收起(一条历史、一条 batch)
97
- console.log(hiddenChannel.ok, hiddenChannel.indexes, hiddenChannel.skipped)
108
+ view.hideFeatures('line', ['l1', 'l2']) // 只收这两条线,通道声明不动
109
+ const hiddenGroup = view.hideFeatures('group', { channelId: 'c1', groupId: 'c1::2' })
110
+ view.hideFeatures('group', ['c1::2', { channelIndex: 0, groupIndex: 1 }])
111
+ // 'selection':节点 / 线路取同类型选中项;通道 / 分组按选中回线的归属整条 / 整组展开
112
+ view.hideFeatures('point', 'selection')
113
+ view.showFeatures('group', { channelId: 'c1', groupId: 'c1::2' })
114
+ console.log(hiddenChannel.indexes, hiddenGroup.ids, hiddenGroup.groups, hiddenGroup.skipped)
98
115
 
99
116
  // 整份换通道声明(与 updateChannel 的分工:这条全换、点不出某一条)
100
117
  view.updateData({ channels: [{ id: 'c1', lineIds: ['l1', 'l2'] }, { id: 'c2', from: 'a', to: 'b' }] })
101
118
 
102
119
  // patchPoints(entries, patch?):批量打补丁 —— 每条自带补丁,或一批定位 + 一份公共补丁
103
120
  view.patchPoints([{ id: 'a', name: 'A' }, { index: 1, x: 10 }])
121
+ view.patchPoints(['a', 'b'], { color: '#ff3b30', size: 14, symbol: 'square' }) // 一批节点同一份样式补丁
104
122
  view.patchPoints(['a', 'b'], { status: 1 }) // → { ok, indexes, items, count, skipped }
105
123
 
106
124
  // patchLines(entries, patch?):同 patchPoints,定位的是线路
107
125
  view.patchLines([{ id: 'l1', width: 4 }, { id: 'l2', color: '#0f0' }])
108
- view.patchLines(['l1', 'l2'], { color: '#f00' })
126
+ view.patchLines(['l1', 'l2'], { color: '#f00', width: 3, lineType: 'dotted' })
109
127
 
110
128
  // updateBackground(background?):换背景层(底色 / 图片 / GeoJSON);不传等于清掉
111
129
  view.updateBackground({ color: '#0d1b2a', image: { src: '/basemap.png' } })
130
+ view.updateBackground({
131
+ color: '#eef4fb',
132
+ geoJson: featureCollection,
133
+ // 图片也可用 corners 摆四角;GeoJSON 用 bounds 把整份几何缩放到目标矩形
134
+ geoJsonStyle: {
135
+ aspectScale: 1,
136
+ bounds: { minX: 0, minY: 0, maxX: 1000, maxY: 600 },
137
+ rotation: 15
138
+ }
139
+ })
112
140
  view.updateBackground()
113
141
 
114
142
  // getResolvedBackground() / getBackgroundLabels():背景层解析后的读数 / GeoJSON 区域文字的清单
115
143
  const background = view.getResolvedBackground()
116
144
  const backgroundLabels = view.getBackgroundLabels()
117
145
 
146
+ // beginBackgroundEdit(options?):进入组件内置的背景编辑器;没有图片 / GeoJSON 背景时只报 warning
147
+ view.beginBackgroundEdit({
148
+ aspectLocked: true, // 四角等比;false 出四角 + 四边中点
149
+ rotation: true, // 再显示一个旋转点
150
+ snapAngle: 15, // Shift 吸附 15°;0 关闭
151
+ fitView: true,
152
+ style: { color: '#2f78b7', lineWidth: 1.5, dashed: true, handleSize: 15, handleShape: 'circle' }
153
+ })
154
+
155
+ // updateBackgroundEdit(patch):改配置或几何;rotation 布尔切旋转点,数字等价 angle
156
+ view.updateBackgroundEdit({ aspectLocked: false, rotation: true, style: { handleShape: 'square' } })
157
+ view.updateBackgroundEdit({ center: [500, 300], width: 800, height: 450 })
158
+ view.updateBackgroundEdit({ angle: 30 }) // 顺时针 30°;也可写 { rotation: 30 }
159
+ view.updateBackgroundEdit({ fitView: true }) // 把整块背景放回视野
160
+
161
+ // getBackgroundEdit():当前编辑器读数;没开启返回 null
162
+ const editorState = view.getBackgroundEdit()
163
+ console.log(editorState?.backgroundType, editorState?.center, editorState?.width, editorState?.height, editorState?.rotation)
164
+
165
+ // endBackgroundEdit():移除编辑层,保留编辑后的背景,并恢复进入前的 backgroundEditing
166
+ view.endBackgroundEdit()
167
+
118
168
  // batch(fn, options?):一批改动收成一条数据变更、历史里一步
119
169
  view.batch(() => {
120
170
  view.updatePoint('a', { x: 10 })
@@ -240,6 +290,14 @@ const layoutReady = view.isLayoutReady()
240
290
  // getMinimapElement():拿鹰眼图根节点做整图拖动 / 自定义手势(没配 minimap 时为 null)
241
291
  const minimapElement = view.getMinimapElement()
242
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
+
243
301
  // getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放
244
302
  view.setMinimapZoom(4)
245
303
  view.zoomMinimapBy(1.25)
@@ -367,6 +425,13 @@ if (spacing.minDistance !== null) {
367
425
  view.updateOptions({ fitSpacing: { minDistance: spacing.minDistance } })
368
426
  }
369
427
 
428
+ // getPointsSpacing(refs):指定一组节点的整体中心距(CSS 像素;筛选 / 隐藏不影响)
429
+ const groupSpacing = view.getPointsSpacing(['a', { index: 2 }, { id: 'b' }])
430
+ if (groupSpacing.nearest) {
431
+ console.log('组内最近', groupSpacing.nearest.from.id, groupSpacing.nearest.to.id, groupSpacing.minDistance, 'px')
432
+ }
433
+ console.log('组内最远', groupSpacing.maxDistance, 'px', '世界距离', groupSpacing.maxDistance / view.getScale())
434
+
370
435
  // forEachPointInView(rect, visit):只遍历视口里的节点,不建中间数组;回调返回 false 提前结束
371
436
  view.forEachPointInView(view.getViewRect(), (index, item, x, y, radius) => {
372
437
  readout.push(`${item.name}:半径 ${radius.toFixed(1)}px`)
@@ -385,6 +450,49 @@ const box = view.getElementBounds(view.getSelections())
385
450
  const state = view.getViewState()
386
451
  ```
387
452
 
453
+ #### 单条 / 多条要素详情
454
+
455
+ 单条查询用 `getPoint(ref)` / `getLine(ref)` / `getChannel(ref)`。`ref` 可以是 id、数组下标、`{ id }`、`{ index }`,节点与线路还可以写 `{ type, index }`;查不到统一返回 `null`,不会抛错。返回值把「数据里那一份」与「当前画出来的读数」放在一起:节点有 `item` / `coordinate` / `screen` / `radius`,线路有 `item` / `path` / `bounds` / `color` / `width`,通道有 `item` / `lineIndexes` / `skeleton` / `offsets` / `paths` / `from` / `to`。
456
+
457
+ 多条查询用 `listPoints(options)` / `listLines(options)`,返回数组,查不到就是空数组。两个方法支持同一套条件:`indexes` 限定下标,`inView` 按当前视口或世界矩形筛,`limit` 截断,`withScreen: true` 补屏幕坐标,`visibleOnly: false` 连当前被隐藏 / 过滤的要素也列出来(缺省只列真正画在图上的)。节点项还带 `x` / `y` / `radius` / `layerRank`;线路项带解析后的 `path` / `bounds` / `color` / `width`,通道回线另外带 `channelId` / `groupId` / `channelIndex`。
458
+
459
+ ```js
460
+ // ① 单个节点 / 线路 / 通道:按 id 或下标都行
461
+ const point = view.getPoint('station-a') // 也认 0、{ index: 0 }、{ type: 'point', index: 0 }
462
+ const line = view.getLine('line-a-b')
463
+ const channel = view.getChannel('channel-a')
464
+
465
+ if (point) {
466
+ console.log(point.item, point.coordinate, point.screen, point.radius)
467
+ }
468
+ if (line) {
469
+ console.log(line.item, line.path, line.bounds, line.color, line.width)
470
+ }
471
+ if (channel) {
472
+ console.log(channel.item, channel.lineIndexes, channel.skeleton, channel.paths, channel.from, channel.to)
473
+ }
474
+
475
+ // ② 多个节点 / 线路:先按 id 找下标,再交给 listPoints / listLines
476
+ const pointIds = ['station-a', 'station-b']
477
+ const pointIndexes = pointIds.map(id => view.getPoint(id)?.index).filter(index => index !== undefined)
478
+ const points = view.listPoints({ indexes: pointIndexes, withScreen: true, visibleOnly: false })
479
+
480
+ const lineIds = ['line-a-b', 'line-b-c']
481
+ const lineIndexes = lineIds.map(id => view.getLine(id)?.index).filter(index => index !== undefined)
482
+ const lines = view.listLines({ indexes: lineIndexes, withScreen: true, visibleOnly: false })
483
+
484
+ // 也可以不按 id:列屏幕内前 20 条,或列全部但限制条数
485
+ const inViewPoints = view.listPoints({ inView: true, limit: 20, withScreen: true })
486
+ const someLines = view.listLines({ limit: 20 })
487
+
488
+ // ③ 多条通道:getChannel 逐个查;要整份几何再用 getChannels()
489
+ const channelIds = ['channel-a', 'channel-b']
490
+ const channels = channelIds.map(id => view.getChannel(id)).filter(Boolean)
491
+ const allChannelGeometries = view.getChannels()
492
+ ```
493
+
494
+ 查询详情不会改样式、选中集或相机;得到的 `item` 就是数据里那一份对象,`path` / `bounds` / `color` / `width` 是当前相机与样式链算出来的读数。查一批要素的包围盒用 `getElementBounds(keys)`;如果只是想知道画布上某块矩形里有什么,用 `getFeaturesInRect(rect)` 或 `pick(x, y)`,不必先列全量再自己筛。
495
+
388
496
  #### 选中、复制与合并
389
497
 
390
498
  ```js
@@ -438,6 +546,12 @@ view.selectInBox({ x: 40, y: 40, width: 320, height: 200 })
438
546
  view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['line', 'channel'] })
439
547
  view.selectInBox({ x: 40, y: 40, width: 320, height: 200 }, { selectionKinds: ['lineEndpoints', 'channelEndpoints'] }) // 两端节点都在框内才命中
440
548
 
549
+ // selectionBoxPersistent / clearSelectionBox() / getSelectionBox():框选后保留矩形,并手动收起
550
+ view.updateOptions({ boxSelect: true, draggable: true, selectionBoxPersistent: true })
551
+ view.selectInBox({ x: 40, y: 40, width: 320, height: 200 })
552
+ const retainedBox = view.getSelectionBox() // 画布 CSS 像素;没有框时为 null
553
+ view.clearSelectionBox() // 只收框,不改选中集
554
+
441
555
  // selectionKinds:框选与 Ctrl / multiSelect 点击的类型过滤(数组任意组合;'all' / true 全开,false / null 全关)
442
556
  view.updateOptions({ selectionKinds: ['point', 'line'] }) // 只收节点与通道外线路
443
557
  view.updateOptions({ selectionKinds: 'channel' }) // 只收通道回线(再按 channelSelectMode 摊开)
@@ -550,6 +664,12 @@ const groups = view.getChannelGroups('c1')
550
664
  const allGroups = view.getChannelGroups()
551
665
  const groupWaypoints = view.getChannelGroupWaypoints('c1', 'c1::2')
552
666
 
667
+ // channelHiddenLineLayout:隐藏回线 / 分组后保留原车道,或让剩余回线重新居中
668
+ view.updateOptions({ channelHiddenLineLayout: 'center' })
669
+ view.hideFeatures('line', 'l1') // 双回线剩一条时回到中线
670
+ view.updateOptions({ channelHiddenLineLayout: 'preserve' }) // 改回保留原槽位(缺省)
671
+ view.showFeatures('line', 'l1')
672
+
553
673
  // getChangedChannels(options?) / clearChangedChannels(refs?):通道变化对账
554
674
  view.updateChannel('c1', { name: '东干线' })
555
675
  view.addChannelGroupWaypoint('c1', 'c1', [200, 120]) // 拐点变化入账(单组通道的组 id 就是通道 id)
@@ -1108,7 +1228,11 @@ view.off('dataChange') // 摘掉这个事件的全部回调
1108
1228
  view.off('dataChange', sync) // 只摘掉这一个(返回摘掉几条)
1109
1229
 
1110
1230
  // use(plugin):运行时挂插件(立即执行一次,返回卸载函数)
1111
- const dispose = view.use((view, context) => ({ destroy() { /* 插件自己的清理;context.apiVersion 当前为 1 */ } }))
1231
+ const dispose = view.use((view, context) => {
1232
+ context.on('selectChange', payload => console.log('选中变了:', payload.count))
1233
+ context.registerCommand('toggleInspector', () => view.updateOptions({ tooltip: false }))
1234
+ context.onCleanup(() => { /* context 未管理的资源在这里收尾;context.apiVersion 当前为 1 */ })
1235
+ })
1112
1236
  dispose()
1113
1237
 
1114
1238
  // registerCommand(name, handler) / executeCommand(name, payload?) / listCommands():实例级业务命令
@@ -38,7 +38,7 @@ canvas.addEventListener('contextmenu', (event) => {
38
38
 
39
39
  `listPoints()` 用的就是引擎画点那一份可见判定(旧版单独暴露的 `getVisiblePoints()` 并进了这一条):`radius` / 图层号 / 屏幕坐标这些几何读数都在它给的条目里。`focusOn()` 只移动相机、不改数据:缺省只居中、比例不动,`{ zoom: true }` 才按目标范围适配,`{ select: true }` 顺带把这批选上,传一批目标(数组)就是整体居中。
40
40
 
41
- `getPointStyle()` / `getLineStyle()` 给的是**当前解析结果**:节点看尺寸 / 图标 / 层级,线路看原始线宽 / RGBA / 透明度 / 层级;悬停 / 选中样式、整体倍率、热力倍率都不算在里面。逐条自己写死的字段优先于规则与全局样式,所以用它们对账「预览里多大、落地后多大」或者鹰眼图的小图尺寸时,不需要自己重跑样式链。
41
+ `getPointStyle()` / `getLineStyle()` 给的是**当前解析结果**:节点看尺寸 / 图标 / 层级,线路看原始线宽 / RGBA / 透明度 / 层级;悬停 / 选中样式、整体倍率、热力倍率都不算在里面。普通规则高于全局样式但低于逐条字段,`override: true` 的规则则压过逐条字段,所以用它们对账「预览里多大、落地后多大」或者鹰眼图的小图尺寸时,不需要自己重跑样式链。
42
42
 
43
43
  只要「屏幕里这一片」的节点时用 `forEachPointInView()`:它按数据顺序逐个回调 `(index, item, x, y, radius)`,不建中间数组,回调返回 `false` 可以提前结束 —— 放大看一小片时不会去碰整份数据(十万级数据下,面板读数、导出与排序用上面那两条,按视口画自己的东西用这一条):
44
44
 
@@ -58,7 +58,7 @@ view.forEachPointInView(view.getViewRect(), () => {
58
58
 
59
59
  ### 拖拽落点预览(拖到一半就知道会长成什么样)
60
60
 
61
- 从看板 / 列表往画布拖东西时,把「松手之后会长成什么样」先画到地图上:`setDropPreview()` 收的是**准备落地的那一份字段**(与 `addPoint` / `addLine` 的 `item` 同一份),走的是与真实要素完全相同的样式链(规则 -> 全局样式 -> 这一份字段),所以预览里的图标尺寸 / 颜色 / 符号与落地之后一模一样 —— 不是照着样子仿的,是同一个解析结果:
61
+ 从看板 / 列表往画布拖东西时,把「松手之后会长成什么样」先画到地图上:`setDropPreview()` 收的是**准备落地的那一份字段**(与 `addPoint` / `addLine` 的 `item` 同一份),走的是与真实要素完全相同的样式链(普通规则 -> 全局样式 -> 这一份字段 -> `override` 规则),所以预览里的图标尺寸 / 颜色 / 符号与落地之后一模一样 —— 不是照着样子仿的,是同一个解析结果:
62
62
 
63
63
  ```js
64
64
  // dragover:每帧只算一次落点,预览跟着指针走(数据一个字节都不动)
@@ -59,7 +59,7 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
59
59
  - **标签层级、节点遮挡与缩放字号**:跟随节点层级(`followNodeZIndex`)的点标签需要知道哪些更高层节点压住了自己的盒子。覆盖层为此按需建立一张 64px 屏幕网格,只收有效 `zIndex > 0` 的视口内节点;引擎没有任何非 0 节点层时整趟扫描直接跳过。判定只在标签真正参与排版时发生,被盖住的标签不占避让网格、不计 `maxCount`;`updatePoint` / `patchPoints` 会比对被改节点的层号、屏幕半径与坐标,必要重排时被改节点自己没标签也会补一次。`zoomScale` 的字号计算每轮只问一次相机,缩放指数共用 `Math.pow` 结果,交互期按 0.1px 量化后只改变换,停手再按真实字号校正盒子与避让,避免缩放过程中反复重排整屏标签。Canvas 标签仅在同层出现倒序时才排序,已有序时直接复用上一轮顺序。
60
60
  - **选中线路抬层**:`selectionOnTop` 没有把选中线路插进普通的逐线循环,而是把线路实例、阴影、圆头、高亮与热力光晕各自拆成「普通段 + 抬层段」;只在抬层集合真的变化时重铺线路批次,集合内容相同(只换了一份引用)不会把十万条线重算一遍。两端节点单独进抬层点集合,绘制次序因此固定为普通节点 → 抬层线路 → 抬层端点,不需要为每条线动态调整节点层号。
61
61
  - 拾取与框选共用网格索引:点走按数据范围重建的均匀网格,线走随线段重建的 `createSegmentGrid`,每次交互只遍历光标附近的候选,耗时与总量基本脱钩,`getStats().pickMs` 是随手可读的实测值(10 万规模下热态约 0.2 ~ 0.3 ms / 次)。线段按格线步进登记进它真正穿过的格子(早期按包围盒填格,一条斜跨全图的线会占掉上万格、只好整条丢进「超长线」单,而十万条线里绝大多数都是这种,于是每次拾取都要重算十万条的距离);剩下的长线只记一条包围盒,查询时按盒子先筛一遍。10 万点 / 10 万线的合成数据里(随机配对、长线极密),500 次 `pick` 从 3530 ms 降到 164 ms(0.3 ms / 次)。
62
- - 拖动时「哪些线跟着这个点走」按需建索引:`collectDragLinks` 从前每次都把全部线路的端点与组内成员逐个比一遍,10 万点 / 12 万线下这一步占总 CPU 的 27%,是拖动路径上唯一还按总量线性付费的循环。现在线路超过 `LINE_BIND_TOLERANCE_SCAN_LIMIT`(2048)条时改用一张线路归属表(按 `from` / `to` 的 id 与端点坐标建,整份换数据 / 增删线 / 改绑定才重建,只改坐标就地把那一条换掉),只对命中的候选逐字段判定 —— 十万级拖动降到 CPU 的 1% 以下,单条 `updatePoint`(改名这类不动几何的改动)55 → 3.3 ms(稳态 0.02 ms)。线路不多时保留原来的浮点容差扫描(行为与从前逐字不差);索引那条路只认 id 绑定与坐标精确相等 —— 这个量级坐标都是同一份来源算出来的,容差是给手写数据留的余量。
62
+ - 拖动时「哪些线跟着这个点走」按需建索引:`collectDragLinks` 从前每次都把全部线路的端点与组内成员逐个比一遍,10 万点 / 12 万线下这一步占总 CPU 的 27%,是拖动路径上唯一还按总量线性付费的循环。现在线路超过 `LINE_BIND_TOLERANCE_SCAN_LIMIT`(2048)条时改用一张线路归属表(按 `from` / `to` 的 id 与端点坐标建,整份换数据 / 增删线 / 改绑定才重建,只改坐标就地把那一条换掉),只对命中的候选逐字段判定 —— 十万级拖动降到 CPU 的 1% 以下,单条 `updatePoint`(改名这类不动几何的改动)55 → 3.3 ms(稳态 0.02 ms)。线路不多时保留原来的浮点容差扫描(行为与从前逐字不差);索引那条路只认 id 绑定与坐标精确相等 —— 这个量级坐标都是同一份来源算出来的,容差是给手写数据留的余量。2026-10-29 又把点 / 线 id 表拆开,并让无坐标 / id 变化的 `updatePoint` 不再收集跟随线路或重排通道:10 万点 / 10 万线下 `repeat:updatePoint500:4` 从改前 **62.3 / 6.8 / 4.7 / 4.3 ms** 降到三次复测的 **15.0 ~ 15.4 / 1.8 ~ 2.1 / 1.0 ~ 1.3 / 0.8 ~ 0.9 ms**(首段含首次建点 id 表),500 次批量改名的稳态约 1 ms。
63
63
  - 视口里的线路改走线段网格:线路标签排版这类「只看屏幕里这一片」的路径(`forEachLineInView`)原先要把每条线的包围盒过一遍,10 万级 + 标签的 CPU profile 里它占 47 ~ 58%。现在线路超过 `LINE_VIEW_GRID_MIN_LINES`(2048)条、且估计出来的候选线段只占总数的 1/32 以内(`LINE_VIEW_GRID_COST_SHARE`)时改查拾取用的那张线段网格,再逐条按包围盒确认;候选排回要素下标顺序,脏线与没有线段的退化线单独补一遍,访问顺序与判定口径与整份扫描逐行相同。网格脏着(正在拖线 / 改线)或线路不多时照旧整份扫描。
64
64
  - 视口里的节点遍历也先查点网格:`forEachPointInView` 在节点超过 `POINT_VIEW_GRID_MIN_POINTS`(2048)、点网格不脏且估计候选不足总数的 1/32(`POINT_VIEW_GRID_COST_SHARE`)时,只查视口覆盖的格子、把候选排回数据顺序再逐个精确判定;网格脏着(刚改过数据 / 拖拽中)或视口几乎铺满时仍走整份扫描,避免为了省一趟遍历先付出建网格和排序的成本。`createPointGrid.estimate(..., stopAfter)` 与查询共用同一套格子口径,传 `stopAfter` 时一旦超过阈值立即返回,只判断候选是否过密时不必扫完视口里的全部格子;框选线路则复用已有的 `forEachLineInView`,不再为每条候选线复制路径 / 包围盒 / 颜色快照。
65
65
  - 数据包围盒读数改为缓存:整份解析本来已经顺路算出点与线的范围,`getBounds()` 直接接住这一份;定向改点 / 改线、拖拽、追加要素与复制预览只把缓存标脏,下一次真被问到时才用点坐标和每条线的 `line.bounds` 重建,不再逐顶点重扫。`getBounds()` 每次返回独立副本,调用方就地修改不会污染下一次读数;追加到原范围之外、拖出边界这类增量也会立即失效。10 万点 + 10 万线(每条 10 顶点)实测连续 10 次 `getBounds()`:优化前 **5.2 ~ 6.6 ms / 次**,优化后 **0.00 ms / 次**;改一个点后缓存重建一次 **3.6 ms**,读数正确。
@@ -159,6 +159,18 @@ const stats = view.getStats() // clusterCount / clusterPointCount /
159
159
 
160
160
  同一轮加的 `graphics` 扩展图形层是**独立 DOM 覆盖层**:只读相机做投影,增删改不触发引擎重排;锚点随相机平移 / 缩放,尺寸默认保持屏幕像素,`scaleWithView: true` 才按相机比例缩放。图形位于画布之上、标签之下,不参与拾取;如果只是少量线框 / 图片 / Canvas,开销与这几十个 DOM 节点相当,不会把十万级要素的渲染批次重新排一遍。
161
161
 
162
+ ### 保留框 DOM 跟随与拾取热路径
163
+
164
+ - **div 保留框只写必要属性**:保留框同时保存屏幕矩形与世界范围。相机平移 / 缩放后,引擎在渲染帧从世界范围重投影;DOM 位置只写 `transform: translate3d(...)`,宽高只在缩放或拖动改变时更新,颜色 / 线型 / 类名按样式版本缓存,平移过程中不再重复写。`getSelectionBox()` 与框内命中判断也先同步到当前相机,所以读数、DOM 与框内拖动不会停在旧位置。
165
+ - **拾取去掉逐候选临时对象**:`pick()` / `pickDraggablePoint()` 不再为每个候选调用 `projectPoint()` 造数组,也不再逐个 `Math.hypot()`;相机中心、比例和半视口在循环外取一次,候选内联投影后直接比较平方距离。聚合圆同样复用这一份投影读数,只有最终命中才构造 `screen` 与 `distance`。
166
+ - **10 万点 / 10 万线基准**(`node scripts/bench-scale.mjs --points=100000 --lines=100000 --gpu=true`,无头 Chrome + ANGLE / AMD 集显):改前单次基线为平移 p50 / p95 `6.9 / 33.8 ms`、缩放 `18.4 / 25.2 ms`、拾取 p95 / avg `0.2 / 0.06 ms`、`refresh()` `92.9 ms`;改后连续 3 次为平移 `7 / 14.9 ~ 22.4 ms`、缩放 `18.2 ~ 19.0 / 24.0 ~ 24.9 ms`、拾取 `0.2 ~ 0.3 / 0.05 ~ 0.07 ms`、`refresh()` `88.5 ~ 135.2 ms`。无头环境的帧节拍、GC 与合成抖动会盖过这几项小优化,不能把平移 / 刷新差值全部归因于本轮;能确定的是 DOM 写次数与拾取临时分配减少了,拾取仍稳定在亚毫秒级。
167
+
168
+ ### 规则字段预计算与 override 分层
169
+
170
+ - `compileRules` 在配置变化时预计算每条规则真正写到的样式字段,逐要素合并只遍历这几个键;没有 `override: true` 规则时,`RULE_HAS_OVERRIDE` 私有标记让热路径跳过第二份暂存清理。
171
+ - 普通规则与 override 规则仍共用零分配 scratch,优先级变化只发生在取值层:状态 / 热力图 > override > 要素自身 > 普通规则 > 全局样式。
172
+ - 10 万要素、4 条规则、9 个样式字段的 Node 微基准(同进程交替取中位):规则合并 `21.82 → 17.69 ms`,约省 `4.13 ms`(约 `1.23×`),逐项校验和一致。这条量的是解析期规则合并,不是浏览器帧耗时;10 万级页面仍以合批、增量上传与标签预算为主。
173
+
162
174
  ## 渲染后端(WebGPU 开关)
163
175
 
164
176
  `renderer` 把「换绘制后端」收成一个开关:`'auto'`(缺省)/ `'webgl2'` / `'webgpu'`,布尔值 `true` / `false` 等价于 `'webgpu'` / `'webgl2'`。