JavaScript is required

线条形状、锚点与标签位置

这个示例在已加载的人像关系图上演示全图级的边形态控制、曲线连接锚点和标签位置控制。它通过 `getLines()` 配合 `updateLine()` 原地重设所有已渲染边,同时通过悬浮辅助窗提供共享画布设置和图片导出。

运行时线条形状、连接点与标签放置控制

这个示例构建了什么

这个示例构建了一个全屏关系图,同一份已加载的图数据可以在运行时实时切换样式。画布在平铺背景上展示圆形头像节点、按颜色区分的关系边,以及一个悬浮控制窗口。该窗口可以把每条线在直线与曲线走线之间切换、改变曲线与节点的连接方式,并在带框文本标签与沿路径放置的文本之间切换。

图本身的内容在加载后保持不变。它的主要亮点是,用户无需重建数据集或更改节点位置,就可以在一张稠密的头像关系图上比较多种边渲染行为。

数据如何组织

数据来自 mock-data-api.ts 中的本地 RGJsonData 对象。它声明了 rootId: "N13"、21 个节点以及更多的连线记录。每个节点都带有可见样式字段,例如 colorborderColordata.icon;每条线都带有 fromtotextcolorfontColor 以及一个类型化的 data 载荷。

布局之前没有真正的预处理。fetchJsonData() 只是用一个简短的 Promise 包装这个静态对象,而 initializeGraph() 会把结果直接传给 setJsonData()。有些节点对会被有意重复出现,例如 N1 -> N15N13 -> N8,这有助于示例展示在线条重复连接时,线条形状和连接方式的选择会如何表现。在实际项目中,同样的结构可以表示人物网络、调查链路、利益相关方地图,或同一对实体之间存在多种连接类型的服务关系。

如何使用 relation-graph

RGProvider 包裹整个页面,RGHooks.useGraphInstance() 则是 MyGraph 内部的主要运行时控制接口。图选项使用内置的力导向布局,关闭调试模式,设置圆形节点,将 multiLineDistance 保持为 20,把布局迭代次数上限设为 50,并提供节点颜色和边框的兜底设置。

这里重要的实现细节在于,当选择器发生变化时,示例不会重建 RGJsonData。异步加载完成后,initializeGraph() 会调用 loading()setJsonData()updateMyGraphData()clearLoading()moveToCenter()zoomToFit()。之后,第二个 effect 会监听 lineShapelineJunctionPointtextOnPath,然后遍历 graphInstance.getLines(),并用 updateLine(...) 重写每一条已经渲染出来的线。

这一步线条更新流程正是 relation-graph 功能组合使用的地方。lineShape 会在 RGLineShape.StandardStraightRGLineShape.StandardCurve 之间切换。启用直线模式时,两个端点都会被强制恢复为 RGJunctionPoint.border;启用曲线模式时,选中的连接点值会同时写入 fromJunctionPointtoJunctionPoint。同一轮更新还会切换 useTextOnPath,因此标签位置可以在不重新加载图的情况下发生变化。

这个示例还使用了 slot 和共享辅助组件。RGSlotOnNode 用圆形肖像头像和位于节点下方的标签替换了默认节点主体。DraggableWindow 承载线条控制项,并且可以打开共享的 CanvasSettingsPanel,其中 RGHooks.useGraphStore()graphInstance.setOptions() 用于切换滚轮与拖拽行为。这个共享面板还可以通过 prepareForImageGeneration()domToImageByModernScreenshot()restoreAfterImageGeneration() 导出当前图像。

本地 SCSS 则完成了最终的视觉呈现,包括平铺画布背景、白色带框线条标签,以及被选中节点的蓝色光晕。

关键交互

  • Line Shape 选择器会把所有已加载的边改写为直线或曲线走线。
  • Line JunctionPoint 选择器只会在启用曲线走线时出现,让用户比较 border、paired-side 和 single-side 锚点模式。
  • Line Text On Path 选择器会在普通带框标签与直接渲染在线条路径上的文本之间切换。
  • 点击空白画布区域会调用 clearChecked(),从图中移除选中高亮。
  • 悬浮辅助窗口可以拖拽、最小化、切换为画布设置覆盖层,并可用于下载当前图像。

关键代码片段

这个数据片段表明,示例从一个固定的 RGJsonData 对象开始,其中包含根节点、每个节点的样式以及自定义头像 URL。

const jsonData = {
    "rootId": "N13",
    "nodes": [
        {
            "id": "N1",
            "text": "Liangping.Hou",
            "color": "#ec6941",
            "borderColor": "#ff875e",
            "data": {

这个选项片段证明,该图在运行时线条更新开始前,使用了力导向布局、圆形节点以及固定的重复边间距。

const graphOptions: RGOptions = {
    debug: false,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultNodeShape: RGNodeShape.circle,
    multiLineDistance: 20,
    layout: {
        layoutName: 'force',
        maxLayoutTimes: 50
    },
    defaultNodeBorderWidth: 2,

这段初始化流程表明,图只加载一次,应用当前线条设置,然后再将视口居中并适配到合适大小。

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

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

这个更新函数是核心技巧:它重写每一条已经渲染的线,而不是重新生成数据集。

const updateMyGraphData = async () => {
    graphInstance.getLines().forEach((line) => {
        graphInstance.updateLine(line, {
            lineShape,
            fromJunctionPoint: lineShape === RGLineShape.StandardStraight ? RGJunctionPoint.border : lineJunctionPoint,
            toJunctionPoint: lineShape === RGLineShape.StandardStraight ? RGJunctionPoint.border : lineJunctionPoint,
            useTextOnPath: textOnPath
        });
    });
};

这个控制片段证明,连接点选择是有条件的,只有图正在显示曲线时它才有意义。

{
    lineShape !== RGLineShape.StandardStraight &&
    <div>
        <div className="text-base py-2">Line JunctionPoint:</div>
        <SimpleUISelect
            data={[
                { value: RGJunctionPoint.border, text: 'Border' },
                { value: RGJunctionPoint.ltrb, text: 'Left/Top/Right/Bottom' },
                { value: RGJunctionPoint.lr, text: 'Left/Right' },
                { value: RGJunctionPoint.tb, text: 'Top/Bottom' },

这个节点 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="absolute transform translate-y-[35px]" style={{ color: node.color }}>{node.text}</div>
        </div>
    )}
</RGSlotOnNode>

这个共享设置片段表明,这个悬浮工作区也可以在不更改图数据的情况下导出当前图像。

const downloadImage = async () => {
    const canvasDom = await graphInstance.prepareForImageGeneration();
    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
        graphBackgroundColor = '#ffffff';
    }
    const imageBlob = await domToImageByModernScreenshot(canvasDom, {
        backgroundColor: graphBackgroundColor
    });
    if (imageBlob) {
        downloadBlob(imageBlob, 'my-image-name');
    }

这个示例的独特之处

比较记录表明,这个示例最接近 line-multi-lines-gapcustom-line-stylesearch-and-focususe-dagre-layout-2,但它们也揭示了一个更聚焦的要点。这里的主要重点是在图已经加载完成后,对内置边行为进行图级控制:形状切换、曲线连接点选择,以及沿路径文本切换。

line-multi-lines-gap 相比,这个示例在重复边间距上的着力较少,而更关注曲线连接器如何附着到节点上。与 custom-line-style 相比,它的区别价值不在于 CSS 外观换肤,而在于对内置线条几何和标签放置的运行时控制。与 search-and-focususe-dagre-layout-2 相比,这个图的重点也不是导航或重新布局;节点位置保持在力导向布局中,变化的只有边的渲染方式。

比较文件也限制了可以安全提出的结论。悬浮辅助窗口、画布设置、图像导出以及头像风格的关系场景,并不是这个示例独有的,它也不是一个结构编辑器。真正让它突出的,是稠密头像关系图、图级 getLines()updateLine() 更新、仅在曲线模式下出现的连接点选择器,以及在带框标签和沿路径文本之间进行运行时切换的组合。

这种模式还适用于哪里

  • 关系图或社交图场景中,团队需要在固定数据集上比较更易读的连接器样式,再决定生产环境默认值。
  • 调查、风险或欺诈图谱中,一对实体之间可能存在多种关系类型,而边的可读性比结构编辑更重要。
  • 服务和依赖关系图中,需要一个紧凑的控制界面来调整连接器走线、锚点策略和标签放置,而无需重新运行布局逻辑。
  • 内部演示或 QA 工作台中,设计师和工程师需要在真实的图场景中审查线条行为,并导出当前状态的快照。