gis-view-webgl2 0.1.4 → 0.1.5

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/README.md +7 -7
  2. package/dist/index.cjs +1 -1
  3. package/dist/index.d.ts +140 -20
  4. package/dist/index.js +1 -1
  5. package/docs/AI/345/257/271/350/257/235/346/261/207/346/200/273.md +45 -0
  6. package/docs/GisView.md +53 -53
  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 +36 -0
  8. package/docs/demos/ConnectDemo.vue +114 -3
  9. package/docs/demos/MinimapDemo.vue +62 -16
  10. package/docs/demos/api-index.js +15 -14
  11. package/docs/demos/gis-view-demo/GisViewDemoStage.vue +25 -16
  12. package/docs/demos/gis-view-demo/useGisViewDemo.js +27 -16
  13. package/docs/index.html +3 -3
  14. package/docs/internals/13-/351/271/260/347/234/274/345/233/276.md +6 -5
  15. package/docs/internals/16-01-/344/274/230/345/214/226/344/270/216/345/217/226/350/210/215-/344/270/212.md +5 -3
  16. 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 +12 -0
  17. package/docs/manual/03-/351/205/215/347/275/256/351/241/271.md +11 -3
  18. package/docs/manual/09-/351/200/232/351/201/223/344/270/216/345/244/232/345/233/236/347/272/277.md +1 -1
  19. package/docs/manual/10-/351/200/232/351/201/223/347/274/226/350/276/221/344/270/216/346/226/260/345/242/236.md +423 -3
  20. package/docs/manual/14-/350/247/206/345/233/276/344/270/216/347/255/233/351/200/211.md +79 -28
  21. package/docs/manual/16-/345/256/236/344/276/213/346/226/271/346/263/225/346/200/273/350/241/250.md +6 -5
  22. package/docs/manual/17-/345/217/202/346/225/260/351/200/237/346/237/245.md +5 -4
  23. package/docs/manual/18-/351/200/220/346/226/271/346/263/225/347/244/272/344/276/213.md +117 -5
  24. 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 +5 -2
  25. package/docs/steps/README.md +5 -5
  26. package/docs/steps/selftest-demos.html +149 -5
  27. package/docs/steps/selftest-docs.html +3 -3
  28. package/docs/steps/selftest-full.html +315 -12
  29. package/docs/steps/selftest-perf.html +39 -1
  30. package/package.json +1 -1
@@ -7,8 +7,8 @@
7
7
  * 源码或文档改过之后重跑一遍;`node docs/steps/check-doc-coverage.mjs` 会对账。
8
8
  */
9
9
 
10
- /** { members: 256, methods: 253, properties: 3, options: 96, sections: 16 } */
11
- export const API_SUMMARY = {"members":256,"methods":253,"properties":3,"options":96,"sections":16}
10
+ /** { members: 257, methods: 254, properties: 3, options: 96, sections: 16 } */
11
+ export const API_SUMMARY = {"members":257,"methods":254,"properties":3,"options":96,"sections":16}
12
12
 
13
13
  /** 实例成员,按 docs/GisView.md「参数速查」的分组排 */
14
14
  export const API_SECTIONS = [
@@ -61,6 +61,7 @@ export const API_SECTIONS = [
61
61
  { name: "getViewport", kind: "method", params: "无", returns: "{ cssWidth, cssHeight, pixelRatio, displayScale }:画布 CSS 尺寸、设备像素比与渲染分辨率比例", doc: "/** * 当前视口。", examples: [{"group":"相机与视图","code":"// getBounds() / getViewRect() / getViewport():数据范围 / 视口世界矩形 / 画布尺寸读数\nconst dataBounds = view.getBounds()\nconst viewRect = view.getViewRect()\nconst { cssWidth, cssHeight, pixelRatio } = view.getViewport()\n","from":"header"}] },
62
62
  { name: "getViewRect", kind: "method", params: "无", returns: "GisViewBounds", doc: "当前可视范围的世界坐标矩形;自建缩略图 / 坐标读数用它换算视口框,内置鹰眼图见「鹰眼图」", examples: [{"group":"相机与视图","code":"// getBounds() / getViewRect() / getViewport():数据范围 / 视口世界矩形 / 画布尺寸读数\nconst dataBounds = view.getBounds()\nconst viewRect = view.getViewRect()\nconst { cssWidth, cssHeight, pixelRatio } = view.getViewport()\n","from":"header"}] },
63
63
  { name: "isLayoutReady", kind: "method", params: "无", returns: "boolean", doc: "大数据发散渲染(progressiveInit)是否已经铺完;没铺完时标签与拾取只看得到已经画出去的那部分", examples: [{"group":"相机与视图","code":"// isLayoutReady():容器量到尺寸、首批布局完成没有\nconst layoutReady = view.isLayoutReady()\n","from":"header"}] },
64
+ { name: "getMinimapElement", kind: "method", params: "无", returns: "HTMLElement | null 鹰眼图根节点(没配 minimap 时为 null);外部整图拖动 / 自定义手势用它,配合 minimap.container 可挂到外部宿主", doc: "鹰眼图根节点(没配 minimap 时为 null):接整图拖动或自定义手势;配合 minimap.container 挂到外部宿主时拿到的仍是同一根节点", examples: [{"group":"相机与视图","code":"// getMinimapElement():拿鹰眼图根节点做整图拖动 / 自定义手势(没配 minimap 时为 null)\nconst minimapElement = view.getMinimapElement()\n","from":"header"}] },
64
65
  { name: "getMinimapZoom", kind: "method", params: "无", returns: "number", doc: "读 / 写鹰眼图自身的倍数:set 夹在 minimap.minZoom ~ maxZoom 之间,zoomMinimapBy 以画布 CSS 像素 origin 为锚点缩放(缺省小图中心),没配 minimap 时是空操作", examples: [{"group":"相机与视图","code":"// getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放\nview.setMinimapZoom(4)\nview.zoomMinimapBy(1.25)\nconst minimapZoom = view.getMinimapZoom()\n","from":"header"}] },
65
66
  { name: "setMinimapZoom", kind: "method", params: "zoom: number 鹰眼图的目标倍数(被 minimap.minZoom / maxZoom 夹住)", returns: "this", doc: "读 / 写鹰眼图自身的倍数:set 夹在 minimap.minZoom ~ maxZoom 之间,zoomMinimapBy 以画布 CSS 像素 origin 为锚点缩放(缺省小图中心),没配 minimap 时是空操作", examples: [{"group":"相机与视图","code":"// getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放\nview.setMinimapZoom(4)\nview.zoomMinimapBy(1.25)\nconst minimapZoom = view.getMinimapZoom()\n","from":"header"}] },
66
67
  { name: "zoomMinimapBy", kind: "method", params: "factor: number 倍数;origin?: [x, y] 以小图里的哪个点为中心(缺省小图中心)", returns: "this", doc: "读 / 写鹰眼图自身的倍数:set 夹在 minimap.minZoom ~ maxZoom 之间,zoomMinimapBy 以画布 CSS 像素 origin 为锚点缩放(缺省小图中心),没配 minimap 时是空操作", examples: [{"group":"相机与视图","code":"// getMinimapZoom() / setMinimapZoom(zoom) / zoomMinimapBy(factor, origin?):鹰眼图缩放\nview.setMinimapZoom(4)\nview.zoomMinimapBy(1.25)\nconst minimapZoom = view.getMinimapZoom()\n","from":"header"}] },
@@ -176,7 +177,7 @@ export const API_SECTIONS = [
176
177
  { name: "getWaypoints", kind: "method", params: "lineIndex?: number 只看这一条线路(不传给全部)", returns: "GisViewWaypointInfo[]", doc: "当前显示中的拐点(受 waypointVisibility 影响),带坐标与选中态", examples: [{"group":"拐点与通道","code":"// getWaypoints(lineIndex?) / getParallelGroups():拐点清单 / 新增拐点集合\nconst waypoints = view.getWaypoints()\nconst waypointsOfLine = view.getWaypoints(0) // 只看这一条线(线路下标)\nconst parallelGroups = view.getParallelGroups()\n","from":"header"}] },
177
178
  { name: "getParallelGroups", kind: "method", params: "无", returns: "GisViewParallelGroup[]", doc: "新增拐点集合:被加过 / 拖过拐点的通道与线路,结构与 test.json 的 parallel_groups 一致", examples: [{"group":"拐点与通道","code":"// getWaypoints(lineIndex?) / getParallelGroups():拐点清单 / 新增拐点集合\nconst waypoints = view.getWaypoints()\nconst waypointsOfLine = view.getWaypoints(0) // 只看这一条线(线路下标)\nconst parallelGroups = view.getParallelGroups()\n","from":"header"}] },
178
179
  { name: "getChannels", kind: "method", params: "无", returns: "GisViewChannelInfo[]", doc: "通道运行时状态:骨架折线、每条回线的偏移、推导出来的折线路径与间距;分组之后一个通道的每个分组各一条,带 channelId / channelIndex(id 是分组 id)", examples: [{"group":"拐点与通道","code":"// getChannels() / getChannelMembers() / getChannelsInView():通道全量几何 / 只成员与偏移 / 视口里的通道\nconst channelGeometries = view.getChannels()\nconst channelMembers = view.getChannelMembers()\nconst channelsInView = view.getChannelsInView()\n","from":"header"}] },
179
- { name: "getChannelMembers", kind: "method", params: "无", returns: "GisViewChannelMembers[]:每条通道(分组之后每组各一条)的 { channelId, channelIndex, lineIndexes, offsets } —— 只要成员下标与偏移、不复制骨架 / 折线,自建覆盖层按通道分流绘制用它;整份几何(含 paths)用 getChannels()", doc: "通道成员表:每个通道的成员下标与各自的偏移(不复制几何),需要按通道分流绘制时用它(内置鹰眼图的「一个通道只画一根」就按它挑中间那条)", examples: [{"group":"拐点与通道","code":"// getChannels() / getChannelMembers() / getChannelsInView():通道全量几何 / 只成员与偏移 / 视口里的通道\nconst channelGeometries = view.getChannels()\nconst channelMembers = view.getChannelMembers()\nconst channelsInView = view.getChannelsInView()\n","from":"header"}] },
180
+ { name: "getChannelMembers", kind: "method", params: "无", returns: "GisViewChannelMembers[]:每条通道(分组之后每组各一条)的 { channelId, channelIndex, lineIndexes, offsets } —— 只要成员下标与偏移、不复制骨架 / 折线,自建覆盖层按通道分流绘制用它;整份几何(含 paths)用 getChannels()", doc: "通道成员表:每个通道的成员下标与各自的偏移(不复制几何),需要按通道分流绘制时用它(内置鹰眼图的「一个通道只画一根」按它取当前可见成员,再逐顶点平均成居中代表线)", examples: [{"group":"拐点与通道","code":"// getChannels() / getChannelMembers() / getChannelsInView():通道全量几何 / 只成员与偏移 / 视口里的通道\nconst channelGeometries = view.getChannels()\nconst channelMembers = view.getChannelMembers()\nconst channelsInView = view.getChannelsInView()\n","from":"header"}] },
180
181
  { name: "getChannelGroups", kind: "method", params: "channelId?: string | number 只看这一条通道(不传给全部)", returns: "GisViewChannelGroupInfo[]", doc: "通道分组现状:每组的成员(下标 + 线路 id)、骨架与可以直接写回声明的中间拐点(path);不传 channelId 返回所有分组,见「通道分组」", examples: [{"group":"拐点与通道","code":"// getChannelGroups(channelId?) / getChannelGroupWaypoints(channelId?, groupId?):分组现状 / 某一组的拐点\nconst groups = view.getChannelGroups('c1')\nconst allGroups = view.getChannelGroups()\nconst groupWaypoints = view.getChannelGroupWaypoints('c1', 'c1::2')\n","from":"header"}] },
181
182
  { name: "getChannelGroupWaypoints", kind: "method", params: "channelId?: string | number 通道 id(不传取「当前分组」所在的通道);groupId?: string | number 分组 id(不传取这条通道的第一组)", returns: "GisViewChannelGroupWaypoints | null:整组共用的 path(给后端那一份)+ lines[] 每条回线偏移后的 path / waypoints", doc: "一个分组的拐点现状:整组共用的骨架与中间拐点(path,给后端就是这一份)+ 组里每条回线按各自车道偏移后的折线与拐点(lines[].waypoints);channelId 不传取「当前分组」所在的通道、groupId 不传取第一组,见「分组拐点」", examples: [{"group":"拐点与通道","code":"// getChannelGroups(channelId?) / getChannelGroupWaypoints(channelId?, groupId?):分组现状 / 某一组的拐点\nconst groups = view.getChannelGroups('c1')\nconst allGroups = view.getChannelGroups()\nconst groupWaypoints = view.getChannelGroupWaypoints('c1', 'c1::2')\n","from":"header"}] },
182
183
  { name: "getLineChannel", kind: "method", params: "ref: GisViewFeatureRef 线路定位(id、lines 下标或 { id } / { index })", returns: "{ lineIndex, channelId, channelIndex, channel, groupId, group };这条线不在任何通道里时 null", doc: "这条线路属于哪条通道:getLineChannel('line-a')(id)/ getLineChannel(12)(lines 下标)给 { lineIndex, channelId, channelIndex, channel, groupId, group },不在通道里返回 null。点了一条回线想知道它归哪条通道 / 哪一组时用它,不必自己扫通道定义", examples: [{"group":"拐点与通道","code":"// getLineChannel(ref):这条线属于哪条通道 / 哪个分组(独立线路给 null)\nconst channelOfLine = view.getLineChannel('l1')\n","from":"header"}] },
@@ -193,21 +194,21 @@ export const API_SECTIONS = [
193
194
  { name: "addChannelGroupWaypoint", kind: "method", params: "channelId 通道 id;groupId 分组 id;coordinate 新拐点世界坐标", returns: "this", doc: "给某个分组加拐点、把这一组单独拉走:不给坐标就按默认位置放一个(第 n 组沿法线往外让 n 档),其余分组一根拐点都不动,见「分组拐点」", examples: [{"group":"拐点与通道","code":"// addChannelGroupWaypoint(channelId, groupId, coordinate) / removeChannelGroupWaypoint(channelId, groupId, pathIndex)\nview.addChannelGroupWaypoint('c1', 'c1::2', [200, 120])\nview.removeChannelGroupWaypoint('c1', 'c1::2', 1)\n","from":"header"}] },
194
195
  { name: "removeChannelGroupWaypoint", kind: "method", params: "channelId / groupId / pathIndex 要删的骨架顶点下标", returns: "this", doc: "删掉某个分组的拐点(缺省删最后一个)", examples: [{"group":"拐点与通道","code":"// addChannelGroupWaypoint(channelId, groupId, coordinate) / removeChannelGroupWaypoint(channelId, groupId, pathIndex)\nview.addChannelGroupWaypoint('c1', 'c1::2', [200, 120])\nview.removeChannelGroupWaypoint('c1', 'c1::2', 1)\n","from":"header"}] },
195
196
  { name: "runChannelAction", kind: "method", params: "actionId: GisViewChannelActionId 动作名('select-group' / 'deselect-group' / 'add-waypoint' / 'remove-waypoint' / 'split' / 'merge',见 getChannelContext().actions);options?: GisViewChannelActionOptions 动作自己的参数(groupId / lineId / coordinate…)", returns: "GisViewChannelContext | null", doc: "执行右键交互框里的动作:select-group / deselect-group / add-waypoint / remove-waypoint / split / merge(options 带 channelId / groupId / coordinate / pathIndex / size),返回执行之后的上下文", examples: [{"group":"拐点与通道","code":"// setCurrentChannelGroup(channelId, groupId) / runChannelAction(actionId, options?):当前分组与动作\nview.setCurrentChannelGroup('c1', 'c1::2')\nview.setCurrentChannelGroup('c1', null) // 不选中\nview.runChannelAction('select-group', { groupId: 'c1::2' })\nview.runChannelAction('split', { lineId: 'l2' })\n","from":"header"}] },
196
- { name: "beginAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 草稿配置:from(预指定起点,下一步只选终点)/ kind / circuits / autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段)/ routing / path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式)/ handoff(交接模式)/ select(点中的节点顺手选中,走 onSelect 那一套)/ originalEvent", returns: "this(只负责那条示例假通道,真正新增走 addChannel)", doc: "进入「新增通道」:缺省点两个节点选起点 / 终点,或由 options.from 预指定起点、只需再点终点;options 还支持 kind('channel' 新建通道 / 'line' 新建独立线路,缺省 'channel')/ circuits(一次加几条,缺省 1)/ autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段,连同线路一起新建)/ routing('straight' / 'orthogonal')/ path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式)/ handoff(交接模式:ready 发完收起草稿,自己调 addChannel 落地)/ select(点中的节点顺手选中)/ originalEvent,见「新增通道」", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
197
- { name: "getAddChannelDraft", kind: "method", params: "无", returns: "GisViewChannelDraftPayload | null", doc: "当前新增通道草稿(两个节点的信息与可选方案),没有草稿时为 null", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
198
- { name: "updateAddChannelStyle", kind: "method", params: "patch?: GisViewChannelDraftStyle | null 字段级合并的预览样式(箭头位置 / 多回线间距 / 自绘等);传 null 回到全局 channelDraftStyle", returns: "this", doc: "字段级合并当前草稿的预览样式(color / width / dot / dotColor / arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw);传 null 回到全局 channelDraftStyle,没有草稿时什么也不做", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
199
- { name: "continueAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 开下一段草稿(写法与 beginAddChannel 一致,不传就沿用这一段原来的)", returns: "this", doc: "开下一段草稿:连续模式落地时内部自动调(发 phase: 'restart'),也可以手动调;草稿已经退出时按 options 重新进来", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
200
- { name: "cancelAddChannel", kind: "method", params: "无", returns: "this(收掉草稿并补一条 phase: 'cancel')", doc: "取消新增通道(草稿期间按 Esc 等价,会补一条 phase: 'cancel')", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
201
- { name: "endAddChannel", kind: "method", params: "无", returns: "this(静默收掉视图上的示例假通道 / 线路,数据不动、不补事件)", doc: "结束新增通道:静默收掉视图上的示例假通道 / 线路(不补 cancel 事件),数据一个字节都不改 —— 结束事件里决定「不新建了」就用它", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
202
- { name: "commitAddChannel", kind: "method", params: "resolution?: GisViewChannelAddResolution 落地方式('channel' / 'line',缺省看草稿);options?: GisViewChannelAddInput 要覆盖的字段(name / circuits / colors / lineWidth / routing / path…)", returns: "GisViewChannelAddResult", doc: "", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?) / commitAddChannel(resolution?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 预指定起点:接下来只点终点\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\nview.commitAddChannel() // 两个节点选完后落地(resolution 缺省用推荐方案)\nview.commitAddChannel({ type: 'joinGroup', groupId: 'c1::2' }) // 指定方案\nview.continueAddChannel({ kind: 'line' }) // 连续模式下开下一段(也能换这一段加什么)\nview.cancelAddChannel() // 取消(Esc / 右键等价)\n","from":"header"}] },
197
+ { name: "beginAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 草稿配置:from(预指定起点,下一步只选终点)/ kind / circuits / autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段)/ routing / path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式,含 dots 与 layer)/ handoff(交接模式)/ select(点中的节点顺手选中,走 onSelect 那一套)/ originalEvent", returns: "this(只负责那条示例假通道,真正新增走 commitAddChannel / addChannel)", doc: "进入「新增通道」:缺省点两个节点选起点 / 终点,或由 options.from 预指定起点、只需再点终点;options 还支持 kind('channel' 新建通道 / 'line' 新建独立线路,缺省 'channel')/ circuits(一次加几条,缺省 1)/ autoCommit / continuous / chain / name / lineId / id / lineProps / linePropsByMember / lineItems(第二段,连同线路一起新建)/ routing('straight' / 'orthogonal')/ path(中间拐点)/ channelProps / channelDraftStyle(本次草稿的预览样式,含 dots / layer)/ handoff(交接模式:ready 发完收起草稿,自己调 addChannel 落地)/ select(点中的节点顺手选中)/ originalEvent,见「新增通道」", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
198
+ { name: "getAddChannelDraft", kind: "method", params: "无", returns: "GisViewChannelDraftPayload | null", doc: "当前新增通道草稿(两个节点的信息与可选方案),没有草稿时为 null", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
199
+ { name: "updateAddChannelStyle", kind: "method", params: "patch?: GisViewChannelDraftStyle | null 字段级合并的预览样式(箭头位置 / 多回线间距 / 两端 dots / 层级 layer / 自绘等);传 null 回到全局 channelDraftStyle", returns: "this", doc: "字段级合并当前草稿的预览样式(color / width / dot / dotColor / dots / layer / arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw);传 null 回到全局 channelDraftStyle,没有草稿时什么也不做", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
200
+ { name: "continueAddChannel", kind: "method", params: "options?: GisViewChannelDraftOptions 开下一段草稿(写法与 beginAddChannel 一致,不传就沿用这一段原来的)", returns: "this", doc: "开下一段草稿:连续模式落地时内部自动调(发 phase: 'restart'),也可以手动调;草稿已经退出时按 options 重新进来", examples: [{"group":"拐点与通道","code":"// beginAddChannel(options?) / getAddChannelDraft() / updateAddChannelStyle(patch?) / continueAddChannel(options?)","from":"header"}] },
201
+ { name: "cancelAddChannel", kind: "method", params: "无", returns: "this(收掉草稿并补一条 phase: 'cancel')", doc: "取消新增通道(草稿期间按 Esc 等价,会补一条 phase: 'cancel')", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
202
+ { name: "endAddChannel", kind: "method", params: "无", returns: "this(静默收掉视图上的示例假通道 / 线路,数据不动、不补事件)", doc: "结束新增通道:静默收掉视图上的示例假通道 / 线路(不补 cancel 事件),数据一个字节都不改 —— 结束事件里决定「不新建了」就用它", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"}] },
203
+ { name: "commitAddChannel", kind: "method", params: "input?: GisViewChannelAddResolution | GisViewChannel | GisViewLine | GisViewChannelAddCommitInput,五种写法:① 不传 / null = 用草稿 preferred;② 旧 resolution:字符串或只含非 undefined 的 type / groupId / groupIndex / channelId 的对象;③ 直接线路对象:没有 lineIds 数组,且写了 kind: 'line' / path / from / to / start / end 之一,业务字段与自己的 lineProps 透传到新线路;④ 直接通道对象:Array.isArray(lineIds) 且没有容器键,lineIds 逐条作新回线 id 种子、groups 作分组声明、自己的 lineProps 对所有新回线生效,其余字段进 channelProps,缺省 resolution: 'newChannel';⑤ 容器:出现 channel / line / lines / lineData / lineItems / channelGroups / group / channelProps / resolution 任一键(只写 lineProps 也算)。容器里 line 缺省是独立线路,lines 是成员表(给了几条建几条,空数组沿用草稿);channel.lineIds 只在没有 lines 时当种子。字段优先级:from / to = 顶层 → channel → line → lineItems 第一个对象项(裸 id 跳过)→ 草稿;path = 顶层 → line → channel → 草稿;分组 = channelGroups → group → channel.groups;容器 lineProps = 草稿 → 顶层 → line.lineProps → 逐条,不读 channel.lineProps;channelProps = 草稿 → channel 业务字段 → 顶层 channelProps。直接线路 / 直接通道对象默认新建,要并进已有分组必须用容器并显式写 resolution。options?: { auto?: boolean; continuous?: boolean; originalEvent?: Event | null }:auto 只标记自动提交,continuous: false 强制本次后结束,originalEvent 带回重开的草稿。ready 的方案对象不能整条回传,只取 { type, groupId, groupIndex, channelId } 子集。调用前草稿必须 ready,否则不写数据;没有草稿的程序化新增用 addChannel / addLine。完整示例见「10 通道编辑与新增」与「18 逐方法示例」", returns: "GisViewChannelAddResult", doc: "把已 ready 的草稿落到数据上。input 有五类:不传(用草稿 preferred)、旧 resolution(字符串,或只含 type / groupId / groupIndex / channelId 的对象)、直接线路对象(有 from / to / path / kind: 'line' 等线路形状)、直接通道对象(必须有 lineIds,缺省新建通道)、容器 { channel?, line?, lines?, lineData?, lineItems?, channelGroups?, group?, channelProps?, lineProps?, resolution?, ... }。对象字段优先、没写的沿用草稿;line 缺省表示独立线路,lines 才是成员回线表;直接通道对象缺省新建,直接线路对象缺省按独立线路落地,要并进已有分组必须用容器并显式写 resolution。options?: { auto?, continuous?, originalEvent? } 只影响本次事件与连续草稿。方案读数不能整条回传,只取 { type, groupId, groupIndex, channelId }。完整字段优先级与示例见「10 通道编辑与新增」的 commitAddChannel / 对象直传", examples: [{"group":"拐点与通道","code":"// commitAddChannel(input?, options?) / cancelAddChannel() / endAddChannel()\nview.beginAddChannel({\n kind: 'channel',\n circuits: 3,\n channelDraftStyle: {\n color: '#2f6fd0', width: 2.5, arrow: true, arrowSize: 14, arrowPositions: [0, 0.5, 1],\n circuitSpacing: 12, opacity: 0.9,\n // 两端示例点:false 全关;对象可统一 size / color / shape,from / to 再分别覆盖\n dots: { size: 5, shape: 'square', from: { color: '#2f6fd0' }, to: { color: '#d9544d' } },\n layer: 'below-points' // 示例线 / 箭头 / customDraw 画在真实节点之下;示例点仍在节点之后\n }\n}) // 点两个节点:第一个起点、第二个终点;通道 3 回时预览也画 3 条平行回线\nview.beginAddChannel({ from: 'station-a' }) // 只预指定起点:接下来只点终点(没有 from + to 的写法)\nconst draft = view.getAddChannelDraft() // 草稿读数(跟随光标的预览、两个节点、方案表与实际 style)\nview.updateAddChannelStyle({ arrowPositions: [0.25, 0.75], arrowReverse: true }) // 字段级合并,只改箭头\nview.updateAddChannelStyle(null) // 回到全局 channelDraftStyle\n","from":"header"},{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"}] },
203
204
  { name: "getChannelAddOptions", kind: "method", params: "from / to 两端节点 id;count?: number 回数(缺省 1);kind?: 'channel' | 'line'(缺省 'channel')", returns: "GisViewChannelAddPlan | null", doc: "两个节点之间的新增方案:新通道 / 新分组 / 并进某一组 / 无法添加;count 是这一次要加的回数(余量按它判),kind: 'line' 时方案表为空(独立线路不挑方案)", examples: [{"group":"拐点与通道","code":"// getChannelAddOptions(from, to, count?, kind?):两个节点之间能怎么加(界面拿它让用户选)\nconst plan = view.getChannelAddOptions('a', 'b', 2)\nconst linePlan = view.getChannelAddOptions('a', 'b', 1, 'line')\n","from":"header"}] },
204
205
  { name: "getChannelBetween", kind: "method", params: "from / to 两端节点 id", returns: "GisViewChannelBetween | null", doc: "两个节点之间的通道读数(只读):有通道时 hasChannel: true,channelId / channelIndex / groupCount / currentGroupId 与 groups / group / channel(id / name / lineIds / 分组明细)都给全;没有通道时 hasChannel: false、channel: null、groups: [],端点认不出来返回 null —— 与 getChannelAddOptions 的读数同一份,界面做「这两个点之间已经有通道 / 有几组」的判断用它,不必算方案表", examples: [{"group":"拐点与通道","code":"// getChannelBetween(from, to):只要通道读数 —— 有没有通道 / 几组 / 当前哪组(不做方案判断,端点认不出返回 null)\nconst between = view.getChannelBetween('a', 'b')\nif (between?.hasChannel) {\n console.log(between.channelId, between.groupCount, between.groups.map(group => group.id), between.currentGroupId)\n}\n","from":"header"}] },
205
- { name: "addChannel", kind: "method", params: "channel: GisViewChannelAddSpec 通道信息 { from, to, name?, id?, lineData?, group? }(后两个是第二 / 第三段写在对象上的别名);lineData?: GisViewChannelLineData 线路数组(Partial<GisViewLine> / string / number 的一条或一批,按每项 id 建 lineIds);group?: GisViewChannelAddGroup | GisViewChannelAddGroup[] 可选分组信息 { id?, lineIds?, path? }。旧写法 addChannel(input, lineItems?)(GisViewChannelAddInput:circuits / resolution / channelId / groupId / groupIndex / kind / routing / path 那套)照样认;path 是中间拐点,独立线路落地成完整折线、通道只写骨架拐点", returns: "GisViewChannelAddResult(channelLineIds 是落地后通道的完整成员表,独立线路为 null)", doc: "程序化新增通道(新签名,只新建):第一段通道信息(from / to / name / id / path 等通道字段),第二段线路数组 line_data —— 一条或一批(字符串 / 数字按 id 认),按每项 id 建 lineIds,第三段可选分组信息({ id?, lineIds?, path? },单个 / 一组一个)—— 第二段 / 第三段显式给了才走这条新路;两个节点之间已经有通道时整条失败(reason 指路下面两条)。旧写法 addChannel(input, lineItems?)(带 circuits / resolution / channelId / groupId / groupIndex / kind,或只写 { from, to })照样认:按方案表新建或并进已有通道,第二段 lineItems 给了几条就建几条", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
206
+ { name: "addChannel", kind: "method", params: "channel: GisViewChannelAddSpec 通道信息 { from, to, name?, id?, lineData?, group? }(后两个是第二 / 第三段写在对象上的别名);lineData?: GisViewChannelLineData 线路数组(Partial<GisViewLine> / string / number 的一条或一批,按每项 id 建 lineIds);group?: GisViewChannelAddGroup | GisViewChannelAddGroup[] 可选分组信息 { id?, lineIds?, path? }。旧写法 addChannel(input, lineItems?)(GisViewChannelAddInput:circuits / resolution / channelId / groupId / groupIndex / kind / routing / path 那套)照样认;path 是中间拐点,独立线路落地成完整折线、通道只写骨架拐点", returns: "GisViewChannelAddResult(channelLineIds 是落地后通道的完整成员表,独立线路为 null)", doc: "程序化新增通道(新签名,只新建):第一段通道信息(from / to / name / id / path 等通道字段),第二段线路数组 line_data —— 一条或一批(字符串 / 数字按 id 认),按每项 id 建 lineIds,第三段可选分组信息({ id?, lineIds?, path? },单个 / 一组一个)—— 第二段 / 第三段显式给了才走这条新路;两个节点之间已经有通道时整条失败(reason 指路下面两条)。旧写法 addChannel(input, lineItems?)(带 circuits / resolution / channelId / groupId / groupIndex / kind,或只写 { from, to })照样认:按方案表新建或并进已有通道,第二段 lineItems 给了几条就建几条", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
206
207
  { name: "addChannelGroupLines", kind: "method", params: "channelId 目标通道 id;lineData?: GisViewChannelLineData 这次新建的线路数组(按每项 id 建回线);group?: GisViewChannelAddGroup 新分组信息 { id?, path? }(缺省 ${通道 id}::N、不带拐点)", returns: "GisViewChannelAddResult", doc: "给已有通道新加一组回线(原 addChannel 的 newGroup 那一支):第二段线路数组 line_data 按每项 id 建回线,groupInfo({ id?, path? })给新分组的 id 与中间拐点,缺省 ${通道 id}::N、不带拐点;通道 / 线路认不出来时 ok: false + reason", examples: [{"group":"拐点与通道","code":"// 已经有通道时用拆出来的两条(语义直说,不用自己拼 resolution)\nview.addChannelGroupLines('c1', [{ id: 'm-5' }], { id: 'c1::3' }) // 新加一组\nview.joinChannelGroupLines('c1', 'c1::2', [{ id: 'm-6' }]) // 并进某一组(序号 1 也认)\nconsole.log(joined.channelLineIds) // 更新后这条通道的完整成员表(lineIds)\n","from":"code"}] },
207
208
  { name: "addChannelGroups", kind: "method", params: "channelId 目标通道 id;groups: GisViewChannelAddGroupInput | GisViewChannelAddGroupInput[] 每项 { id?, path?, lineData? | lines? | lineItems? }(线路数据与 addChannel 第二段同一份写法);options?: { emit?: boolean; label?: string } emit: false 只落地不通知", returns: "GisViewAddChannelGroupsResult", doc: "给已有通道一次新加多组回线:groups 每项 { id?, path?, lineData | lines | lineItems },线路数据与 addChannel 第二段同一份写法;逐组落地但只记一步历史、只发一条 addChannelGroups 数据变更,返回 { ok, reason, count, results, lineIds, groupIds, groups },options.emit: false 只落地不通知", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
208
209
  { name: "joinChannelGroupLines", kind: "method", params: "channelId 目标通道 id;groupId?: string | number 并进哪一组(认分组 id 也认序号,从 0 起;不传挑第一个还有余量的分组);lineData?: GisViewChannelLineData 这次新建的线路数组", returns: "GisViewChannelAddResult", doc: "把新回线并进已有通道的某一组(原 addChannel 的 joinGroup 那一支):第二段线路数组 line_data,groupId 认分组 id 也认序号(从 0 起、按声明顺序),不传就挑第一个还有余量的分组", examples: [{"group":"拐点与通道","code":"// 已经有通道时用拆出来的两条(语义直说,不用自己拼 resolution)\nview.addChannelGroupLines('c1', [{ id: 'm-5' }], { id: 'c1::3' }) // 新加一组\nview.joinChannelGroupLines('c1', 'c1::2', [{ id: 'm-6' }]) // 并进某一组(序号 1 也认)\nconsole.log(joined.channelLineIds) // 更新后这条通道的完整成员表(lineIds)\n","from":"code"}] },
209
210
  { name: "addChannels", kind: "method", params: "input: GisViewChannelAddInput | GisViewChannelAddInput[] 一条或一批声明(每条与 addChannel 同一套写法;routing: 'orthogonal' 自动正交折线,path 是中间拐点,独立线路落地成完整 path、通道只写骨架);options?: { emit?: boolean } emit: false 只改数据不发 dataChange", returns: "GisViewAddChannelsResult", doc: "addChannel 的批量版:一次新增多条通道 / 独立线路(每条是新签名或旧写法,逐条各自认;每项的 routing / path 走同一套解析,path 是中间拐点),逐条落地但只重排一次场景、只记一步历史、只发一条 addChannels 数据变更;返回 { ok, count, results, lineIds, channelIds, items },失败条目在各自的 reason 里说明原因、不影响其余条目,options.emit: false 时不发 onDataChange,见「动态换数据与加载完成」", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
210
- { name: "addLine", kind: "method", params: "input: GisViewChannelAddInput 同上(等价 addChannel({ ...input, kind: 'line' });routing: 'orthogonal' 自动正交折线,path 是中间拐点,落地成含两端的完整折线);第二段 GisViewChannelLineData 与 addChannel 的线路数组同一份 —— 每项可以只写一个 id(裸字符串 / 数字按 { id } 收)、写对象给逐条字段,不写就按 from-to-序号 自动编号", returns: "GisViewChannelAddResult", doc: "/** * 程序化新增独立线路(不挂通道)。", examples: [{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
211
+ { name: "addLine", kind: "method", params: "input: GisViewChannelAddInput 同上(等价 addChannel({ ...input, kind: 'line' });routing: 'orthogonal' 自动正交折线,path 是中间拐点,落地成含两端的完整折线);第二段 GisViewChannelLineData 与 addChannel 的线路数组同一份 —— 每项可以只写一个 id(裸字符串 / 数字按 { id } 收)、写对象给逐条字段,不写就按 from-to-序号 自动编号", returns: "GisViewChannelAddResult", doc: "/** * 程序化新增独立线路(不挂通道)。", examples: [{"group":"拐点与通道","code":"// commitAddChannel 只负责把已 ready 的草稿落地;没有草稿的程序化新增用 addChannel / addLine。","from":"header"},{"group":"拐点与通道","code":"// addChannel(spec, lineData?, group?) / addChannels(input, options?) / addChannelGroups(channelId, groups, options?) / addLine(input):程序化新增\nconst channelAdd = view.addChannel({ from: 'a', to: 'b', name: '二号通道', path: [[200, 120]] }, [{ id: 'm-1' }, { id: 'm-2' }])\nconst manyChannels = view.addChannels([{ from: 'a', to: 'b', circuits: 1 }, { from: 'b', to: 'a', circuits: 2, kind: 'line' }])\nconst manyGroups = view.addChannelGroups('c1', [\n { id: 'c1::4', path: [[130, 70]], lineData: [{ id: 'm-7' }, { id: 'm-8' }] },\n { path: [[150, 90]], lines: ['m-9'] }\n])\nconsole.log(manyGroups.count, manyGroups.lineIds, manyGroups.groupIds)\nconst lineAdd = view.addLine({ from: 'a', to: 'b', name: '三回' })","from":"header"}] },
211
212
  { name: "removeLine", kind: "method", params: "lineId: string | number 要删的线路 id", returns: "GisViewRemoveLineResult", doc: "移除一条线路(撤销刚落地的新增线路):挂在通道上的回线会从通道声明里一起摘掉,通道空了整条删掉,返回 { ok, line, lineId, index, channelId, removedChannel }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
212
213
  { name: "removeLines", kind: "method", params: "lineIds: string | number | 数组 一次删多条", returns: "GisViewRemoveLinesResult", doc: "一次移除多条线路(传数组或单条 id):通道声明与分组跟着收拾、删空的通道整条走,返回 { ok, lineIds, indexes, lines, entries, removedChannels }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
213
214
  { name: "removeChannel", kind: "method", params: "channelId 通道 id", returns: "GisViewRemoveChannelResult", doc: "移除整条通道:它名下的回线一条条摘掉,返回 { ok, channelId, lineIds, lines, removedChannel }", examples: [{"group":"拐点与通道","code":"// removeLine(lineId) / removeLines(lineIds) / removeChannel(channelId):删线路 / 删多条 / 删整条通道\nview.removeLine('l1')\nview.removeLines(['l1', 'l2'])\nview.removeLines('l1') // 单条也认\nview.removeChannel('c1')","from":"header"}] },
@@ -366,7 +367,7 @@ export const API_OPTIONS = [
366
367
  { name: "channelTangentInset", type: "number | null", defaultValue: "null", doc: "两端切点往节点里收多少 CSS 像素:缺省(null)按成员回线里最粗那条的线宽收,线因此不会从节点边缘探出去;0 表示切点正好落在节点边上,负数外收(切点往节点外让、回线整段比节点宽);单个通道可用 tangentInset 覆盖;内收两侧合计最多收掉节点宽度的 40%,外收单侧最多让出 50%" },
367
368
  { name: "channelMaxGroups", type: "number", defaultValue: "0", doc: "一条通道最多几个分组(0 表示不限):新增通道时用来判断还能不能变成新分组,见「新增通道」" },
368
369
  { name: "channelGroupMaxLines", type: "number", defaultValue: "0", doc: "一个分组最多几条回线(0 表示不限):新增通道时用来判断还能不能并进某一组,见「新增通道」" },
369
- { name: "channelDraftStyle", type: "GisViewChannelDraftStyle", defaultValue: "蓝色 2px + 两端圆点,箭头关闭", doc: "新增通道草稿预览的全局样式:color / width / dot / dotColor / arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw;beginAddChannel({ channelDraftStyle }) 可按这一次草稿覆盖,updateAddChannelStyle(patch) 可在草稿期间增量修改" },
370
+ { name: "channelDraftStyle", type: "GisViewChannelDraftStyle", defaultValue: "蓝色 2px + 两端圆点,箭头关闭", doc: "新增通道草稿预览的全局样式:color / width / dot / dotColor / dots(false 关示例点,或 { show, size, color, shape, from, to } 分端配置;示例点固定在点中的节点中心)/ layer('above' 默认,'below-points' 让示例线 / 箭头 / customDraw 位于真实节点之下)/ arrow / arrowSize / arrowPositions / arrowReverse / arrowColor / circuitSpacing / opacity / customDraw;beginAddChannel({ channelDraftStyle }) 可按这一次草稿覆盖,updateAddChannelStyle(patch) 可在草稿期间增量修改" },
370
371
  { name: "webglLayers", type: "GisViewWebGLLayer[]", defaultValue: "[]", doc: "WebGL 自定义绘制层:每层提供 draw({ gl, canvas, viewport, camera, frame, project, unproject, requestRender }),在内置内容之后按 order 绘制;运行时也可用 addWebGLLayer / updateWebGLLayer / removeWebGLLayer 维护" },
371
372
  { name: "background", type: "GisViewBackground", defaultValue: "浅色底", doc: "背景层,支持底色、图片与 GeoJSON" },
372
373
  { name: "pointStyle", type: "GisViewPointStyle", defaultValue: "见下", doc: "点的默认样式,可被单个点覆盖;运行时按字段合并用 updatePointStyle(patch)(updateOptions({ pointStyle }) 是整份换)" },
@@ -384,7 +385,7 @@ export const API_OPTIONS = [
384
385
  { name: "labelStates", type: "GisViewLabelState[]", defaultValue: "无", doc: "标签的运行时状态(getLabelStates() 的那一份:拖出来的偏移、改过的内容与样式、运行时加的标签、隐藏覆盖):创建时喂回来就回显,等价于创建之后调 setLabelStates(states);updateOptions({ labelStates }) 同样认。exportData({ format: 'input' }) 的结果里就叫 labelStates,存档 → 回灌直接接上" },
385
386
  { name: "graphics", type: "GisViewGraphic[]", defaultValue: "无", doc: "与节点无关的扩展图形:按世界坐标放 SVG 路径 / 图片 / Canvas,支持锚点、偏移、旋转、透明度、zIndex 与 scaleWithView;位于画布之上、标签之下,运行时用 addGraphic / addGraphics / updateGraphic / removeGraphic / clearGraphics 增删改,见「扩展图形(graphic)」" },
386
387
  { name: "tooltip", type: "GisViewTooltipOptions", defaultValue: "无", doc: "悬停提示:点 / 线 / 通道分别配置内容,见「悬浮提示」" },
387
- { name: "minimap", type: "GisViewMinimapOptions | null", defaultValue: "无", doc: "内置鹰眼图:位置、尺寸、节点 / 线路 / 视口框三份样式与交互都可配,extent 决定小图按数据范围还是「数据 ∪ 底图」铺(默认 auto,GeoJSON 底图不会缩成一个点),zoomable 打开后小图自身可整体放大缩小(空白处拖动可以平移小图或直接框选让主图显示过去,每格倍率 zoomStep),通道还能只画一根;小图同样支持规则(pointRules / lineRules,缺省沿用主图规则的颜色与层级),可以只画某几层(levels)、关掉分层叠画(layered)、按三组系数给节点与线路定尺寸(整体倍数 pointSizeScale / lineWidthScale、跟随小图倍数 pointZoomScale / lineZoomScale(只在小图自身倍数 ≠ 1 时起作用)、跟随主视图地图倍数 pointMapZoomScale / lineMapZoomScale),视口框与框选矩形的颜色 / 背景 / 边框 / 圆角也能配(boxStyle / selectStyle),extent 还能直接写死一份世界范围、容器可加 className / style、layers 按 placement 分数据前 / 数据后、段内按 order 拆出多层自绘内容(旧 draw 钩子仍兼容并排在最后)、onInteraction 在手势结束时给出类型 / 世界坐标 / 相机、maxPixelRatio 限制备份倍率上限,不配则不产生任何 DOM,见「鹰眼图」" },
388
+ { name: "minimap", type: "GisViewMinimapOptions | null", defaultValue: "无", doc: "内置鹰眼图:位置、尺寸、节点 / 线路 / 视口框三份样式与交互都可配,extent 决定小图按数据范围还是「数据 ∪ 底图」铺(默认 auto,GeoJSON 底图不会缩成一个点),extentFollowCamera 默认 false、打开后才允许 auto 范围随相机收窄。zoomable 三态互斥:false 关闭、true 手动缩放小图自身、'follow' 让整体范围跟随主图(followZoomScale 是主图倍数的指数,跟随模式下 setMinimapZoom / zoomMinimapBy 不抢控制权);空白处拖动可平移小图或直接框选让主图显示过去(dragAction,box 不依赖缩放,手动模式下 Ctrl / ⌘ 才切换动作),每格倍率 zoomStep。通道还能只画一根;小图同样支持规则(pointRules / lineRules,缺省沿用主图规则的颜色、层级、最终节点尺寸与图标、样式链线宽,节点 / 线路尺寸按两边视口比例缩放且不受主图相机倍数影响),可以只画某几层(levels)、关掉分层叠画(layered)、按三组系数给节点与线路定尺寸(整体倍数 pointSizeScale / lineWidthScale 对自有与继承要素都生效;跟随小图倍数 pointZoomScale / lineZoomScale 在小图自身放大时也作用于继承要素;跟随主视图地图倍数 pointMapZoomScale / lineMapZoomScale 只作用于自有规则 / 样式),视口框与框选矩形的颜色 / 背景 / 边框 / 圆角也能配(boxStyle / selectStyle),extent 还能直接写死一份世界范围、容器可加 className / style、layers 按 placement 分数据前 / 数据后、段内按 order 拆出多层自绘内容(旧 draw 钩子仍兼容并排在最后)、onInteraction 在手势结束时给出类型 / 世界坐标 / 相机、maxPixelRatio 限制备份倍率上限,不配则不产生任何 DOM,见「鹰眼图」" },
388
389
  { name: "filter", type: "GisViewFilterInput | null", defaultValue: "无", doc: "初始筛选条件,等价于创建之后调用 setFilter;mode 缺省 'hide',写 'remove' 则删除不命中的要素,见「筛选」" },
389
390
  { name: "filterWorker", type: "boolean | GisViewFilterWorkerOptions", defaultValue: "{ threshold: 20000 }", doc: "筛选是否用 Web Worker 计算:默认点 + 线总数超过阈值才用,false 表示始终同步算" },
390
391
  { name: "searchWorker", type: "boolean | GisViewSearchWorkerOptions", defaultValue: "{ threshold: 20000 }", doc: "[异步搜索](14-视图与筛选.md#搜索与定位)是否用 Web Worker 计算:默认点 + 线总数超过阈值才用,false 表示 searchAsync 也同步算;同步的 search 一直在主线程算,不受这一项影响" },
@@ -4,8 +4,8 @@ const {
4
4
  containerRef, stageRef, isFullscreen, fullscreenSupported, toggleFullscreen, viewRef, selectionCount, stats,
5
5
  qualityBadge, cursorText, hostZoom, zoomText, pointerText, screenText, lngLatText, canvasMenu, closeCanvasMenu,
6
6
  canvasMenuStyle, canvasMenuRows, runCanvasMenuAction, pointSizeScale, lineWidthScale, minimapSize,
7
- minimapEnabled, minimapShowLines, minimapExtent, minimapZoomable, minimapDragAction, minimapChannelCircuits,
8
- minimapZoomStep, zoomMinimap, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize,
7
+ minimapEnabled, minimapShowLines, minimapExtent, minimapExtentFollowCamera, minimapZoomMode, minimapFollowZoomScale,
8
+ minimapDragAction, minimapChannelCircuits, minimapZoomStep, zoomMinimap, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize,
9
9
  minimapLineWidth, minimapPointZoom, minimapLineZoom, minimapPointMapZoom, minimapLineMapZoom, minimapBoxFill,
10
10
  minimapBoxFillOpacity, minimapBoxStroke, minimapBoxStrokeWidth, minimapBoxStrokeStyle, minimapBoxRadius,
11
11
  minimapDrawLayer, minimapSummary, minimapLayerOptions, heatDensityText, channelMenu, closeChannelMenu,
@@ -105,6 +105,10 @@ const {
105
105
  <option value="data">范围:只看数据</option>
106
106
  <option value="background">范围:只看底图</option>
107
107
  </select>
108
+ <label class="demo-minimap-switch" title="extent=auto 时是否允许范围随相机收窄;默认关,避免包围盒与视口框自己跳">
109
+ <input v-model="minimapExtentFollowCamera" type="checkbox" :disabled="!minimapEnabled || minimapExtent !== 'auto'" />
110
+ 范围跟相机
111
+ </label>
108
112
  <label class="demo-minimap-switch">
109
113
  <input v-model="minimapEnabled" type="checkbox" />
110
114
  鹰眼
@@ -117,22 +121,27 @@ const {
117
121
  <input v-model="minimapChannelCircuits" type="checkbox" />
118
122
  回线
119
123
  </label>
120
- <label class="demo-minimap-switch">
121
- <input v-model="minimapZoomable" type="checkbox" />
122
- 可缩放
123
- </label>
124
- <select v-model="minimapDragAction" :disabled="!minimapEnabled || !minimapZoomable" title="开了可缩放之后,小图空白处拖动的动作(按住 Ctrl / ⌘ 拖动是另一个)">
124
+ <select v-model="minimapZoomMode" :disabled="!minimapEnabled" title="缩放模式:关闭 / 手动缩放小图 / 整体范围跟随主图;手动与跟随互斥">
125
+ <option value="off">缩放:关闭</option>
126
+ <option value="manual">缩放:手动</option>
127
+ <option value="follow">缩放:跟随主图</option>
128
+ </select>
129
+ <select v-model="minimapDragAction" :disabled="!minimapEnabled" title="小图空白处拖动的动作:pan 平移小图内容(需手动缩放),box 直接框选(不依赖缩放开关);只有手动缩放时 Ctrl / ⌘ 才切换另一个动作">
125
130
  <option value="pan">拖动=平移小图</option>
126
131
  <option value="box">拖动=框选</option>
127
132
  </select>
128
- <select v-model.number="minimapZoomStep" :disabled="!minimapEnabled || !minimapZoomable" title="每一次放大 / 缩小的倍率(滚轮一格与 - / + 按钮)">
133
+ <label class="demo-minimap-switch" title="zoomable=follow 时主图相对基准倍数的指数:1 同倍率、0.5 为主图放大 4 倍时小图放大 2 倍、0 不随主图变化">
134
+ 跟随指数
135
+ <input v-model.number="minimapFollowZoomScale" type="number" min="0" max="2" step="0.1" :disabled="!minimapEnabled || minimapZoomMode !== 'follow'" />
136
+ </label>
137
+ <select v-model.number="minimapZoomStep" :disabled="!minimapEnabled || minimapZoomMode !== 'manual'" title="每一次放大 / 缩小的倍率(滚轮一格与 - / + 按钮;只用于手动缩放)">
129
138
  <option :value="1.2">步进 1.2</option>
130
139
  <option :value="1.4">步进 1.4</option>
131
140
  <option :value="1.8">步进 1.8</option>
132
141
  <option :value="2">步进 2</option>
133
142
  </select>
134
- <button type="button" :disabled="!minimapEnabled || !minimapZoomable" @click="zoomMinimap(1 / minimapZoomStep)">-</button>
135
- <button type="button" :disabled="!minimapEnabled || !minimapZoomable" @click="zoomMinimap(minimapZoomStep)">+</button>
143
+ <button type="button" :disabled="!minimapEnabled || minimapZoomMode !== 'manual'" @click="zoomMinimap(1 / minimapZoomStep)">-</button>
144
+ <button type="button" :disabled="!minimapEnabled || minimapZoomMode !== 'manual'" @click="zoomMinimap(minimapZoomStep)">+</button>
136
145
  </div>
137
146
  <div class="demo-minimap-toolbar demo-minimap-toolbar--rules">
138
147
  <select v-model="minimapRuleMode" :disabled="!minimapEnabled" title="小图的规则:沿用主图(只取颜色与层级)/ 按电压单独配一份(半径、形状、层级都在内)/ 完全不用规则">
@@ -163,7 +172,7 @@ const {
163
172
  放大线路
164
173
  <input v-model.number="minimapLineZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
165
174
  </label>
166
- <span class="demo-control-hint">「放大节点 / 放大线路」跟的是<strong>小图自身</strong>的倍数:要勾上「可缩放」再滚轮 / +- 放大鹰眼图才看得到效果(改这两个值时这里会自动打开);要让节点 / 线路跟着<strong>主视图</strong>的缩放走,用下面的「节点跟主图 / 线路跟主图」(pointMapZoomScale)。</span>
175
+ <span class="demo-control-hint">「放大节点 / 放大线路」跟的是<strong>小图自身</strong>的倍数:要勾上「可缩放」再滚轮 / +- 放大鹰眼图才看得到效果(改这两个值时这里会自动打开)。沿用主图规则时,节点 / 线路先按两边视口比例缩放,<strong>主视图缩放不会改变它们的大小</strong>;下面的「节点跟主图 / 线路跟主图」只作用于小图自有规则 / 样式。</span>
167
176
  <label class="demo-minimap-switch" title="节点半径的整体倍数:与主视图的 pointSizeScale 同名同义(常数倍数,与小图倍数无关)">
168
177
  整体节点
169
178
  <input v-model.number="minimapPointSize" type="number" min="0" max="4" step="0.2" :disabled="!minimapEnabled" />
@@ -172,13 +181,13 @@ const {
172
181
  整体线路
173
182
  <input v-model.number="minimapLineWidth" type="number" min="0" max="4" step="0.2" :disabled="!minimapEnabled" />
174
183
  </label>
175
- <label class="demo-minimap-switch" title="节点半径跟着主视图的地图倍数放大:0 不跟随、1 同倍率、0.5 是主图放大 4 倍节点放大 2 倍(基准缺省取主图适应视图时的比例)">
184
+ <label class="demo-minimap-switch" title="小图自有规则 / 样式的节点半径跟着主视图的地图倍数放大:0 不跟随、1 同倍率;沿用主图规则的节点不吃这一份">
176
185
  节点跟主图
177
- <input v-model.number="minimapPointMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
186
+ <input v-model.number="minimapPointMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled || minimapRuleMode === 'inherit'" />
178
187
  </label>
179
- <label class="demo-minimap-switch" title="线路宽度跟着主视图的地图倍数放大:0 不跟随、1 同倍率">
188
+ <label class="demo-minimap-switch" title="小图自有规则 / 样式的线路宽度跟着主视图的地图倍数放大:0 不跟随、1 同倍率;沿用主图线宽不吃这一份">
180
189
  线路跟主图
181
- <input v-model.number="minimapLineMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled" />
190
+ <input v-model.number="minimapLineMapZoom" type="number" min="0" max="1" step="0.1" :disabled="!minimapEnabled || minimapRuleMode === 'inherit'" />
182
191
  </label>
183
192
  </div>
184
193
  <div class="demo-minimap-toolbar demo-minimap-toolbar--box">
@@ -212,7 +221,7 @@ const {
212
221
  自定义层
213
222
  </label>
214
223
  </div>
215
- <span class="demo-minimap-hint">内置鹰眼图 · 拖框移视图 · 空白处长按后跟随(没开「可缩放」时)· 开了「可缩放」:滚轮按步进放大缩小(Ctrl / ⌘ + 滚轮留给主图)、空白处拖动按上面的选择平移小图或直接框选、关掉「回线」一个通道只画一根 · 范围三档:自动(数据 ∪ 底图,GeoJSON 底图不会缩成一点)/ 只看数据 / 只看底图 · 规则:沿用主图(节点取主图的描边色,不再是白点)/ 按电压单独配(半径与形状也换)/ 不用,分层叠画可以关,层级可以只看某几层 · 尺寸三组系数与主视图对应:整体节点 / 整体线路是常数倍数,放大节点 / 放大线路跟着小图自身的倍数,节点跟主图 / 线路跟主图跟着主视图的地图倍数 · 视口框与框选矩形的底色、不透明度、边框(颜色 / 宽度 / 线型)与圆角都能调 · 「自定义层」是 draw 钩子的示例:每帧按小图坐标画指北针与范围中心十字</span>
224
+ <span class="demo-minimap-hint">内置鹰眼图 · 拖框移视图 · 缩放模式:关闭 / 手动缩放小图 / 跟随主图,手动与跟随互斥 · 手动模式滚轮按步进缩放小图(Ctrl / ⌘ + 滚轮留给主图),跟随模式按指数随主图倍数整体变化;「拖动=框选」不依赖缩放开关 · 空白处长按后跟随只在未手动缩放时走 · 范围三档:自动(数据 ∪ 底图)/ 只看数据 / 只看底图,「范围跟相机」默认关 · 规则:沿用主图 / 按电压单独配 / 不用;分层、层级、三组尺寸系数都能调 · 视口框与框选矩形的底色、不透明度、边框与圆角都能调 · 「自定义层」是 draw 钩子的示例</span>
216
225
  </details>
217
226
  </div>
218
227
  </div>
@@ -3443,7 +3443,8 @@ export const useGisViewDemo = () => {
3443
3443
  /**
3444
3444
  * 鹰眼图:改成组件自带的能力,这里只留开关与样式配置。
3445
3445
  * 节点与线路各自一份样式,框可以直接拖,空白处长按之后视角跟过去;
3446
- * 「可缩放」打开后滚轮缩放鹰眼图自身(zoomable),按钮走的是实例方法 setMinimapZoom 那一组。
3446
+ * 缩放模式三选一:关闭 / 手动缩放小图(zoomable: true)/ 跟随主图(zoomable: 'follow');
3447
+ * 手动模式走滚轮与 setMinimapZoom 那一组,跟随模式只由主图倍数带动。
3447
3448
  */
3448
3449
  const MINIMAP_SIZES = {
3449
3450
  small: [132, 99],
@@ -3456,9 +3457,13 @@ export const useGisViewDemo = () => {
3456
3457
  const minimapShowLines = ref(true)
3457
3458
  /** 小图范围:auto 数据 ∪ 底图(GeoJSON 底图不会缩成一个点)、data 只看数据(老行为)、background 只看底图 */
3458
3459
  const minimapExtent = ref('auto')
3459
- /** 是否允许缩放鹰眼图自身:关掉时小图上的滚轮还是缩放主图 */
3460
- const minimapZoomable = ref(false)
3461
- /** 开了内部缩放之后,小图空白处拖动做哪个动作:平移小图内容 / 直接框选(Ctrl / ⌘ 换另一个) */
3460
+ /** auto 范围是否随相机收窄:默认关,始终按数据 ∪ 底图铺 */
3461
+ const minimapExtentFollowCamera = ref(false)
3462
+ /** 缩放模式:off 关闭、manual 手动缩放小图、follow 整体范围跟随主图;后两者互斥 */
3463
+ const minimapZoomMode = ref('off')
3464
+ /** 跟随主图时主图倍数的指数:1 同倍率、0.5 是主图放大 4 倍小图放大 2 倍 */
3465
+ const minimapFollowZoomScale = ref(1)
3466
+ /** 小图空白处拖动做哪个动作:平移小图内容 / 直接框选;box 不依赖手动缩放 */
3462
3467
  const minimapDragAction = ref('pan')
3463
3468
  /** 通道在小图上画几根:true 画全部回线,false 一个通道只画偏移最靠中间的那一根 */
3464
3469
  const minimapChannelCircuits = ref(true)
@@ -3523,7 +3528,8 @@ export const useGisViewDemo = () => {
3523
3528
  /** 鹰眼图折叠条上的摘要:不展开设置也能看到当前档位 */
3524
3529
  const minimapSummary = computed(() => {
3525
3530
  const size = MINIMAP_SIZES[minimapSize.value] ?? []
3526
- return `${size[0] ?? 0}×${size[1] ?? 0} · ${minimapEnabled.value ? '开' : '关'} · ${minimapShowLines.value ? '线路' : '无线'} · ${minimapChannelCircuits.value ? '全部回线' : '一根回线'}`
3531
+ const zoom = minimapZoomMode.value === 'manual' ? '手动缩放' : minimapZoomMode.value === 'follow' ? '跟随主图' : '不缩放'
3532
+ return `${size[0] ?? 0}×${size[1] ?? 0} · ${minimapEnabled.value ? '开' : '关'} · ${zoom} · ${minimapShowLines.value ? '线路' : '无线'} · ${minimapChannelCircuits.value ? '全部回线' : '一根回线'}`
3527
3533
  })
3528
3534
  /** 主图节点规则里出现过的层号:按电压分层关掉时全是 0,就没有可选的层 */
3529
3535
  const minimapLayerOptions = computed(() => {
@@ -3579,6 +3585,8 @@ export const useGisViewDemo = () => {
3579
3585
  height: MINIMAP_SIZES[minimapSize.value][1],
3580
3586
  // 范围:默认数据与底图合起来,GeoJSON 底图不会因为与数据范围不重合而缩成一个点
3581
3587
  extent: minimapExtent.value,
3588
+ // auto 默认不跟相机收窄;打开后相机进出数据 / 底图范围时才会重新算包围盒
3589
+ extentFollowCamera: minimapExtentFollowCamera.value,
3582
3590
  position: 'bottom-right',
3583
3591
  margin: 12,
3584
3592
  pointStyle: { color: '#4b8dff', size: 1.4 },
@@ -3592,7 +3600,8 @@ export const useGisViewDemo = () => {
3592
3600
  // 被 levels 挡掉的节点,连在它们身上的线路按同一套 cascade 一起隐藏('false' 表示不连锁)
3593
3601
  cascade: minimapCascade.value,
3594
3602
  layered: minimapLayered.value,
3595
- // 节点 / 线路的尺寸系数与主视图一一对应:整体倍数、跟随小图自身倍数、跟随主视图地图倍数
3603
+ // 节点 / 线路的尺寸系数:整体倍数对自有与沿用主图的要素都生效;小图自身倍数也两边都算;
3604
+ // 跟随主视图地图倍数只作用于小图自有规则 / 样式(沿用主图时尺寸按视口比例,不受主图缩放影响)
3596
3605
  pointSizeScale: minimapPointSize.value,
3597
3606
  lineWidthScale: minimapLineWidth.value,
3598
3607
  // 节点与线路跟着小图自身的倍数放大:滚轮 / - + 放大鹰眼图时它们一起变大
@@ -3616,9 +3625,11 @@ export const useGisViewDemo = () => {
3616
3625
  draw: minimapDrawLayer.value ? drawMinimapOverlay : undefined,
3617
3626
  interactive: true,
3618
3627
  longPressDelay: 180,
3619
- // 开关控制能不能整体放大缩小:minZoom / maxZoom 是倍数范围,zoom 是初始倍数
3620
- zoomable: minimapZoomable.value,
3621
- // 每一次放大 / 缩小的倍率、空白处拖动的动作(pan 平移小图 / box 直接框选)、通道画全部回线还是一根
3628
+ // 三档互斥:true 手动缩放小图,'follow' 整体范围跟随主图,false 关闭;minZoom / maxZoom 是倍数范围
3629
+ zoomable: minimapZoomMode.value === 'manual' ? true : minimapZoomMode.value === 'follow' ? 'follow' : false,
3630
+ // 跟随主图时主图相对基准倍数的指数;手动缩放模式下不参与
3631
+ followZoomScale: minimapFollowZoomScale.value,
3632
+ // 每一次放大 / 缩小的倍率、空白处拖动的动作(box 不依赖手动缩放)、通道画全部回线还是一根
3622
3633
  zoomStep: minimapZoomStep.value,
3623
3634
  dragAction: minimapDragAction.value,
3624
3635
  channelCircuits: minimapChannelCircuits.value,
@@ -4774,24 +4785,24 @@ export const useGisViewDemo = () => {
4774
4785
  }
4775
4786
  /**
4776
4787
  * 小图倍数的两个系数(pointZoomScale / lineZoomScale)只在小图自身被放大时起作用 ——
4777
- * 默认「可缩放」是关的,改这两个值看不出变化,容易被当成没生效。这里在用户真的填了正数时
4778
- * 顺手把「可缩放」打开,并在读数里说一句怎么看到效果。
4788
+ * 缩放模式是关的时候,改这两个值看不出变化,容易被当成没生效。这里在用户真的填了正数时
4789
+ * 顺手切到「手动缩放小图」,并在读数里说一句怎么看到效果;跟随主图的模式不被覆盖。
4779
4790
  */
4780
4791
  watch([minimapPointZoom, minimapLineZoom], ([point, line]) => {
4781
- if (!minimapEnabled.value || minimapZoomable.value) {
4792
+ if (!minimapEnabled.value || minimapZoomMode.value !== 'off') {
4782
4793
  return
4783
4794
  }
4784
4795
  if (!(Number(point) > 0) && !(Number(line) > 0)) {
4785
4796
  return
4786
4797
  }
4787
- minimapZoomable.value = true
4788
- writeLastEvent('「放大节点 / 放大线路」跟的是小图自身的倍数:已自动打开「可缩放」,滚轮或 + / - 放大鹰眼图就能看到节点 / 线路一起变大')
4798
+ minimapZoomMode.value = 'manual'
4799
+ writeLastEvent('「放大节点 / 放大线路」跟的是小图自身的倍数:已自动切到「手动缩放」,滚轮或 + / - 放大鹰眼图就能看到节点 / 线路一起变大')
4789
4800
  })
4790
4801
  watch([showTooltip, tooltipHtml, tooltipChannelScope], () => viewRef.value?.updateOptions({ tooltip: buildTooltipOptions() }))
4791
4802
  watch([filterVoltage, filterType, filterKeyword, filterLines, filterCascade], () => applyFilterFromControls())
4792
4803
  // 数据映射:换模式 / 换比例就把当前数据按新映射重画一遍(数据不动,只换画出来的坐标)
4793
4804
  watch([datasetProjection, datasetProjectionScale], () => applyDatasetMapping())
4794
- watch([minimapSize, minimapEnabled, minimapShowLines, minimapExtent, minimapZoomable, minimapDragAction, minimapChannelCircuits, minimapZoomStep, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize, minimapLineWidth, minimapPointZoom, minimapLineZoom, minimapPointMapZoom, minimapLineMapZoom, minimapBoxFill, minimapBoxFillOpacity, minimapBoxStroke, minimapBoxStrokeWidth, minimapBoxStrokeStyle, minimapBoxRadius, minimapDrawLayer], () => viewRef.value?.updateOptions({ minimap: buildMinimapOptions() }))
4805
+ watch([minimapSize, minimapEnabled, minimapShowLines, minimapExtent, minimapExtentFollowCamera, minimapZoomMode, minimapFollowZoomScale, minimapDragAction, minimapChannelCircuits, minimapZoomStep, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize, minimapLineWidth, minimapPointZoom, minimapLineZoom, minimapPointMapZoom, minimapLineMapZoom, minimapBoxFill, minimapBoxFillOpacity, minimapBoxStroke, minimapBoxStrokeWidth, minimapBoxStrokeStyle, minimapBoxRadius, minimapDrawLayer], () => viewRef.value?.updateOptions({ minimap: buildMinimapOptions() }))
4795
4806
  // 两种画法「好看」的门槛不一样:smooth 靠 alpha 往外淡,3% 就够;band 是硬边色块,抬到 15% 才留得住「大片浅色 + 环环相扣的热点」。
4796
4807
  // 换画法时把这两个跟着画法走的滑杆带回它自己的缺省,免得拿 smooth 的 3% 去看 band(整张图糊成一片橙)
4797
4808
  watch(heatmapDensityStyle, value => {
@@ -6142,7 +6153,7 @@ export const useGisViewDemo = () => {
6142
6153
  exportPartToFile, refreshExportSample, slimSample, sampleJson, refreshDataShape, scheduleDataShape,
6143
6154
  printDataShape, exportWholeImage, startCopyPick, copyPointByApi, copyThenPaste, waypointPhaseText,
6144
6155
  syncParallelGroups, copyParallelGroupRecords, handleWaypoint, MINIMAP_SIZES, minimapSize, minimapEnabled,
6145
- minimapShowLines, minimapExtent, minimapZoomable, minimapDragAction, minimapChannelCircuits, minimapZoomStep,
6156
+ minimapShowLines, minimapExtent, minimapExtentFollowCamera, minimapZoomMode, minimapFollowZoomScale, minimapDragAction, minimapChannelCircuits, minimapZoomStep,
6146
6157
  zoomMinimap, minimapRuleMode, minimapLayered, minimapLevels, minimapCascade, minimapPointSize,
6147
6158
  minimapLineWidth, minimapPointZoom, minimapLineZoom, minimapPointMapZoom, minimapLineMapZoom, minimapBoxFill,
6148
6159
  minimapBoxFillOpacity, minimapBoxStroke, minimapBoxStrokeWidth, minimapBoxStrokeStyle, minimapBoxRadius,
package/docs/index.html CHANGED
@@ -1323,7 +1323,7 @@
1323
1323
  { file: 'manual/14-视图与筛选.md', title: '手册 14 · 视图与筛选', blurb: '筛选 / 搜索 / 视图与背景', parent: 'GisView.md' },
1324
1324
  { file: 'manual/15-导出与集成.md', title: '手册 15 · 导出与集成', blurb: '导出 / 插件 / 自建界面', parent: 'GisView.md' },
1325
1325
  { file: 'manual/16-实例方法总表.md', title: '手册 16 · 实例方法总表', blurb: '实例方法主表', parent: 'GisView.md' },
1326
- { file: 'manual/17-参数速查.md', title: '手册 17 · 参数速查', blurb: '256 个成员逐个列全', parent: 'GisView.md' },
1326
+ { file: 'manual/17-参数速查.md', title: '手册 17 · 参数速查', blurb: '257 个成员逐个列全', parent: 'GisView.md' },
1327
1327
  { file: 'manual/18-逐方法示例.md', title: '手册 18 · 逐方法示例', blurb: '每个成员一段示例', parent: 'GisView.md' },
1328
1328
  { file: 'manual/19-读数与拖拽预览.md', title: '手册 19 · 读数与拖拽预览', blurb: '读数 / 定位 / 落点预览', parent: 'GisView.md' },
1329
1329
  { file: 'manual/20-性能与大数据.md', title: '手册 20 · 性能与大数据', blurb: '大数据开关与实测', parent: 'GisView.md' },
@@ -1399,7 +1399,7 @@
1399
1399
  { file: '../src/gis-view/README.md', title: 'src/gis-view/README.md', blurb: '源码导读与逐轮实现记录' }
1400
1400
  ]
1401
1401
  const PAGES = [
1402
- { href: 'api/index.html', title: 'API 速查(方法 + 示例)', blurb: '256 个实例成员 / 96 个 options,逐个带示例与复制' },
1402
+ { href: 'api/index.html', title: 'API 速查(方法 + 示例)', blurb: '257 个实例成员 / 96 个 options,逐个带示例与复制' },
1403
1403
  { href: 'playground/index.html', title: '调试演示页(粘代码就跑)', blurb: '左侧编辑、右侧真实实例 + GeoJSON 底图:内置示例与「这一份文档」的示例同在一个下拉,粘代码直接跑' },
1404
1404
  { href: 'guide/sample-document.html', title: 'sample-document.json 图解', blurb: '点数据看端点 / 拐点 / 分组骨架' },
1405
1405
  { href: 'steps/index.html', title: '从 0 手写分步 demo', blurb: '13 步,每步一个文件' },
@@ -1956,7 +1956,7 @@
1956
1956
  { badge: 'START', title: '第一次使用', desc: '引入、最小数据与首屏配置,先把图画出来。', meta: '01 快速开始 · 约 10 分钟', file: 'manual/01-快速开始.md' },
1957
1957
  { badge: 'BACKEND', title: '接后端与导出', desc: '编辑接口、导出回灌、插件与自建界面。', meta: '15 导出与集成', file: 'manual/15-导出与集成.md' },
1958
1958
  { badge: 'PERF', title: '十万级性能', desc: 'Worker 筛选 / 异步搜索、批量更新与质量档。', meta: '20 性能与大数据', file: 'manual/20-性能与大数据.md' },
1959
- { badge: 'API', title: '查方法与参数', desc: '实例方法、options 与可复制示例集中检索。', meta: 'API 速查 · 256 个成员', href: 'api/index.html', external: true }
1959
+ { badge: 'API', title: '查方法与参数', desc: '实例方法、options 与可复制示例集中检索。', meta: 'API 速查 · 257 个成员', href: 'api/index.html', external: true }
1960
1960
  ]
1961
1961
  const renderReadingPaths = () => `<section class='doc-paths' aria-label='推荐阅读路径'>` +
1962
1962
  `<div class='doc-paths__head'><strong>按任务选一条阅读路径</strong><span>第一次进来先看快速开始;带着明确目标时直接跳到对应章节。</span></div>` +
@@ -4,11 +4,12 @@
4
4
 
5
5
  - **底图**:`minimap.js` 把节点与线条直接写进一块离屏像素缓冲(`ImageData`),按「层级 + 颜色 + 线宽 + 透明度」分桶后再画 —— 绘制次数随分桶数走,不随数据量走;底图只在数据或筛选结果变化时重画;节点像素结果按「数据引用 + 尺寸 + 范围 + 变换 + 样式」缓存,同一份数据前后两次重画直接复用(十万点重铺 20ms → 0.3ms),数据范围也按数据对象进 `WeakMap`。
6
6
  - **视口框**:单独一个 DOM 元素,相机变化只改 `transform`(不重画底图),所以拖动小图不掉帧。
7
- - **规则**:小图可以有自己的 `pointRules` / `lineRules`(颜色、半径 / 线宽、形状、层级),不配时沿用主图规则但**只取颜色与层级**(主图的尺寸是主画布上的像素,搬到小图上会糊成一片)。
8
- - **交互**:视口框上按下 = 拖框;空白处长按(`longPressDelay`)= 视角跟随;`zoomable` 打开后滚轮按 `zoomStep` 缩放小图自身(锚点落光标处),Ctrl / ⌘ + 滚轮留给主图;`dragAction` 决定空白处拖动是平移小图还是框选(按住 Ctrl / ⌘ 自动切到另一个动作)。拖框与视角跟随带来的相机变化是「程序化」的:标签层不进交互态(`hideOnInteract` 不会把主视图的标签收起来),拖动结束补一次真实排版。每次真正有位移的手势结束时通过 `onInteraction` 发一条 `{ type, originalEvent, local, world, zoom, extent, camera, viewport }`(`type` 为 `box-drag` / `select` / `pan` / `zoom` / `wheel-main` / `long-press`);无位移的点击不发,`pointermove` 不逐帧回调,长按在触发跟随时发。
9
- - **范围**:`extent` 决定小图按哪段世界范围铺 —— `measure(data)` 量出可见数据的范围,`backgroundBoundsOf(background)` 量出底图范围(GeoJSON 用画出来的 `bounds`,图片用四角 quad 的包围盒),`auto`(默认)取两者的并集,`data` 只看数据(旧行为),`background` 只看底图。数据为空时 `measure()` 返回 `null`、不会当成「范围是正负一」;修掉的问题见 15.12。`auto` 的例外(本轮):相机正落在其中一份范围里、而那一份在并集里连 6 个像素都占不到时(数据还没映射到底图坐标系就是这样)只画正在看的那一份;`followFocusedExtent()` 在相机挪进 / 挪出这份小范围时补一次重画 —— 判断只用上一次重画缓存的两份范围,每帧几次比较。
10
- - **通道**:`channelCircuits: false` 时每个通道只画偏移最靠中间的那一根回线(底图分辨率本来就在一像素上下,多回线只会糊成一片)。
11
- - **背景**:默认 `background: 'inherit'`,直接拿引擎解析好的那份背景在 Canvas2D 上重画:底色铺满,图片按 quad 拆成两组三角形做仿射 + 裁剪(任意四边形都不会画歪),GeoJSON 面用奇偶规则填充(洞自动镂空)、纯线几何的区域再描一遍、点要素画成固定 1.2px 半径的小圆;底图的线宽(取配置值、最低 0.4px 保可见)与区域文字字号(固定档,夹在 5 ~ 12px)都是小图上的固定像素,不乘「小图比例 ÷ 主图比例」—— 缩放主视图时底图不会单独放大 / 缩小(本轮删掉了 `mapRatio`,见 15.17)。也可以给 `minimap.background` 单独配一份(结构与主视图的 `background` 完全一致,只是换 Canvas2D 画),`backgroundStyle` 是小图容器的 CSS 底色。区域文字先量「区域在小图上的屏幕矩形 ≥ 文字尺寸 × 0.85」再画,放不下就整条跳过(缩略图上那些小块本来也读不出来);能画的按区域从大到小排,和已经放下的标签重叠就跳过 —— 小图上几十个区域各画一行字只会叠成一团。
7
+ - **挂载与位置**:`minimap.container` 接受 `HTMLElement` 或选择器,把 `.gis-view__minimap` 根节点挂到外部宿主;换容器只 `append` 同一根节点,画布、缓存与事件监听都不重建,`destroy()` 从当前宿主摘掉。`offset` 是相对 `position` 角落的 `[x, y]` 位移(正数向右 / 向下),`updateOptions` 只改 `left` / `right` / `top` / `bottom`,因此整图拖动不需要重画小图;根节点通过 `getMinimapElement()` 暴露给外部手势。
8
+ - **规则**:小图可以有自己的 `pointRules` / `lineRules`(颜色、半径 / 线宽、形状、层级),不配时沿用主图样式链:颜色 / 描边色 / 层级取规则,节点尺寸与符号取引擎最终解析结果(点自身 > 主图规则 > `pointStyle`),线路线宽取样式链的原始线宽(不含主图整体倍数 / 缩放联动 / 热力倍数)。`inheritSize` / `inheritWidth` 标记的节点 / 线路再乘一次「小图 CSS 视口宽高 ÷ 主图 CSS 视口宽高」的较小比例(`inheritedSizeFactor()`),这一份只跟两边视口尺寸有关,主图相机缩放不会改继承要素的大小;小图自身放大时仍可按 `pointZoomScale` / `lineZoomScale` 叠加,`pointSizeScale` / `lineWidthScale` 对自有与继承要素统一生效,`pointMapZoomScale` / `lineMapZoomScale` 只作用于小图自有规则 / 样式。小图自己的尺寸不受视口比例这一步影响。圆形按主图的面积对齐系数还原真实直径;图片 / SVG 图标与自定义图元异步解码后单独盖在点层上,解码前先用小图基准尺寸的圆 / 方回落,完成后只失效点层并补一帧。
9
+ - **交互**:视口框上按下 = 拖框;空白处长按(`longPressDelay`)= 视角跟随;`zoomable` 三态互斥:`true` 手动缩放小图自身(滚轮按 `zoomStep`、锚点落光标处,Ctrl / ⌘ + 滚轮留给主图),`'follow'` 让整体范围跟随主图(`followZoomScale` 是主图倍数的指数,`setMinimapZoom` / `zoomMinimapBy` 不抢控制权),`false` 只缩放主图。`dragAction` 决定空白处拖动是平移小图还是框选,`box` 不依赖 `zoomable`;只有手动模式按住 Ctrl / ⌘ 才自动切到另一个动作。拖框与视角跟随带来的相机变化是「程序化」的:标签层不进交互态(`hideOnInteract` 不会把主视图的标签收起来),拖动结束补一次真实排版。每次真正有位移的手势结束时通过 `onInteraction` 发一条 `{ type, originalEvent, local, world, zoom, extent, camera, viewport }`(`type` 为 `box-drag` / `select` / `pan` / `zoom` / `wheel-main` / `long-press`);无位移的点击不发,`pointermove` 不逐帧回调,长按在触发跟随时发。
10
+ - **范围**:`extent` 决定小图按哪段世界范围铺 —— `measure(data)` 量出可见数据的范围,`backgroundBoundsOf(background)` 量出底图范围(GeoJSON 用画出来的 `bounds`,图片用四角 quad 的包围盒),`auto`(默认)取两者的并集,`data` 只看数据(旧行为),`background` 只看底图。数据为空时 `measure()` 返回 `null`、不会当成「范围是正负一」;修掉的问题见 15.12。`extentFollowCamera` 默认 `false`,`auto` 始终用并集,外围内容消失不会让包围盒与视口框跳动;打开后才有例外:相机正落在其中一份范围里、而那一份在并集里连 6 个像素都占不到时(数据还没映射到底图坐标系就是这样)只画正在看的那一份;`followFocusedExtent()` 在相机挪进 / 挪出这份小范围时补一次重画 —— 判断只用上一次重画缓存的两份范围,每帧几次比较。
11
+ - **通道**:`channelCircuits: false` 时每个分组按当前可见成员逐顶点平均成一根代表线,双回线因此落在两根正中间,三回线落在中间那条;只剩一条可见成员时就是它自己,顶点数对不上时退回第一条可见成员(底图分辨率本来就在一像素上下,多回线只会糊成一片)。
12
+ - **背景**:默认 `background: 'inherit'`,直接拿引擎解析好的那份背景在 Canvas2D 上重画:底色铺满,图片按 quad 拆成两组三角形做仿射 + 裁剪(任意四边形都不会画歪),GeoJSON 面用奇偶规则填充(洞自动镂空)、纯线几何的区域再描一遍、点要素画成固定 1.2px 半径的小圆;底图的线宽(取配置值、最低 0.4px 保可见)与区域文字字号(固定档,夹在 5 ~ 12px)都是小图上的固定像素,不乘「小图视口 ÷ 主图视口」的比例 —— 缩放主视图时底图不会单独放大 / 缩小(本轮删掉了 `mapRatio`,见 15.17)。也可以给 `minimap.background` 单独配一份(结构与主视图的 `background` 完全一致,只是换 Canvas2D 画)。`backgroundStyle` 显式设置时由容器 CSS 接管底色:继承或独立的底图仍画图片与 GeoJSON,但画布底色改成透明,不再用主图的不透明底色盖住容器;不写时仍按主图底色铺满。区域文字先量「区域在小图上的屏幕矩形 ≥ 文字尺寸 × 0.85」再画,放不下就整条跳过(缩略图上那些小块本来也读不出来);能画的按区域从大到小排,和已经放下的标签重叠就跳过 —— 小图上几十个区域各画一行字只会叠成一团。
12
13
  - **扩展层**:`layers` 接收一组 `{ id?, order?, placement?, enabled?, draw }`。`placement: 'below-data'` 在内置线路 / 节点之前执行,适合网格、分区底色;`'above-data'`(缺省)在数据之后执行,适合指北针、业务标记。两段内部各按 `order` 从小到大,相同或缺省 `order` 按数组顺序。`setOptions` 时把过滤、排序与分段结果缓存成两条数组,绘制帧只遍历当前段,不再每帧 `map` / `filter` / `sort`。每个回调独立 `save` / `restore`,一层改过的画布状态不会漏进下一层;`enabled: false` 可临时关掉一层,旧的单层 `draw` 保持兼容并固定最后执行,回调里的 `stage` 为 `'overlay'`。这样网格、指北针、业务标记可以各自维护,不必把所有内容堆进一个钩子。
13
14
  - **清晰度与开销**:小图画布用自己的一份倍率(`clamp(devicePixelRatio × 宿主显示比例, 1, maxPixelRatio)`,`maxPixelRatio` 默认 4、夹在 1 ~ 4),主视图为全屏帧率按像素预算压低渲染倍率时,小图不受影响;小图尺寸大或高分屏填充压力高时可以把上限降到 1 ~ 2。容器另有 `contain: layout paint` 与 `isolation: isolate`,把布局与重绘影响收在自身。
14
15
 
@@ -284,11 +284,13 @@
284
284
 
285
285
  10 万点 / 6 万线里删掉一条线,`removeLine` 实测 61 ms(最快一次):`removeLines` 收尾走 `applyOptions()` → 引擎 `setOptions({ points, lines, channels, hiddenPoints, hiddenLines })`,补丁里带了 `points`,于是整份 `buildScene()` —— 十万个节点重解析一遍、圆点与贴图批次整份重铺,纯粹陪跑(删一条线一个节点都没动)。收益是把点那一半摘掉(约 30 ms;其余是线路侧的重编号:删掉中间一条线,后面每条线的下标都往前挪一格,`lineSegmentSlots` / `decorationRanges` / 选中镜像 / 通道成员表全都要跟着重排)。要做的话得让 `setOptions` 认得「节点这一半逐元素还是上一轮那批对象」(`patch.points` 与内部副本逐元素比身份,十万次比较约 0.3 ms),再让 `resolveFeatures` 支持只跑线路那一半(符号预热账本 / 热力值域 / 点层序这些与点绑定的账要原样留着)。这一条按「值不值得做」排在建增量通道(16.35 末尾那条)之后:删一条线的场景不多,而它牵扯的是数据更新的主路径 —— **本轮(15.65)把它做掉了:判定改成「逐元素同引用 + 解析输入快照」两步,只跳过点那一半;`removeLine` 10 万点 / 6 万线 50.7 → 41.3 ms。** 剩下那 ~40 ms 是线路侧的整份重编号,见 16.37。
286
286
 
287
- ### 16.37 线路那一半也整半跳过(候选,本轮量过)
287
+ ### 16.37 线路那一半也整半跳过(已在当前轮落地)
288
288
 
289
- 与 15.65 做掉的「点那一半」同构:`patch.lines` 与内部那一份**逐元素同引用**(六万次身份比较约 0.2 ms)、`patch.hiddenLines` 内容没变、`lineStyleInputsUnchanged()`(线基准样式 / 规则 / 效果 / 线方向 / 热力图)与线路状态样式都没变时,`resolveLines` 那一趟与它的索引 / 虚线登记 / 层序 / 装饰锚点几趟可以整半跳过。
289
+ 与 15.65 做掉的「点那一半」同构。判定分两层:先在 `applyOptions` 收配置前确认 `patch.lines` 与内部那一份**逐元素同引用**、`patch.hiddenLines` 内容没变、`patch.channels` 也逐条同引用、通道宽度 / 节点尺寸 / 整体倍率 / `lineFollowsNode` 这些会改车道与回线折线的输入没变,并且没有 `appendedPoints` / `appendedLines`;收完配置后再用快照确认线基准样式 / 规则 / 效果 / 线方向 / 热力图、节点基准样式与线路悬停 / 选中 / 多选状态样式都没变。`forceResolve`、交互中、销毁后以及增量删除路都直接回退整份重解析。
290
290
 
291
- 收益:隔离基准里六万条线的 `resolveLines` 约 17 ms(见 15.65),加上索引与层序那几趟约 10 ms;**只对「线路数据原样再交一遍」这种补丁生效**(`updateOptions({ lines })` 里内容真的换了、`removeLine` 这种下标平移的都不算),排线路批次那一趟(约 12 ms)也仍然要跑。代价是又要在数据更新的主路径上加一层判定与一笔「线路侧定向更新把数据写回内部数组」的账(与 `pointHalfOutOfSync` 同一类,见 `updateLine`)。按「收益面窄 + 动主路径」排在这里:真要做的话,先量一量业务里「同一份线路数据反复回灌」到底占多少。
291
+ 命中时 `resolveFeatures` 跳过 `resolveLines`、`lineByIndex` / 最大半线宽 / 虚线图案槽位 / 线路装饰与效果标记 / 布局层序 / 线路包围盒这几趟,直接沿用上一轮线路那一半的读数;排线路批次那一趟仍然照常跑。线路半包围盒不是只在整份解析时记账:追加补线会把新路径并进来,拖点 / 拐点 / 通道骨架这类增量改线也会在 `applyResolvedLinePath` 里把新路径并进来;还没有可信读数时记 `lineHalfOutOfSync`,下一轮放弃跳过,避免沿用旧范围。
292
+
293
+ 收益只在「同一批线路对象原样再交一遍」的补丁上兑现(内容真的换了、`removeLine` 这种下标平移的路都不算)。`selftest-perf.html` 的 20000 点 / 8000 线回灌基准(同一批对象连交 5 次取中位)由 **14.3 ms** 降到 **6.3 ms**;同页新增三条断言,锁定条数与线路实例数不变、通道声明没换时派生折线原样保留、换通道骨架时线路几何必须重推。性能页当前为 **167 passed / 0 failed**。
292
294
 
293
295
 
294
296
  ### 16.38 `removePoints` 的反向增量(候选 → 已在 15.66 落地)
@@ -251,6 +251,18 @@
251
251
  - **编辑类开关缺省关闭**(详见 15.39 与 15.25):`features` 与详细配置都没写时,框选 / 拖拽 / 拐点 / 复制 / 合并 / 新增通道 / 通道右键菜单一律关着 —— 缺省口径=只读浏览;`features: 'edit'` 一次全开,单开写 `draggable: true` / `boxSelect: true` / `waypointEditable: true` / `copy: {…}` / `merge: {…}` 或 `features: { drag: true }`。自测页与 demo 里要用这些交互的地方都显式打开了开关,因此断言与画面语义不变(变的只是「什么都不写」时的行为,这正是需求本身)。
252
252
  - **验证**:十个自测页在真时间下逐页跑完全绿(`selftest` 26 / `selftest-actions` 96 / `selftest-effects` 85 / `selftest-labels` 90 / `selftest-demo` 112 / `selftest-demos` 61 / `selftest-scale` 42 / `selftest-perf` 72 / `selftest-full` 68 / `selftest-features` 264),`npx tsc --noEmit` 0 错,`node docs/steps/check-doc-coverage.mjs` 无缺口,`npm run build` 通过。
253
253
 
254
+ #### 第十六轮:同一批点线原样回灌,线路那一半整半复用
255
+
256
+ 上一轮把节点那一半在「逐元素同引用」时整半跳过;这一轮把同一套判据补到线路侧,并额外核对隐藏集合、通道声明、通道车道输入与线路样式快照。命中后不再重跑 `resolveLines` 与其索引 / 虚线槽位 / 层序 / 包围盒,追加补线和增量改线则把新路径并进线路半包围盒,防止下一次回灌沿用旧范围。
257
+
258
+ `selftest-perf.html` 用 20000 点 / 8000 线、同一批对象连续回灌 5 次,取中位:
259
+
260
+ | 场景 | 优化前 | 优化后 |
261
+ | --- | --- | --- |
262
+ | 同一批点线原样回灌(20000 点 / 8000 线) | 14.3 ms | **6.3 ms** |
263
+
264
+ 优化后的 5 次读数依次是 5.6 / 6.3 / 6.3 / 6.4 / 10.7 ms;同页新增三条断言分别核对条数与线路实例数不变、通道声明未变时派生折线原样保留、换通道骨架时线路几何必须重推。该快路只对原样回灌生效,内容真的换了或删除导致下标平移时仍走原来的整份 / 增量路径。
265
+
254
266
  ### WebGPU 值不值得上
255
267
 
256
268
  | 维度 | 现状 | WebGPU 能带来的变化 |