JavaScript is required

人物关系图筛选

此示例构建了一个只读的人物关系查看器,包含力导向画布、头像风格节点、带标签的关系连线、全宽过滤栏以及一个悬浮辅助窗口。图谱会填满页面高度,保持完整角色集合可见,并将 `teacher-student`、`relative`、`collusion` 和 `report` 等关系直接展示在连线路径上。

在保留上下文的同时过滤角色关系图

此示例构建的内容

此示例构建了一个只读的人物关系查看器,包含力导向画布、头像风格节点、带标签的关系连线、全宽过滤栏以及一个悬浮辅助窗口。图谱会填满页面高度,保持完整角色集合可见,并将 teacher-studentrelativecollusionreport 等关系直接展示在连线路径上。

用户可以按性别、角色立场和关系类型缩小查看范围。这里的关键行为是,不匹配的项目会被降低透明度而不是移除,因此图谱仍会保留周边上下文,同时让当前激活的子集更加突出。

数据组织方式

该示例从 mock-data-api.ts 加载一个 RGJsonData 对象。它使用扁平的 nodes 数组和扁平的 lines 数组,并以 rootId: "N13" 作为锚点 id。每个节点都包含 idtextcolorborderColor 和一个 data 对象,其中承载了可过滤字段:genderisGoodManicon。每条连线都包含 fromtotext、颜色字段以及 data.type,后者就是工具栏标签所使用的关系分类。

在调用 setJsonData(...) 之前没有进行任何结构预处理。fetchJsonData() 只是为这份静态数据集包了一层很短的异步延迟,然后原样返回。在真实应用中,只要具备节点元数据和关系分类,同样的数据结构也可以表示干系人地图、调查看板、家族或角色关系图、合作伙伴生态,或客户关系图。

relation-graph 的使用方式

index.tsx 使用 RGProvider 包裹整个示例,这让 MyGraph 和共享的悬浮工具面板都能通过 hooks 获取同一个图实例。MyGraph 使用 RGHooks.useGraphInstance() 来加载数据集、显示和清除加载状态、让视口居中、在挂载后适配图谱尺寸、在点击空白画布时清除选中状态,以及在过滤过程中更新节点或连线的透明度。

图配置定义了查看器的样式。该示例使用内置的 force 布局,设置 force_node_repulsion: 0.5maxLayoutTimes: 50,使用直线、圆形节点、沿路径渲染的连线文本,以及 multiLineDistance: 20 以保证平行关系的可读性。工具栏以垂直方式放置在左上角,并且在图级别提供了兜底节点颜色和节点边框宽度,尽管数据集本身也提供了明确颜色。

主要的视觉定制来自 RGSlotOnNode,它用一个 48x48 的头像块和下方的文本标签替换了默认节点主体。随后 SCSS 文件通过 relation-graph 的内部 peel 类重绘了已选中节点和已选中连线,把连线标签变成彩色标签样式,并为当前激活节点外壳增加了一圈彩色高亮。

共享的 DraggableWindowCanvasSettingsPanel 展示了辅助工具如何悬浮在图之上,而不改变核心过滤模式。通过 useGraphStore()graphInstance.setOptions(...),该面板可以在运行时切换滚轮和拖拽行为,并利用 prepareForImageGeneration()restoreAfterImageGeneration() 下载画布图像。这些控件属于辅助支撑;示例的核心价值仍然是基于元数据、作用于已挂载图实例的过滤方式。

关键交互

最主要的交互是外部过滤工具栏。两个分段选择器分别用于选择性别和角色立场,一组可点击的关系标签则用于切换哪些关系类别保持完全可见。每次工具栏发生变化,都会更新 React state,并重新执行一次基于图实例的过滤流程。

该过滤行为刻意采用“聚焦 + 上下文”而不是“隐藏或重建”的方式。节点透明度取决于 genderisGoodMan,而连线透明度只取决于所选的关系类型列表。这种分离意味着该示例会分别对节点和连线做淡化处理,而不是尝试根据节点匹配结果去推导连线是否可见。

点击画布背景会调用 clearChecked(),它会重置 relation-graph 的选中样式,但不会清除当前已激活的过滤条件。悬浮辅助窗口也可以被拖动、最小化、展开为设置面板,并用于下载图像,但这些操作影响的是图周围的工作区,而不是数据本身。

关键代码片段

这段数据集片段说明,图数据中携带了可用于过滤的节点元数据,例如 genderisGoodManicon

{
    "id": "N1",
    "text": "Liangping.Hou",
    "color": "#ec6941",
    "borderColor": "#ff875e",
    "data": {
        "isGoodMan": "1",
        "gender": "male",
        "icon": "https://dss2.baidu.com/6ONYsjip0QIZ8tyhnq/it/u=2308340537,462224207&fm=58&app=83&f=JPEG?w=250&h=250&s=EC708F46DA96B89CB69D5DDA0300D014&n=侯亮平"
    }
}

这段连线片段说明,关系过滤是由 data.type 驱动的,而不是依赖特殊的边 id 或第二张派生表。

{
    "from": "N20",
    "to": "N18",
    "text": "report",
    "color": "#ed724d",
    "fontColor": "#ed724d",
    "data": {
        "type": "report"
    }
}

这段初始化路径展示了示例中的异步加载封装,以及数据集到达之后的视口适配顺序。

const initializeGraph = async () => {
    const myJsonData: RGJsonData = await fetchJsonData();

    graphInstance.loading();
    await graphInstance.setJsonData(myJsonData);
    graphInstance.clearLoading();
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

这段配置代码说明,该示例被配置成一个带直线标签连线、可显示多重边间距的力导向头像查看器。

const graphOptions: RGOptions = {
    debug: false,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultNodeShape: RGNodeShape.circle,
    defaultLineTextOnPath: true,
    multiLineDistance: 20,
    layout: {
        layoutName: 'force',
        force_node_repulsion: 0.5,
        maxLayoutTimes: 50
    },
    defaultNodeBorderWidth: 2,
    defaultNodeColor: '#e85f84'
};

这段过滤流程是核心技巧:它扫描已挂载的图,并原地修改节点和连线的透明度,而不是重建数据集。

graphInstance.getNodes().forEach(thisNode => {
    let _isHideThisLine = false;
    if (checkecItems.checkedGender !== '') {
        if (thisNode.data!['gender'] !== checkecItems.checkedGender) {
            _isHideThisLine = true;
        }
    }
    if (checkecItems.checkedCharacterAlignment !== '') {
        if (thisNode.data!['isGoodMan'] !== checkecItems.checkedCharacterAlignment) {
            _isHideThisLine = true;
        }
    }
    graphInstance.updateNode(thisNode, {
        opacity: _isHideThisLine ? 0.1 : 1
    });
});
graphInstance.getLines().forEach(thisLine => {
    let hideLine = false;
    if (checkecItems.checkedRelationType.indexOf(thisLine.data!['type']) === -1) {
        hideLine = true;
    }
    graphInstance.updateLine(thisLine, {
        opacity: hideLine ? 0.1 : 1
    });
});

这个 slot 渲染器展示了示例如何将默认节点转换为头像标记,并把标签放在图片下方。

<RGSlotOnNode>
    {({ node }: RGNodeSlotProps) => (
        <div className="w-12 h-12 flex place-items-center justify-center">
            <div className="my-node-avatar" style={{ backgroundImage: `url(${node.data?.icon})` }} />
            <div className="my-node-name absolute transform translate-y-[35px]">{node.text}</div>
        </div>
    )}
</RGSlotOnNode>

此示例的独特之处

对比数据表明,当团队需要对一个固定关系图按类别缩小范围时,这个示例尤其突出。与 search-and-focus 相比,它不会把视口跳转到某一个被选中的节点,也不会构建定位工作流。它会让整个网络始终保持可见,并通过运行时透明度更新,按性别、角色立场和关系类型进行整体过滤。

scene-relationship-op 相比,这个示例在结构上保持固定且只读。它不会打开以节点为中心的编辑浮层、添加子节点、复制节点或创建边。因此,相较于上下文图编辑,它更适合作为一个围绕工具栏驱动探索、面向现成人物图的参考实现。

line-hightlight-pro 相比,它的重点从单条被点击的边转向跨多个元素、基于多轴元数据的过滤。它少见的组合在于:力导向头像关系图、沿路径显示的直线标签、顶部过滤工具栏,以及基于透明度的“聚焦 + 上下文”,而这一切都建立在已挂载图实例之上,而非重建后的数据集之上。

这一模式还适用于哪些场景

这一模式也很适合调查分析看板,在这类场景中,分析人员需要按角色、隶属关系、风险标签或关系类别对人物网络进行淡化筛选,同时又不丢失更广泛的上下文。它同样适用于干系人地图、客户关系视图、角色关系图和合作伙伴图谱,在这些场景里,同一张网络需要从多个元数据维度进行探索。

这种方法还可以扩展到欺诈网络、合规审查,以及内部组织图或影响力图谱。在这些场景中,真正可复用的关键并不是特定的人物数据集,而是外部控件、稳定的图结构与运行时透明度更新的组合,它能在强调某个子集的同时保持整张网络的可读性。