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