JavaScript is required

Curved Line Path Text Controls

This example renders a fixed left-to-right tree whose long relationship labels follow curved edge paths by default. A floating control window changes curve family, junction anchors, path placement, text alignment, offsets, and maximum visible text length, while shared scaffolding adds canvas settings and image export.

Controlling Text on Curved Relation Graph Lines

What This Example Builds

This example builds a fixed left-to-right tree whose relationship labels are rendered directly on curved edge paths. The canvas stays visually simple, but a floating control window lets the user compare three curved line families, switch connection anchors, move the label along the path, change text alignment, and tune offsets and visible text length.

The main point is not custom drawing. It is a focused reference for making long edge labels readable on curved connectors with relation-graph’s built-in line renderer and live instance APIs.

How the Data Is Organized

The graph data is a static inline RGJsonData object inside MyGraph.tsx. It uses rootId: 'a', sixteen hard-coded nodes, and fifteen hard-coded lines, and every line repeats the same long text: These relationship texts are very long.

There is no fetch step and no preprocessing before setJsonData(). The preparation happens in the options object instead: line shape, junction point, line-text length, and layout settings are derived from React state or local literals before the graph is rendered. In a real application, the same structure could stand in for dependency explanations, approval conditions, ownership relationships, or any tree where the edge text carries meaningful business wording.

How relation-graph Is Used

The example is wrapped in RGProvider, and RGHooks.useGraphInstance() supplies the live graph instance. RelationGraph receives a state-derived graphOptions object that keeps the layout fixed as a tree growing from the left with treeNodeGapH: 200 and treeNodeGapV: 30. The same options object also sets uniform node dimensions, places the built-in toolbar in the lower-right corner, enables defaultLineTextOnPath, and binds the default curved line family and junction policy to component state.

Graph initialization is explicit. initializeGraph() calls setJsonData(), then moveToCenter() and zoomToFit(), so each reload frames the sample tree immediately. When the selected lineShape changes, the code reruns initializeGraph() because defaultLineShape matters when the lines are created. For the other label controls, the component keeps the current graph mounted and uses graphInstance.updateOptions() plus graphInstance.getLines() and graphInstance.updateLine() to rewrite the live lines in place.

There are no custom node, line, canvas, or viewport slots in this demo, and there are no graph event handlers. The example stays close to the built-in renderer and customizes it through options, instance APIs, and SCSS. my-relation-graph.scss enlarges node text and colors .rg-line-text blue so the path labels remain the visual focus.

The floating panel is provided by the shared DraggableWindow helper rather than by demo-specific graph code. That shared helper adds dragging, minimizing, a settings overlay, canvas wheel and drag mode switches, and image export through prepareForImageGeneration(), domToImageByModernScreenshot(), and restoreAfterImageGeneration().

Key Interactions

  • The Line Shape selector switches among RGLineShape.StandardCurve, RGLineShape.Curve2, and RGLineShape.Curve5, then reloads the graph so the chosen default curve applies to all edges.
  • The Junction Point selector swaps between RGJunctionPoint.border and RGJunctionPoint.lr, which changes how curved lines attach to nodes.
  • The Line Text Anchor selector rewrites every current line to use start, middle, or end alignment.
  • The placeText selector rewrites every current line so the label sits near the start, center, or end of the path.
  • The x and y range inputs update the graph-wide line-text offsets without rebuilding the data.
  • The max-length slider changes the configured label length limit used by the graph options.
  • The floating utility window can be dragged or minimized, and its shared settings panel can change canvas wheel and drag behavior or export the graph as an image.

Key Code Fragments

This fragment shows that the core behavior is driven by relation-graph options rather than custom SVG rendering.

const graphOptions: RGOptions = {
    defaultLineShape: lineShape,
    defaultJunctionPoint: junctionPoint,
    defaultLineTextOnPath: true,
    lineTextMaxLength: lineTextMaxLength,
    defaultLineTextOffsetX: 2,
    defaultLineTextOffsetY: -3,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 200,
        treeNodeGapV: 30
    }
};

This fragment proves that the example uses a small static tree with repeated long edge labels to isolate text readability.

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a' },
        { id: 'b', text: 'b' },
        // ...
    ],
    lines: [
        { from: 'a', to: 'b', text: 'These relationship texts are very long' },
        // ...
    ]
};

This fragment shows the load path that inserts the JSON data and immediately frames it in the viewport.

const initializeGraph = async () => {
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

This fragment is the key runtime pattern for live label retuning: graph options are updated first, then every mounted line is rewritten for placement and anchor alignment.

graphInstance.updateOptions({
    defaultJunctionPoint: junctionPoint as RGJunctionPoint,
    lineTextMaxLength: lineTextMaxLength,
    defaultLineTextOffsetX: textOffsetX,
    defaultLineTextOffsetY: textOffsetY
});

const lines = graphInstance.getLines();
lines.forEach(line => {
    graphInstance.updateLine(line.id, {
        placeText,
        textAnchor: lineTextAnchor
    });
});

This fragment shows that changing the curve family is handled by reloading the graph, not only by mutating the mounted options.

useEffect(() => {
    initializeGraph();
}, [lineShape]);

This fragment shows the shared image-export flow used by the floating settings panel.

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

The comparison data shows that this example stands out because it stays committed to curved text-on-path labels from the start and then exposes several readability controls from one small floating panel. Compared with line-text-position, it does less orientation comparison but goes deeper into always-on curved path text through placeText, textAnchor, junction switching, and max-length tuning on a fixed left-to-right tree.

It is also distinct from nearby line-presentation demos such as custom-line-style, line-style2, and custom-line-animation. Those examples lean harder on CSS class families, checked-line emphasis, or motion presets. This example keeps the built-in renderer visually plain and uses geometry plus label options as the teaching surface. The most distinctive implementation pattern is the graph-wide getLines() plus updateLine() pass that rewrites path-label placement on already rendered curved edges.

Where Else This Pattern Applies

This pattern transfers well to systems where the relationship text itself carries meaning and must remain readable on curved connectors. Examples include approval trees with rule descriptions, dependency graphs with explanatory edge text, ownership maps, process branches, and knowledge graphs where the edge phrase matters as much as the node label.

It also works as a design-review sandbox. Teams can swap in their own representative tree data, keep the same control surface, and quickly decide which curve family, anchor policy, truncation limit, and text offsets make long edge labels readable before they build heavier custom rendering.