Orthogonal Line Label Controls
A compact relation-graph demo for tuning detached text labels on orthogonal tree links. It rotates the same tree through four directions, batch-updates live lines for placement and anchoring, and exposes offset, bend-distance, and corner-radius controls to compare readability.
Orthogonal Line Label Controls
What This Example Builds
This example builds a compact tree-layout sandbox for detached text labels on orthogonal connectors. The canvas shows a fixed sample tree with plain black node labels and blue line labels, while a floating utility window lets the user rotate the tree, change where line text sits, switch text anchoring, and tune label offsets and bend geometry. The main result is a side-by-side mental comparison of label readability as the same relationships are rerouted through different orthogonal directions.
How the Data Is Organized
The graph data is an inline RGJsonData object with one rootId, a flat nodes array, and a lines array that defines the tree edges. There is no fetch step and no domain-specific preprocessing before setJsonData; the component loads the prepared sample directly and then runs layout. In a real application, the same structure could represent approval chains, category trees, call hierarchies, dependency trees, or any labeled parent-child relationships that need readable edge text.
How relation-graph Is Used
index.tsx wraps the example in RGProvider, and MyGraph.tsx uses RGHooks.useGraphInstance() to control loading, relayout, viewport fitting, and line updates. The RelationGraph instance receives a tree layout with RGLineShape.SimpleOrthogonal, defaultLineTextOnPath: false, orientation-aware defaultJunctionPoint, state-driven text offsets, and a state-driven default polyline radius. When the direction selector changes, the example calls graphInstance.updateOptions(...) to swap layout.from, layoutDirection, and the horizontal or vertical node gaps, then reruns doLayout(), moveToCenter(), and zoomToFit().
The label controls use two different mechanisms on purpose. defaultLineTextOffsetX, defaultLineTextOffsetY, and defaultPolyLineRadius are passed through graph options, while placeText, textAnchor, and polyLineStartDistance are applied imperatively to every existing line through getLines() and updateLine(). The page does not use custom node, line, or canvas slots; it relies on graph options, instance APIs, a floating helper component, and stylesheet overrides instead. The shared DraggableWindow also exposes a settings overlay where the user can change wheel mode, switch drag behavior, and export the current graph as an image.
Key Interactions
- The direction selector switches the tree between
left,right,top, andbottom, then recalculates layout so the same labels can be compared in horizontal and vertical orientations. - The
Place TextandLine Text Anchorselectors batch-update all current edges, which changes label semantics without rebuilding the dataset. - The x-offset and y-offset sliders feed new default label offsets into the graph options, moving detached labels around the orthogonal segments.
- The first-bend slider changes
polyLineStartDistanceon each live edge, and the radius slider changes the default corner radius for orthogonal turns. - The floating utility window can be dragged or minimized, and its settings panel can switch wheel or drag behavior and download an image of the current graph.
Key Code Fragments
This options block fixes the example on orthogonal routing and wires label offsets, detached text, and bend radius into relation-graph configuration.
const graphOptions: RGOptions = {
debug: false,
defaultNodeWidth: 70,
defaultNodeHeight: 30,
defaultLineWidth: 2,
defaultLineShape: RGLineShape.SimpleOrthogonal,
defaultJunctionPoint: (layoutFrom === 'left' || layoutFrom === 'right') ? RGJunctionPoint.lr : RGJunctionPoint.tb,
defaultLineTextOnPath: false,
defaultLineTextOffsetX: textOffsetX,
defaultLineTextOffsetY: textOffsetY,
defaultPolyLineRadius: defaultPolyLineRadius,
This branch shows how the example changes orientation and spacing rules before asking the graph to lay itself out again.
const updateMyOptions = () => {
const layoutOptions = graphOptions.layout as RGTreeLayoutOptions;
if (layoutFrom === 'left' || layoutFrom === 'right') {
graphInstance.updateOptions({
layout: {
...layoutOptions,
from: layoutFrom,
treeNodeGapH: 200,
treeNodeGapV: 10,
layoutDirection: 'h'
}
});
This function is the core line-level mutation pattern: it rewrites placement, anchoring, and first-bend distance on every current edge.
const updateTextPlacementAndAnchor = () => {
for (const line of graphInstance.getLines()) {
graphInstance.updateLine(line, {
placeText,
textAnchor,
polyLineStartDistance
});
}
};
This export flow comes from the shared floating settings panel and shows how the graph DOM is prepared for image capture.
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');
}
What Makes This Example Distinct
According to the comparison data, this example is not just another line-label demo. Compared with line-text-position, it keeps the line shape fixed on SimpleOrthogonal and stays focused on detached orthogonal label semantics rather than switching across shape families or text-on-path modes. Compared with text-on-curve, it is about off-path readability around bends, not curved path text or truncation. Compared with adv-line-slot3 and ever-changing-tree, it puts more weight on line-level placement logic by combining placeText, textAnchor, polyLineStartDistance, direction-aware relayout, and corner-radius tuning in one smaller diagnostic surface.
The comparison record also highlights an unusual combination: a minimal orthogonal tree, blue-emphasized detached labels, four-way direction switching, per-line placement and anchor rewrites, first-bend control, and graph-level offset and radius controls in the same floating panel. That makes this example a stronger starting point for label-legibility work than for general line styling or graph editing.
Where Else This Pattern Applies
This pattern transfers well to tree-shaped UIs where edge labels must remain readable after layout direction changes. Examples include org charts with role annotations, approval trees with decision labels, service dependency trees with protocol or ownership text, menu or taxonomy browsers with edge semantics, and workflow trees that need printed or exported screenshots. The reusable idea is to keep the data stable while exposing both graph-level defaults and per-line overrides, so teams can tune label readability before they commit to a final visual language.