JavaScript is required

可切换图案的胶带线条

这个示例在固定树布局中用插槽渲染的丝带路径替换标准 relation-graph 连线。它结合根级 CSS 填充变量、可复用 SVG pattern 定义、保留点击行为的自定义连线渲染、共享画布控制和截图导出,使同一组丝带可在运行时切换纯色与动画填充。

树形图上的可切换图案填充带状连线

这个示例构建了什么

这个示例构建了一个小型树形查看器,其中每条关系都被渲染为填充的带状连线,而不是标准的描边边线。画布使用深色网格背景、洋红色矩形节点,以及一个悬浮的辅助窗口,允许用户在运行时重新设置所有可见带状连线的样式。

用户可以在线条填充样式之间切换,包括普通的半透明颜色和多种 SVG 图案预设,其中包含动态条纹、波浪、箭头和彩虹流动效果。最关键的细节在于,这个示例并不是简单地装饰内置线条。它使用自定义 slot 渲染的 SVG path 替换了可见边的表面,同时仍然保留了 relation-graph 正常的线条点击行为。

数据是如何组织的

图数据直接在 initializeGraph() 内部以内联方式声明为一个 RGJsonData 对象,其中包含 rootId、扁平的 nodes 数组和扁平的 lines 数组。这个示例树包含九个节点和八条 from/to 连接,并在一次 setJsonData() 调用中全部加载完成。

在加载 JSON 之前没有显式的预处理步骤。自定义几何计算发生在之后,也就是 relation-graph 提供运行时 slot 数据之后。MyLineContent 接收 lineConfig.fromlineConfig.to 以及运行时的 line.data 偏移字段,而 generateLinePath4Curve(...) 则使用这些值在节点边界之间生成一个闭合的带状路径。

在真实系统中,相同的数据结构可以表示子系统依赖、服务关系、产品模块或流程阶段。静态节点标签可以替换为业务实体,而运行时线条几何则可以支持更厚的路由带、通道式依赖,或带品牌风格的关系表面。

relation-graph 是如何使用的

index.tsx 使用 RGProvider 包裹整个示例,因此图画布与悬浮工具窗口共享同一个 relation-graph 上下文。MyGraph.tsx 定义了基础的 RelationGraph 配置:矩形节点、边框交汇点、默认颜色,以及具有固定水平和垂直间距的树形布局。

RGHooks.useGraphInstance() 负责驱动初始化。在挂载时,这个示例通过 setJsonData() 加载内联数据集,然后使用 moveToCenter()zoomToFit() 将图重新居中并适配到视口中。之后图的结构保持静态不变。

核心定制发生在 RGSlotOnLine 中。示例没有把边的输出交给内置渲染器,而是通过 slot 将每条线传入 MyLineContent,由它借助 generateLinePath4Curve(...) 计算闭合的 SVG 路径,应用用于填充和状态的 CSS 变量,并把点击事件继续转发给 graphInstance.onLineClick(...)

运行时主题样式拆分在 React state、SVG defs 和 SCSS 之间。fillStyleId 存储在组件状态中,然后通过根级 CSS 变量 --rg-line-fill-style 暴露出来。MySvgDefs 注入可复用的 <pattern><linearGradient> 定义,而 my-relation-graph.scss 将这些填充应用到 .rg-line,为选中线条添加洋红色轮廓、增强悬停时的带状亮度,并重设画布和节点样式。

这个示例还复用了共享的 DraggableWindow 工具。它的设置浮层通过 RGHooks.useGraphStore() 读取当前图配置,使用 setOptions(...) 更新滚轮和拖拽行为,并通过 prepareForImageGeneration()getOptions()restoreAfterImageGeneration() 导出画布。

关键交互

主要交互是悬浮窗口中的填充样式选择器。选择不同选项会更新 fillStyleId,从而立即重写所有自定义带状路径所使用的根级 CSS 填充引用。

点击带状连线仍然会参与 relation-graph 的线条交互流程。MyLineContent 会阻止自定义 SVG path 上的 DOM 冒泡,并将原生事件转发给 graphInstance.onLineClick(...),因此即使可见边已经被自定义,选中状态的反馈仍然可以正常工作。

悬浮窗口可以被拖动、最小化,并切换到设置浮层。在该浮层中,用户可以修改滚轮行为、修改画布拖拽行为,并下载当前图的截图。

关键代码片段

这段配置代码展示了该示例如何将布局和默认节点设置保留在 relation-graph 内部,同时将图准备为固定的树形展示。

defaultNodeShape: RGNodeShape.rect,
defaultJunctionPoint: RGJunctionPoint.border, // Use enums
defaultLineColor: '#2E74B5',
defaultNodeColor: '#ffffff',
defaultNodeBorderWidth: 2,
defaultNodeBorderColor: '#2E74B5',
// Layout is recommended to be configured in options for automatic setJsonData effect
layout: {
    layoutName: 'tree', // Example layout, adjust as needed
    treeNodeGapH: 200,
    treeNodeGapV: 50
}

这段初始化代码证明,数据以内联方式保留,并且图会在加载后立即完成居中和适配。

// 5. Set data
// setJsonData includes logic for addNodes, addLines and doLayout
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

这个包装样式就是运行时主题切换器。一个状态值控制所有自定义线条表面的填充来源。

<div
    className="my-graph"
    style={{
        height: '100vh',
        '--rg-line-fill-style': (fillStyleId ? `url(#${fillStyleId})` : 'rgba(255,255,255,0.47)')
    }}
>

这段渲染代码展示了 relation-graph 如何把每条线交给自定义 slot 渲染器处理。

<RelationGraph options={graphOptions}>
    {/* 6. Use RGSlotOnLine component to define line slots */}
    <RGSlotOnLine>
        {({ lineConfig, checked }: RGLineSlotProps) => (
            <MyLineContent lineConfig={lineConfig} checked={checked} />
        )}
    </RGSlotOnLine>
</RelationGraph>

这段自定义线条代码同时展示了两个关键行为:将点击事件转发回 relation-graph,以及用生成的路径替换可见边。

const onLineClick = (e: React.MouseEvent | React.TouchEvent) => {
    // 阻止冒泡,并将事件传递给 graphInstance
    e.stopPropagation();
    graphInstance.onLineClick(line, e.nativeEvent as RGUserEvent);
};
const pathId = graphInstanceId + '-' + line.id;
const path = generateLinePath4Curve(
    lineConfig.from,
    lineConfig.to,
    line,
    1
);

这段辅助代码正是把两个节点锚点转换为一个闭合的带状实体,而不是一条单独描边的实现核心。

const toLeft = fx > tx;
const curve1 = toLeft ? createLinePathForCurve(fx + 1, fy, tx + tw - 1, ty, zoomRate, toLeft) : createLinePathForCurve(fx + fw - 1, fy, tx + 1, ty, zoomRate, toLeft);
const line1 = toLeft ? `L ${Math.round(tx + tw - 1)},${Math.round(ty + th)}` : `L ${Math.round(tx + 1)},${Math.round(ty + th)}`;
const curve2 = toLeft ? createLinePathForCurve(tx + tw, ty + th, fx + 1, fy + fh, zoomRate, !toLeft) : createLinePathForCurve(tx, ty + th, fx + fw - 1, fy + fh, zoomRate, !toLeft);
const path = `M ${curve1.substring(2)} ${line1} ${curve2.substring(2)} Z`;
return path;

这段 SVG defs 代码展示了某些线条皮肤使用的是动态图案,而不是静态颜色。

<pattern id="pat-stripe" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
    <rect width="40" height="40" fill="#fbbf24"/>
    <rect width="20" height="40" fill="#000"/>
    <animateTransform attributeName="patternTransform" type="translate" from="0 0" to="40 0" dur="1s" repeatCount="indefinite" additive="sum"/>
</pattern>

这段设置代码展示了查看器行为变更是如何通过图实例配置应用的,而不是通过重建整个页面实现。

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

这个示例的独特之处

对比数据表明,这个示例与 adv-line-slot2custom-line-stylecustom-line-animationline-style2ever-changing-tree 最为接近,但它的关注点比这些示例都更集中。它最独特的教学价值不只是线条外观可以在运行时变化,而是展示了如何用 slot 渲染的闭合带状路径替换可见关系表面,并通过一个 CSS 填充变量加上一组可复用的 SVG defs 来为这一表面换肤。

adv-line-slot2 相比,这个示例更适合作为固定树形布局的参考。它保留了矩形节点、深色网格画布和基于曲线的带状几何,因此重点始终放在可切换的带状边线上,而不是居中拓扑展示或节点形状变化。

custom-line-stylecustom-line-animation 相比,它介入得更深一层。那两个示例只是重设 relation-graph 原生线条的样式,而这个示例则通过 RGSlotOnLine 直接替换了可见边的几何形态。

line-style2ever-changing-tree 相比,它更少关注仅针对选中线条的强调或参数调优,而更关注在稳定查看器中进行整图范围的带状填充切换。固定树形布局、洋红色矩形节点、深色网格背景、自定义带状路径、动态 SVG 纹理以及共享导出工具的罕见组合,使它成为一个专注于自定义边表面的参考示例。

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

这种模式非常适合迁移到子系统地图、依赖关系图、路由带可视化以及流程树中,尤其是在关系需要呈现为通道或带状结构而不是细连接线时。它在团队希望使用同一个渲染器,在不重建图数据的前提下切换品牌化、状态驱动或动态填充效果时尤其有用。

它也很适合演示工具和白标产品,因为设计团队可能希望基于一套实现提供多种边线皮肤。相同结构还可以适配到能量流向图、网络容量视图、制造流程管线或物流走廊等场景,在这些场景中,关系表面本身就承载着视觉含义。