Line Endpoint Gap Control
This example demonstrates live control of the gap between line endpoints and node borders by rewriting each rendered line's `junctionOffset`. It compares the effect across a centered straight-link cluster and a left-to-right curved tree while reusing a floating workbench for canvas settings and image export.
Live Line Endpoint Gap Control Across Two Layouts
What This Example Builds
This example builds a full-screen comparison board for line endpoint spacing. One disconnected subgraph is rendered as a centered circular cluster with straight border-to-border links, and the other is rendered as a left-to-right tree with curved labeled links. A floating panel explains the behavior, exposes a range slider, and gives access to inherited canvas utilities.
The main result is not the sample data itself. The point is that you can move every line endpoint away from the node border at runtime by changing junctionOffset, then judge the visual effect in two different connector styles on the same canvas.
How the Data Is Organized
The graph is split into two inline RGJsonData objects in MyGraph.tsx: treeJsonData and centerJsonData. Each has its own rootId, nodes, and lines, and the example never merges them into one connected dataset.
Before layout runs, both datasets are preprocessed in code. Tree nodes are made semi-transparent with transparent fills. Center nodes are restyled into circles with smaller dimensions, and center lines are switched to straight links with RGJunctionPoint.border on both ends. Instead of calling setJsonData, the example loads both datasets through addNodes() and addLines(), then places them with doLayoutAll().
In a real application, the same pattern can represent two related views on one canvas: a core system cluster next to a dependency tree, a product capability hub next to a process breakdown, or an equipment overview next to a component hierarchy.
How relation-graph Is Used
RGProvider supplies graph context, and RGHooks.useGraphInstance() is the main control surface. The outer graph stays in layoutName: 'fixed', which lets the example assemble two disconnected networks manually instead of relying on a single automatic layout pass.
The tree side uses the graph-wide defaults: rectangular nodes, curved left-right connectors, text on the path, and a bottom-left horizontal toolbar. The center side overrides those defaults per node and per line to create a contrasting circular cluster with straight border-attached links.
After the data is inserted, the example creates two layout instances with createLayout(): a center layout for the circular cluster and a left-to-right tree layout for the tree. It measures the first subgraph with getNodesRectBox(), offsets the tree root beside it, and then finishes with moveToCenter() and zoomToFit().
The example does not define custom node, line, canvas, or viewport slots. It relies on default rendering and focuses on runtime graph-instance APIs instead: addNodes(), addLines(), getNodeById(), getNetworkNodesByNode(), getLines(), and updateLine().
The shared floating settings panel uses RGHooks.useGraphStore() to read the current wheel and drag modes and graphInstance.setOptions() to update them. The local SCSS override is minimal: it only changes the checked line label to a dark background with white text.
Key Interactions
- The range slider updates React state, and an effect rewrites every current line’s
junctionOffset, so the line-to-node gap changes immediately. - The description panel can be dragged and minimized, which keeps the controls usable without covering one fixed part of the graph.
- The settings overlay lets the user switch wheel behavior between scroll, zoom, and none, and canvas drag behavior between selection, move, and none.
- The same overlay can export the current graph as an image by preparing the canvas DOM, rendering it to a blob, and downloading it.
Key Code Fragments
This options block establishes the fixed outer canvas and the default connector behavior used by the tree network.
toolBarDirection: 'h',
toolBarPositionH: 'left',
toolBarPositionV: 'bottom',
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
defaultLineTextOnPath: true,
lineTextMaxLength: 10,
defaultLineColor: '#000000',
layout: {
layoutName: 'fixed'
}
This preprocessing pass turns the center network into the contrasting circular comparison case.
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';
});
This part places the centered subgraph first and then uses its measured bounds to position the tree root.
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;
This second layout pass turns the remaining nodes into a left-to-right tree with explicit horizontal and vertical gaps.
const myTreeLayout = graphInstance.createLayout({
layoutName: 'tree',
from: 'left',
treeNodeGapH: 200,
treeNodeGapV: 30
});
const treeNodes = graphInstance.getNetworkNodesByNode(treeRootNode);
myTreeLayout.placeNodes(treeNodes, treeRootNode);
This function is the core lesson of the example: it reads the current rendered lines and rewrites junctionOffset in place.
const applyMyOptions = () => {
const lines = graphInstance.getLines();
lines.forEach(line => {
graphInstance.updateLine(line.id, {
junctionOffset: gapOfLineAndNode
});
});
};
What Makes This Example Distinct
According to the comparison records, this example is most distinct when it is treated as a junctionOffset reference, not as a generic floating-panel demo. Its uncommon part is the live range control that rewrites every current line so users can inspect a visible gap between line endpoints and node borders at runtime.
The comparison data also shows why the two-subgraph composition matters. Unlike line-multi-lines-gap, which focuses on spacing between repeated edges, this example studies spacing between an edge endpoint and the node border it touches. Unlike canvas-caliper, the shared mixed-layout scene is used here as a quiet comparison board for endpoint clearance rather than as support for rulers or coordinate inspection.
It is also important to avoid the wrong claims. The floating helper window, settings overlay, canvas mode switches, and image export are shared utilities reused by other examples. The distinctive combination here is the fixed-canvas dual-network composition, translucent node treatment, graph-wide getLines() plus updateLine() mutation, and runtime junctionOffset control in one compact workbench.
Where Else This Pattern Applies
- Readability tuning for knowledge graphs or dependency maps where icons, badges, or decorative node borders make edge endpoints feel visually crowded.
- Organization or approval trees where arrows need a little breathing room away from node outlines without changing the overall layout.
- Engineering or equipment diagrams where one cluster overview and one detail tree need to coexist on one canvas for visual comparison.
- Design-system or graph-theme workbenches where teams want to tune edge geometry parameters live before baking those defaults into a broader viewer.