JavaScript is required

节点与连线点击高亮

这个示例构建只读人物关系图:点击节点高亮关联连线,点击连线只保留该关系的两端节点,点击画布恢复基线样式。它是可逆的交互驱动样式控制参考,包含人像节点插槽、原始样式保存,以及在检查前先稳定收敛的力导布局。

人物关系图中的节点与连线点击高亮

本示例构建了什么

本示例在一个全高画布上构建了一个只读的人物关系图。每个人都以圆形头像配合彩色姓名标签的形式呈现,图谱会先以力导布局启动,再稳定下来,进入便于查看的静态视图。

核心行为是由点击驱动的强调效果。点击节点会将与其相连的所有关系高亮为红色。点击连线会高亮该连线、淡化无关节点,并且只让两个端点节点保持完全可见。点击空白画布会恢复原始样式。这个示例最有价值的地方在于,这三种状态共享同一套可逆的重置流程,而不是通过重新构建图谱来实现。

数据如何组织

数据来自 mock-data-api.ts 中本地的 jsonData 对象,并通过异步的 fetchJsonData() 包装函数暴露,该函数会在 200 ms 后返回结果。返回数据使用标准的 RGJsonData 结构,包含 rootIdnodeslines。在这个示例中,数据集包含 21 个节点和 42 条连线。节点存储 idtextcolorborderColor,以及一个包含头像 URL 和人物属性的 data 对象。连线存储 fromtotextcolorfontColor,以及诸如 friendcouplerelativecollusion 这样的 data.type 值。

在调用 setJsonData() 之前还有一段重要的预处理逻辑。MyGraph.tsx 会把每个节点的原始颜色和边框宽度后备值复制到 node.data 中,把每条连线的原始颜色和宽度复制到 line.data 中,并为原本没有 id 的连线生成形如 line-${index} 的合成 id。正是这一步准备工作,才让后续的重置和重新高亮操作变得可靠。在真实应用中,这种结构同样可以表示调查对象、账户关系、供应商依赖,或者任何既需要对节点也需要对连接进行可逆强调的实体图谱。

relation-graph 的使用方式

index.tsx 使用 RGProvider 包装整个示例,而 MyGraph.tsx 则通过 RGHooks.useGraphInstance() 获取当前实例。这个示例用该实例覆盖了完整生命周期:通过 setJsonData() 加载数据,通过 stopAutoLayout() 停止力导布局,通过 zoomToFitWithAnimation() 将最终视图自动缩放到合适范围,通过 getNodes()getLines() 查询当前图谱元素,通过 getRelatedLinesByNode() 查找节点邻接连线,通过 getLinkByLine() 将被点击的连线解析回它的两个端点,并通过 updateNode()updateLine()updateLineData() 在运行时应用临时样式。

图谱选项定义的是一个紧凑的查看器配置,而不是编辑器。节点默认使用圆形,边框宽度为 2,连线默认使用 RGLineShape.StandardStraight,边的锚点使用 RGJunctionPoint.border。布局采用 force,但图谱不会一直保持运动状态:数据加载完成后,示例会等待一秒,停止自动布局,然后执行一次带动画的适配视图。这让图谱更像一个稳定的人物关系查看器,而不是一个持续运动的模拟。

主要的视觉覆盖点是 RGSlotOnNode。它没有使用默认节点主体,而是通过插槽渲染头像卡片,使用 node.data.icon 作为背景图,并将节点文本显示在下方,其颜色取自节点当前的 color。SCSS 文件主要用于配合这种自定义节点展示方式,包括设置头像尺寸、将其裁成圆形以及为标题添加样式。大部分交互样式并不是来自 CSS 类,而是来自 relation-graph 实例在运行时的更新。

关键交互

  • 点击节点会先重置之前的强调状态,然后将所有相关连线高亮为红色并加粗描边。
  • 点击连线会先重置之前的强调状态,将所有节点透明度降低到 0.2,把所选连线高亮为红色,并且只恢复两个端点节点的完全不透明。
  • 点击画布会执行共享的重置逻辑,并清除图谱的选中状态。
  • 力导布局会先短暂运行到基本稳定,然后被冻结,因此后续交互发生在稳定视图上,而不是移动中的画面上。

关键代码片段

这段代码展示了预处理步骤:在图谱加载前先保存节点和连线的基础样式。

const myJsonData: RGJsonData = await fetchJsonData();
myJsonData.nodes.forEach(n => {
    if (!n.data) n.data = {};
    n.data.orignColor = n.color;
    n.data.originBorderWidth = n.originBorderWidth || 3;
});
myJsonData.lines.forEach(l => {
    if (!l.data) l.data = {};
    l.data.orignColor = l.color;
    l.data.orignLineWidth = l.lineWidth || 2;
});

这段代码说明了,缺失的连线 id 会在加载前被补齐,同时力导布局会在第一秒结束后被显式冻结。

myJsonData.lines.forEach((line, index) => {
    if (!line.id) line.id = `line-${index}`;
});

await graphInstance.setJsonData(myJsonData);
setTimeout(async() => {
    graphInstance.stopAutoLayout();
    await graphInstance.zoomToFitWithAnimation();
}, 1000);

这个共享的重置例程是可逆强调模式的核心:它会根据已保存的值恢复节点透明度、节点颜色、边框宽度、连线颜色、标签颜色和连线宽度。

const resetNodeAndLineStyle = () => {
    graphInstance.getNodes().forEach(node => {
        const originColor = node.data?.orignColor || '#cccccc';
        const originBorderWidth = node.data?.originBorderWidth || 2;
        graphInstance.updateNode(node.id, {
            opacity: 1,
            color: originColor,
            borderWidth: originBorderWidth,
        });
    });
    graphInstance.getLines().forEach(line => {
        const originColor = line.data?.orignColor || '#cccccc';
        const originWidth = line.data?.orignLineWidth || 2;
        graphInstance.updateLine(line.id, {
            color: originColor,
            fontColor: originColor,
            lineWidth: originWidth
        });
    });
};

这个节点点击处理函数展示了如何通过邻接关系查询,将一次点击某个人转换为一组相关连线的高亮。

const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
    resetNodeAndLineStyle();
    const relatedLines = graphInstance.getRelatedLinesByNode(nodeObject);
    relatedLines.forEach(line => {
        if (!line.data?.orignColor) {
            graphInstance.updateLineData(line.id, {
                orignColor: line.color,
                orignLineWidth: line.lineWidth
            });
        }
        graphInstance.updateLine(line.id, {
            color: '#ff0000',
            fontColor: '#ff0000',
            lineWidth: 3
        });
    });
};

这一段连线点击流程展示了“焦点加上下文”的行为:先淡化所有节点,然后只为被点击连线的两个端点恢复可见性。

const onLineClick = (line: RGLine, linkObject: RGLink, $event: RGUserEvent) => {
    resetNodeAndLineStyle();
    graphInstance.getNodes().forEach(node => {
        graphInstance.updateNode(node, {
            opacity: 0.2
        });
    });
    graphInstance.updateLine(line, {
        color: '#ff0000',
        fontColor: '#ff0000',
        lineWidth: 3
    });
    const link = graphInstance.getLinkByLine(line);

这段代码补全了连线点击行为:只让两个关联端点节点恢复为完全不透明。

if (link) {
    [link.fromNode, link.toNode].forEach(node => {
        graphInstance.updateNode(node, {
            opacity: 1
        });
    });
}

这个插槽片段说明,该示例用带头像背景的内容和一个颜色随节点状态变化的标题,替换了默认节点主体。

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="h-12 w-12">
            <div
                className="my-node-style"
                style={{ backgroundImage: `url(${node.data?.icon})` }}
            />
            <div className="c-node-name" style={{ color: node.color }}>
                {node.text}
            </div>
        </div>
    )}
</RGSlotOnNode>

这个示例的独特之处

对比数据表明,这个示例之所以突出,是因为它是一个专注于可逆点击样式的参考实现。它最清晰的独特特征在于,在同一张画布中同时结合了 getRelatedLinesByNode(...)getLinkByLine(...),因此点击节点和点击连线可以产生两种不同的查看行为,而无需切换数据集或模式。

line-hightlight-proline-hightlight 相比,这个示例同时演示了由节点触发和由连线触发的强调,而不是只聚焦一种触发方式。与 scene-relationshipsearch-and-focus 相比,它更强调画布内的可逆样式处理,而不是外部筛选、搜索面板或工具栏驱动的浏览方式。对比数据还指出了一个不那么显眼的细节:这个示例会预先保存节点和连线的基础样式,并提前生成连线 id,这使得重复执行“高亮-重置”循环时无需重新加载图谱也依然安全。

在基于头像的人物关系查看器中,它也特别强调红色关系高亮与点击画布后的恢复机制。因此,对于那些需要在稳定图谱视图上实现临时检查状态,而不是构建更大控制面板或完整调查看板的开发者来说,它是一个更合适的起点。

这种模式还适用于哪里

这种模式也很适合用于调查图谱、欺诈或风险地图、依赖关系查看器、事件关联图,以及合作伙伴或供应商网络。在这些场景中,用户需要先查看某个实体的局部关系,然后快速将画布恢复初始状态。尤其是在节点点击和连线点击需要表达不同含义时,例如“显示与该实体相连的全部内容”与“只聚焦这条具体关系”,这种模式会特别有用。

同样的结构也适用于只读的组织架构图或账户网络查看器,这类场景通常希望布局稳定一次后,就作为一个稳定的查看表面使用。这个示例使用的是基于头像的人物数据,但交互模式本身是通用的:保留基础样式,通过实例 API 应用临时强调,并显式维护重置行为。