JavaScript is required

金庸武侠门派地图联动浏览

这个示例在内置中国 SVG 地图上叠加固定位置的门派标记点,并用假连线把侧边栏选中的卡片连接到一个或多个标记。相同的选中状态还会驱动详情面板,因此它更适合作为地图底图下的主从联动浏览参考,而非地理分析或图编辑示例。

在中国地图上浏览金庸门派

这个示例构建了什么

这个示例构建了一个带有主题风格的浏览视图,把武林门派标记放在内置的中国 SVG 地图之上。画布被组织为三个相互配合的区域:左侧是地图,中间是门派侧边栏列表,右侧是按条件显示的详情卡片。

用户既可以点击地图上的标记点,也可以点击侧边栏卡片来切换当前门派。该选择会重新绘制紫色高亮连接线,并切换详情内容,而不会重建整个场景。这个示例的重点不是地理分析,也不是图编辑,而是一个由固定图节点和普通 DOM 端点共同构成、以地图为底板的主从详情浏览器。

数据是如何组织的

源数据来自 my-data.ts 中本地的 AllMenPai 数组。每条记录都遵循 GroupDoc 结构,并且可以包含:

  • 稳定的 id
  • 用于显示的 title
  • 诸如 mastersmartialArtsdescriptions 以及可选 moreInfo 这样的长文本详情字段
  • 可选的 xy 坐标

只有带坐标的记录才会被投影为 relation-graph 节点。没有坐标的记录仍然会出现在侧边栏中,也依然可以驱动连接线逻辑和详情内容,因此这份数据集可以同时混合类似摘要的条目和基于地图的条目。

在渲染任何内容之前,代码会先从 AllMenPai 中筛选出带有 xy 的条目,将这些坐标按 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.NodeRGJunctionPoint.lrRGLineShape.StandardCurve。样式刻意保持简单,但对整个场景很重要:SCSS 文件为画布加入了斜向网格背景,把中国 SVG 设为绿色,并让节点主体保持透明,这样自定义的标记 slot 就会成为可见的标记本体。

这个示例是一个浏览器,而不是编辑器。用户浏览的是预先准备好的记录和临时高亮状态,但不会创建节点、拖动端点,也不会持久化更改。

关键交互

  • 组件在挂载后通过添加固定节点、适配视口,并在短暂延时后选中 group 0 来完成初始化。
  • 点击地图标记会调用与侧边栏相同的 onGroupClick(...) 流程,因此两个区域会共同更新同一个 checkedGroup 状态。
  • 点击侧边栏卡片会改变高亮卡片样式、重新绘制当前 fake line,并切换详情面板内容。
  • 大多数选择都会从侧边栏卡片连接到一个地图节点,但 group 06 会切换到扇出模式,把一个侧边栏条目连接到每一个带坐标的节点。
  • 详情面板只会在某个 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 06 会向所有已定位节点扇出,而其他有效选择都只绘制一条连接线。

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 06 的特殊扇出模式。与 inventory-structure-diagram 相比,它把地图锚点保留为真正的 relation-graph 节点,并通过 RGSlotOnNode 进行渲染,而不是主要依赖画布 slot DOM 锚点。

element-connect-to-nodeinterest-group 相比,这里最值得复用的经验并不是端点策略对比,也不是双侧成员关系浏览,而是使用同一个选择状态,让侧边栏强调样式、连接线集合和长文本详情内容始终围绕主题地图场景保持同步。

这种模式还适用于哪些地方

这种模式非常适合这样的界面:UI 的一部分是普通的文档式内容,另一部分则是固定空间位置的图层。

  • 区域办公室浏览器:分类卡片连接到品牌地图上的一个或多个分支机构标记
  • 博物馆、校园或展览导览:选中的主题会高亮多个预设位置,并打开长篇参考说明
  • 设施仪表板:列表记录连接到平面图或示意图背景上的固定设备点位
  • 历史或文化图集:选择某项内容时显示描述性内容,同时把摘要条目连接到多个带注释的位置

可复用的核心思路是:把空间锚点保留为图节点,把浏览 UI 保留为 DOM,并且只重绘与当前选择相匹配的那组连接线。