JavaScript is required

Customize Line Text Position

This example builds a small tree graph whose edge labels can be compared across detached-label and text-on-path modes. A floating control window drives x and y label offsets, tree-direction relayout, and default line-shape changes, making it a compact reference for line-label readability tuning.

Tuning Line Text Position in a Tree Relation Graph

What This Example Builds

This example builds a small tree graph that is meant to compare how edge labels behave under different rendering rules. The canvas shows a fixed subsystem hierarchy, while a floating control window lets the user switch between detached labels and text rendered on the line path, move labels with x and y offsets, rotate the tree direction, and swap connector shapes.

The main visual focus is the line text itself. Blue label styling, a restrained node style, and a compact dataset keep attention on readability changes instead of domain-specific content.

How the Data Is Organized

The graph data is a static inline RGJsonData object created inside initializeGraph(). It uses rootId: '2', nine nodes, and eight lines, and every line carries the same text: 'Subsystem' label so the demo can isolate label placement behavior instead of content differences.

There is no external fetch step and no per-line preprocessing before setJsonData(). The important preparation happens in the options object: defaultLineShape, defaultJunctionPoint, defaultLineTextOnPath, defaultLineTextOffsetX, and defaultLineTextOffsetY are all derived from React state before the graph is rendered. In a real product, the same structure could represent equipment hierarchies, organization trees, module dependencies, or any tree where the edge text needs to remain legible across multiple orientations.

How relation-graph Is Used

The example mounts RelationGraph inside RGProvider, then retrieves the live instance through RGHooks.useGraphInstance(). On first render it calls setJsonData(), then applies layout updates and runs doLayout(), moveToCenter(), and zoomToFit() so the small tree is immediately framed in the viewport.

The layout is always a tree layout, but the direction is dynamic. When layoutFrom changes, the code calls graphInstance.updateOptions() to rewrite layout.from and to swap treeNodeGapH and treeNodeGapV depending on whether the tree is horizontal or vertical. The default junction point is also derived from the current direction and line shape, so the label comparison is tied to connector geometry rather than to one fixed routing style.

The label behavior is driven mostly through graph options passed to RelationGraph as props. defaultLineTextOnPath, defaultLineTextOffsetX, and defaultLineTextOffsetY come directly from component state. defaultLineShape also comes from state, but the example reloads the graph when that state changes because the sample is specifically demonstrating how a default line shape is applied when line data is added.

There are no custom node, line, canvas, or viewport slots in this demo, and there are no graph event handlers. Customization happens through shared helper components and stylesheet overrides instead: DraggableWindow hosts the floating controls, SimpleUISelect renders segmented selectors, CanvasSettingsPanel exposes shared canvas settings and image export, and my-relation-graph.scss recolors node text plus both SVG and HTML line-label styles.

Key Interactions

  • The x-offset slider changes textOffsetX from -100 to 100, which shifts the default label position horizontally.
  • The y-offset slider changes textOffsetY from -40 to 40, which shifts the default label position vertically.
  • The text-mode selector switches between Normal Text and Text On Path, making it easy to compare detached labels with labels mounted on the line itself.
  • The tree-direction selector changes the layout between left, right, top, and bottom, then reruns layout and recenters the graph.
  • The line-shape selector switches among straight, curved, and orthogonal defaults, then reinitializes the graph so newly loaded lines inherit the chosen default shape.
  • The floating window can be dragged, minimized, and expanded into a shared settings overlay where canvas wheel mode, drag mode, and image export are available.

Key Code Fragments

This fragment shows that the example uses a fixed inline dataset rather than loading data from an external source.

const myJsonData: RGJsonData = {
    rootId: '2',
    nodes: [
        { id: '2', text: 'ALTXX' },
        { id: '3', text: 'CH2 TTN' },
        // ...
    ],
    lines: [
        { from: '2', to: '5', text: 'Subsystem' },
        // ...
    ]
};
await graphInstance.setJsonData(myJsonData);

This fragment is the core of the demo: line-label mode, offsets, shape, and junction behavior are all derived from component state and passed into RelationGraph.

const graphOptions: RGOptions = {
    defaultLineShape: lineShape,
    defaultJunctionPoint:
        lineShape === RGLineShape.StandardStraight
            ? RGJunctionPoint.border
            : ((layoutFrom === 'left' || layoutFrom === 'right') ? RGJunctionPoint.lr : RGJunctionPoint.tb),
    defaultLineTextOnPath: textOnPath,
    defaultLineTextOffsetX: textOffsetX,
    defaultLineTextOffsetY: textOffsetY,
    layout: { layoutName: 'tree', from: 'left', treeNodeGapV: 30, treeNodeGapH: 150 }
};

This fragment shows how direction changes update the live tree layout and swap the gap values for horizontal versus vertical comparisons.

if (layoutFrom === 'left' || layoutFrom === 'right') {
    graphInstance.updateOptions({
        layout: { ...layoutOptions, from: layoutFrom, treeNodeGapH: 150, treeNodeGapV: 30 }
    });
} else {
    graphInstance.updateOptions({
        layout: { ...layoutOptions, from: layoutFrom, treeNodeGapH: 30, treeNodeGapV: 150 }
    });
}

This fragment proves that line-shape changes are handled by rebuilding the graph data, not only by calling updateOptions().

useEffect(() => {
    // When lineShape changes, data needs to be re-set because the expected effect
    // of lineShape changes on defaultLineShape takes effect when adding line data
    initializeGraph();
}, [lineShape]);

This fragment shows the shared helper path for canvas settings and screenshot export inside the floating window.

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

What Makes This Example Distinct

Compared with nearby label-focused examples such as text-on-curve and text-on-orthogonal, this demo is more about graph-wide defaults than per-line editing. It does not go deep into per-edge anchor or bend controls. Instead, it concentrates on how the same fixed tree behaves when the whole graph switches between detached labels and text-on-path mode while also changing direction and default line shape.

That makes it a stronger starting point when the question is, “Are my default edge labels still readable after I rotate the tree or swap routing styles?” The comparison data also shows that this example combines several uncommon features in one compact playground: x and y label-offset sliders, a normal-text versus text-on-path switch, cross-shape comparison, and four-way tree relayout with orientation-aware junction-point changes.

It is also distinct from layout tuning examples such as center-layout-options and graph-angle-offset. Those examples are closer to general layout adjustment, while this one keeps edge-label readability as the primary subject.

Where Else This Pattern Applies

This pattern transfers well to internal tools where one edge label rule must work across multiple layouts without hand-tuning every connection. Examples include subsystem diagrams, process handoff trees, dependency maps, approval chains, and organization structures where the same label text must remain readable after the graph is rotated or restyled.

It is also useful as a QA sandbox. Teams can wire their own sample data into the same control pattern to validate default edge-label behavior before they invest in more advanced per-line customization.