节点与连线点击高亮
这个示例构建只读人物关系图:点击节点高亮关联连线,点击连线只保留该关系的两端节点,点击画布恢复基线样式。它是可逆的交互驱动样式控制参考,包含人像节点插槽、原始样式保存,以及在检查前先稳定收敛的力导布局。
人物关系图中的节点与连线点击高亮
本示例构建了什么
本示例在一个全高画布上构建了一个只读的人物关系图。每个人都以圆形头像配合彩色姓名标签的形式呈现,图谱会先以力导布局启动,再稳定下来,进入便于查看的静态视图。
核心行为是由点击驱动的强调效果。点击节点会将与其相连的所有关系高亮为红色。点击连线会高亮该连线、淡化无关节点,并且只让两个端点节点保持完全可见。点击空白画布会恢复原始样式。这个示例最有价值的地方在于,这三种状态共享同一套可逆的重置流程,而不是通过重新构建图谱来实现。
数据如何组织
数据来自 mock-data-api.ts 中本地的 jsonData 对象,并通过异步的 fetchJsonData() 包装函数暴露,该函数会在 200 ms 后返回结果。返回数据使用标准的 RGJsonData 结构,包含 rootId、nodes 和 lines。在这个示例中,数据集包含 21 个节点和 42 条连线。节点存储 id、text、color、borderColor,以及一个包含头像 URL 和人物属性的 data 对象。连线存储 from、to、text、color、fontColor,以及诸如 friend、couple、relative 或 collusion 这样的 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-pro 和 line-hightlight 相比,这个示例同时演示了由节点触发和由连线触发的强调,而不是只聚焦一种触发方式。与 scene-relationship 和 search-and-focus 相比,它更强调画布内的可逆样式处理,而不是外部筛选、搜索面板或工具栏驱动的浏览方式。对比数据还指出了一个不那么显眼的细节:这个示例会预先保存节点和连线的基础样式,并提前生成连线 id,这使得重复执行“高亮-重置”循环时无需重新加载图谱也依然安全。
在基于头像的人物关系查看器中,它也特别强调红色关系高亮与点击画布后的恢复机制。因此,对于那些需要在稳定图谱视图上实现临时检查状态,而不是构建更大控制面板或完整调查看板的开发者来说,它是一个更合适的起点。
这种模式还适用于哪里
这种模式也很适合用于调查图谱、欺诈或风险地图、依赖关系查看器、事件关联图,以及合作伙伴或供应商网络。在这些场景中,用户需要先查看某个实体的局部关系,然后快速将画布恢复初始状态。尤其是在节点点击和连线点击需要表达不同含义时,例如“显示与该实体相连的全部内容”与“只聚焦这条具体关系”,这种模式会特别有用。
同样的结构也适用于只读的组织架构图或账户网络查看器,这类场景通常希望布局稳定一次后,就作为一个稳定的查看表面使用。这个示例使用的是基于头像的人物数据,但交互模式本身是通用的:保留基础样式,通过实例 API 应用临时强调,并显式维护重置行为。