线条端点与节点间距调节
这个示例通过改写每条已渲染连线的 `junctionOffset`,演示连线端点与节点边框之间间距的实时控制。它对比了居中直线簇与左到右曲线树两种场景下的效果,并复用悬浮工作台提供画布设置与图片导出。
在两种布局中实时控制连线端点间距
本示例构建了什么
本示例构建了一个用于比较连线端点间距的全屏对照面板。其中一个互不连通的子图会渲染为居中的环形聚类,并使用从边框到边框的直线连接;另一个子图则渲染为从左到右的树形结构,并使用带标签的曲线连接。一个悬浮面板会说明该行为、提供范围滑块,并开放继承而来的画布工具能力。
真正的重点不在示例数据本身,而在于你可以通过修改 junctionOffset,在运行时让每条连线的端点都从节点边框向外移开,然后在同一张画布上对比两种不同连接样式下的视觉效果。
数据是如何组织的
图被拆分为 MyGraph.tsx 中两个内联的 RGJsonData 对象:treeJsonData 和 centerJsonData。它们各自拥有自己的 rootId、nodes 和 lines,示例始终不会把它们合并成一个连通数据集。
在布局运行之前,这两个数据集都会先在代码中做预处理。树图节点会被设置为半透明,并使用透明填充。中心区域的节点会被重新设置为圆形且尺寸更小,中心区域的连线也会被切换为两端都使用 RGJunctionPoint.border 的直线连接。示例没有调用 setJsonData,而是通过 addNodes() 和 addLines() 加载两组数据,再用 doLayoutAll() 完成摆放。
在真实应用中,同样的模式可以表达一张画布上的两个相关视图:例如核心系统聚类与依赖树并列、产品能力中枢与流程拆解并列,或设备总览与组件层级并列。
relation-graph 的使用方式
RGProvider 提供图上下文,RGHooks.useGraphInstance() 则是主要的控制入口。最外层图保持 layoutName: 'fixed',这使示例可以手动组装两个互不连通的网络,而不是依赖一次统一的自动布局。
树形一侧使用图级默认配置:矩形节点、左右方向的曲线连接、沿路径显示文本,以及位于左下角的横向工具栏。中心区域则按节点和连线分别覆盖这些默认值,构造出一个形成对比的圆形聚类,并使用附着在边框上的直线连接。
在数据插入后,示例通过 createLayout() 创建两个布局实例:一个用于环形聚类的中心布局,另一个用于树图的从左到右布局。它先使用 getNodesRectBox() 测量第一个子图,再把树根偏移到它旁边,最后调用 moveToCenter() 和 zoomToFit() 完成收尾。
本示例没有定义自定义节点、连线、画布或视口插槽。它依赖默认渲染,并将重点放在运行时的 graph-instance API 上:addNodes()、addLines()、getNodeById()、getNetworkNodesByNode()、getLines() 和 updateLine()。
共享的悬浮设置面板通过 RGHooks.useGraphStore() 读取当前的滚轮模式和拖拽模式,并通过 graphInstance.setOptions() 更新这些配置。本地 SCSS 覆盖非常少,只是把选中连线标签改成深色背景配白色文字。
关键交互
- 范围滑块会更新 React state,随后一个 effect 会重写当前每条连线的
junctionOffset,因此连线与节点之间的间距会立即发生变化。 - 说明面板可以拖拽和最小化,这样控件就能保持可用,同时不会长期遮挡图中的某个固定区域。
- 设置浮层允许用户在滚动、缩放和禁用之间切换滚轮行为,也允许在框选、平移和禁用之间切换画布拖拽行为。
- 同一个浮层还可以通过准备画布 DOM、将其渲染为 blob 并下载,从而把当前图导出为图片。
关键代码片段
这段 options 配置建立了固定的外层画布,以及树形网络所使用的默认连接行为。
toolBarDirection: 'h',
toolBarPositionH: 'left',
toolBarPositionV: 'bottom',
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineTextOnPath: true,
lineTextMaxLength: 10,
defaultLineColor: '#000000',
layout: {
layoutName: 'fixed'
}
这段预处理逻辑把中心网络变成了一个用于对比的圆形案例。
centerJsonData.nodes.forEach(node => {
node.opacity = 0.5;
node.nodeShape = RGNodeShape.circle;
node.borderWidth = 1;
node.width = 80;
node.height = 80;
node.fontSize = 10;
node.color = 'transparent';
});
这一部分先摆放居中的子图,再利用测量得到的边界来定位树根节点。
const myCenterLayout = graphInstance.createLayout({
layoutName: 'center'
});
const centerNodes = graphInstance.getNetworkNodesByNode(centerRootNode);
myCenterLayout.placeNodes(centerNodes, centerRootNode);
const nodesRectInfo = graphInstance.getNodesRectBox(centerNodes);
treeRootNode.x = nodesRectInfo.maxX - 50;
treeRootNode.y = nodesRectInfo.maxY + 50;
第二次布局会把剩余节点转换为一个从左到右的树,并显式设置水平和垂直间距。
const myTreeLayout = graphInstance.createLayout({
layoutName: 'tree',
from: 'left',
treeNodeGapH: 200,
treeNodeGapV: 30
});
const treeNodes = graphInstance.getNetworkNodesByNode(treeRootNode);
myTreeLayout.placeNodes(treeNodes, treeRootNode);
这个函数是本示例的核心要点:它会读取当前已渲染的所有连线,并原地重写 junctionOffset。
const applyMyOptions = () => {
const lines = graphInstance.getLines();
lines.forEach(line => {
graphInstance.updateLine(line.id, {
junctionOffset: gapOfLineAndNode
});
});
};
本示例的独特之处
根据对比记录,本示例最适合被视为 junctionOffset 的参考案例,而不是一个通用的悬浮面板示例。它不常见的部分在于:通过一个实时范围控制去重写当前所有连线,让用户可以在运行时直接观察连线端点与节点边框之间的可见间隙。
对比数据也说明了为什么双子图组合很重要。不同于重点研究重复边之间间距的 line-multi-lines-gap,本示例研究的是边的端点与其接触节点边框之间的距离。也不同于 canvas-caliper,这里的混合布局共享场景并不是为了标尺或坐标检查,而是作为一个用于观察端点留白的安静对照板。
同样需要避免得出错误结论。悬浮辅助窗口、设置浮层、画布模式切换以及图片导出,都是其他示例也会复用的共享工具。这里真正独特的组合,是固定画布下的双网络构成、半透明节点处理、基于图级 getLines() 加 updateLine() 的变更方式,以及集中在同一个紧凑工作台中的运行时 junctionOffset 控制。
这一模式还适用于哪里
- 用于知识图谱或依赖图的可读性微调,此时图标、徽标或装饰性节点边框可能让边的端点显得过于拥挤。
- 用于组织树或审批树,在不改变整体布局的前提下,让箭头与节点轮廓之间留出一点呼吸空间。
- 用于工程图或设备图,在同一张画布上同时放置一个总览聚类和一个细节树以进行视觉对比。
- 用于设计系统或图主题工作台,让团队在将这些默认值固化到更大范围的查看器之前,先实时调整边几何相关参数。