[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:zh:jin-yong-sect-map-browser":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# 在中国地图上浏览金庸门派\n\n## 这个示例构建了什么\n\n这个示例构建了一个带有主题风格的浏览视图，把武林门派标记放在内置的中国 SVG 地图之上。画布被组织为三个相互配合的区域：左侧是地图，中间是门派侧边栏列表，右侧是按条件显示的详情卡片。\n\n用户既可以点击地图上的标记点，也可以点击侧边栏卡片来切换当前门派。该选择会重新绘制紫色高亮连接线，并切换详情内容，而不会重建整个场景。这个示例的重点不是地理分析，也不是图编辑，而是一个由固定图节点和普通 DOM 端点共同构成、以地图为底板的主从详情浏览器。\n\n## 数据是如何组织的\n\n源数据来自 `my-data.ts` 中本地的 `AllMenPai` 数组。每条记录都遵循 `GroupDoc` 结构，并且可以包含：\n\n- 稳定的 `id`\n- 用于显示的 `title`\n- 诸如 `masters`、`martialArts`、`descriptions` 以及可选 `moreInfo` 这样的长文本详情字段\n- 可选的 `x` 和 `y` 坐标\n\n只有带坐标的记录才会被投影为 relation-graph 节点。没有坐标的记录仍然会出现在侧边栏中，也依然可以驱动连接线逻辑和详情内容，因此这份数据集可以同时混合类似摘要的条目和基于地图的条目。\n\n在渲染任何内容之前，代码会先从 `AllMenPai` 中筛选出带有 `x` 和 `y` 的条目，将这些坐标按 `0.35` 缩放，再转换成 `JsonNode` 记录。随后，同一组源 `id` 会在两个地方复用：一处是如 `location-1` 这样的节点 id，另一处是如 `doc-group-1` 这样的 DOM 端点 id。这里没有 `setJsonData(...)` 或 `doLayout(...)` 步骤。场景是通过 `addNodes(...)` 以命令式方式组装出来的，而同一批数据对象也会被详情面板复用。\n\n在真实系统中，这种结构可以表示区域办公室、历史遗址、零售网点、服务区域，或任何以地图为底板的目录场景，其中有些列表项表示分类，另一些则表示固定点位。\n\n## relation-graph 是如何使用的\n\n图使用固定布局模式运行，配置为 `layoutName: 'fixed'`，因此每个可见的地图标记都会停留在与中国 SVG 背景相匹配的明确坐标上。`debug` 被关闭，组件通过 `RGHooks.useGraphInstance()` 获取运行时控制接口。\n\n这个示例没有加载图 JSON 模型，而是直接使用实例 API：\n\n- `addNodes(...)` 在挂载后插入带定位的门派标记\n- `zoomToFit()` 让初始画布适配视口\n- `clearFakeLines()` 和 `addFakeLines(...)` 在选择变化时替换当前活动的连接线集合\n\n`RGProvider` 提供图上下文，`RelationGraph` 同时承载图层和由画布组合而成的 UI。这个示例没有声明显式的画布 slot 组件，但放在 `RelationGraph` 内部的内容会被当作画布内容使用：包括中国 SVG、侧边栏卡片堆栈，以及按条件显示的详情卡片，它们都位于图画布区域内。\n\n这里最关键的两个 relation-graph 扩展点是：\n\n- `RGSlotOnNode` 用可点击的 `MapPin` 标记和数字标签替换默认节点主体\n- `RGConnectTarget` 把每个侧边栏卡片变成 fake line 可以连接的 DOM 端点\n\n这些 fake line 被配置为从左到右的曲线连接线，并使用 `RGInnerConnectTargetType.Node`、`RGJunctionPoint.lr` 和 `RGLineShape.StandardCurve`。样式刻意保持简单，但对整个场景很重要：SCSS 文件为画布加入了斜向网格背景，把中国 SVG 设为绿色，并让节点主体保持透明，这样自定义的标记 slot 就会成为可见的标记本体。\n\n这个示例是一个浏览器，而不是编辑器。用户浏览的是预先准备好的记录和临时高亮状态，但不会创建节点、拖动端点，也不会持久化更改。\n\n## 关键交互\n\n- 组件在挂载后通过添加固定节点、适配视口，并在短暂延时后选中 group `0` 来完成初始化。\n- 点击地图标记会调用与侧边栏相同的 `onGroupClick(...)` 流程，因此两个区域会共同更新同一个 `checkedGroup` 状态。\n- 点击侧边栏卡片会改变高亮卡片样式、重新绘制当前 fake line，并切换详情面板内容。\n- 大多数选择都会从侧边栏卡片连接到一个地图节点，但 group `0` 和 `6` 会切换到扇出模式，把一个侧边栏条目连接到每一个带坐标的节点。\n- 详情面板只会在某个 group 被选中时显示，并展示所选记录中的 masters、martial arts、descriptions 以及可选的额外文本。\n\n## 关键代码片段\n\n这个片段说明，地图标记并不是硬编码成图 JSON 的。它们是从带坐标的本地记录中投影出来的，经过缩放，并附带源 group id。\n\n```tsx\nconst nodes: JsonNode[] = AllMenPai.filter(g => g.x && g.y).map(g => ({\n    id: 'location-' + g.id,\n    text: '' + g.id,\n    x: g.x * 0.35,\n    y: g.y * 0.35,\n    data: {\n        myGroupId: g.id\n    }\n}));\n```\n\n这个片段证明初始化是命令式的：示例会插入节点、适配视口，并在启动时预选 group `0`。\n\n```tsx\ngraphInstance.addNodes(nodes);\nsetTimeout(() => {\n    graphInstance.zoomToFit();\n    onGroupClick('0');\n}, 200);\n```\n\n这个片段展示了依赖选择状态的连接线策略。group `0` 和 `6` 会向所有已定位节点扇出，而其他有效选择都只绘制一条连接线。\n\n```tsx\nif (groupId === '0' || groupId === '6') {\n    AllMenPai.filter(g => g.x && g.y).forEach((item, idx) => processItem(item.id, idx));\n} else if (groupId) {\n    processItem(groupId, 0);\n}\n\ngraphInstance.clearFakeLines();\ngraphInstance.addFakeLines(myFakeLines);\n```\n\n这个片段展示了 relation-graph 节点如何被渲染为地图标记，并把点击事件重新路由回共享的选择状态。\n\n```tsx\n\u003CRGSlotOnNode>\n    {({ node, checked }: RGNodeSlotProps) => {\n        return (\n            \u003Cdiv\n                className={`pointer-events-auto cursor-point c-i-location ${checked ? 'c-i-location-active' : ''}`}\n                onClick={() => onGroupClick(node.data.myGroupId)}\n            >\n                \u003CMapPin className='transform translate-y-[-25px] translate-x-[-5px]' size={24} />\n                \u003Cdiv className=\"absolute left-[50%] top-1 w-6 text-center transform translate-x-[-50%]\">{node.text}\u003C/div>\n            \u003C/div>\n        );\n    }}\n\u003C/RGSlotOnNode>\n```\n\n这个片段展示了普通的侧边栏卡片如何变成 relation-graph 可以连接的 DOM 端点。\n\n```tsx\n\u003CRGConnectTarget\n    targetId={`doc-group-${group.id}`}\n    junctionPoint={RGJunctionPoint.lr}\n    disableDrag={true}\n>\n    \u003Cdiv\n        className={`px-3 py-2 cursor-pointer bg-green-200 hover:bg-green-300 rounded-lg ${checkedGroup?.id === group.id ? ' bg-green-600 text-white' : ''}`}\n        onClick={() => { onGroupClick(group.id); }}\n    >\n        {group.title}\n    \u003C/div>\n\u003C/RGConnectTarget>\n```\n\n这个片段展示了右侧面板如何直接由选中的数据记录驱动，而不是来自独立的详情获取逻辑。\n\n```tsx\n{checkedGroup && \u003Cdiv className=\"grow p-5 bg-white border rounded-2xl flex flex-col gap-1 whitespace-pre-wrap\">\n    \u003Cp className=\"py-1 text-sm\">Masters:\u003C/p>\n    \u003Cp className=\"py-1 text-xs bg-gray-100 rounded p-2\">{checkedGroup.masters}\u003C/p>\n    \u003Cp className=\"py-1 text-sm\">Martial Arts:\u003C/p>\n    \u003Cp className=\"py-1 text-xs bg-gray-100 rounded p-2\">{checkedGroup.martialArts}\u003C/p>\n\n    {checkedGroup.descriptions && \u003Cdiv className=\"py-1 text-xs bg-gray-100 rounded p-2\">\n        {\n            checkedGroup.descriptions.map((text, idx) => (\n```\n\n## 这个示例的独特之处\n\n对比数据表明，这个示例与其他基于地图的端点示例接近，但强调点并不相同。它的独特性在于，在一个紧凑场景里同时组合了五个元素：内置的中国 SVG 背景、固定位置的图节点标记、侧边栏 `RGConnectTarget` 端点、基于选择状态重建的 fake line，以及带描述信息的主从详情面板。因此，它读起来更像一个经过策划的参考看板，而不是通用的连接线演示。\n\n与 `element-line-edit` 相比，这个示例不仅仅是一对一的列表到位置高亮，还加入了信息量更大的详情卡片，以及针对 group `0` 和 `6` 的特殊扇出模式。与 `inventory-structure-diagram` 相比，它把地图锚点保留为真正的 relation-graph 节点，并通过 `RGSlotOnNode` 进行渲染，而不是主要依赖画布 slot DOM 锚点。\n\n与 `element-connect-to-node` 和 `interest-group` 相比，这里最值得复用的经验并不是端点策略对比，也不是双侧成员关系浏览，而是使用同一个选择状态，让侧边栏强调样式、连接线集合和长文本详情内容始终围绕主题地图场景保持同步。\n\n## 这种模式还适用于哪些地方\n\n这种模式非常适合这样的界面：UI 的一部分是普通的文档式内容，另一部分则是固定空间位置的图层。\n\n- 区域办公室浏览器：分类卡片连接到品牌地图上的一个或多个分支机构标记\n- 博物馆、校园或展览导览：选中的主题会高亮多个预设位置，并打开长篇参考说明\n- 设施仪表板：列表记录连接到平面图或示意图背景上的固定设备点位\n- 历史或文化图集：选择某项内容时显示描述性内容，同时把摘要条目连接到多个带注释的位置\n\n可复用的核心思路是：把空间锚点保留为图节点，把浏览 UI 保留为 DOM，并且只重绘与当前选择相匹配的那组连接线。\n",false,500,1782615414094]