可切换图案的胶带线条
这个示例在固定树布局中用插槽渲染的丝带路径替换标准 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.from、lineConfig.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-slot2、custom-line-style、custom-line-animation、line-style2 和 ever-changing-tree 最为接近,但它的关注点比这些示例都更集中。它最独特的教学价值不只是线条外观可以在运行时变化,而是展示了如何用 slot 渲染的闭合带状路径替换可见关系表面,并通过一个 CSS 填充变量加上一组可复用的 SVG defs 来为这一表面换肤。
与 adv-line-slot2 相比,这个示例更适合作为固定树形布局的参考。它保留了矩形节点、深色网格画布和基于曲线的带状几何,因此重点始终放在可切换的带状边线上,而不是居中拓扑展示或节点形状变化。
与 custom-line-style 和 custom-line-animation 相比,它介入得更深一层。那两个示例只是重设 relation-graph 原生线条的样式,而这个示例则通过 RGSlotOnLine 直接替换了可见边的几何形态。
与 line-style2 和 ever-changing-tree 相比,它更少关注仅针对选中线条的强调或参数调优,而更关注在稳定查看器中进行整图范围的带状填充切换。固定树形布局、洋红色矩形节点、深色网格背景、自定义带状路径、动态 SVG 纹理以及共享导出工具的罕见组合,使它成为一个专注于自定义边表面的参考示例。
这种模式还适用于哪些地方
这种模式非常适合迁移到子系统地图、依赖关系图、路由带可视化以及流程树中,尤其是在关系需要呈现为通道或带状结构而不是细连接线时。它在团队希望使用同一个渲染器,在不重建图数据的前提下切换品牌化、状态驱动或动态填充效果时尤其有用。
它也很适合演示工具和白标产品,因为设计团队可能希望基于一套实现提供多种边线皮肤。相同结构还可以适配到能量流向图、网络容量视图、制造流程管线或物流走廊等场景,在这些场景中,关系表面本身就承载着视觉含义。