JavaScript is required

Custom Line Arrow Presets

This example shows how to define four custom SVG arrowhead markers and apply them through relation-graph's `defaultLineMarker` option. A floating control window lets users switch arrow presets and compare the same hierarchy in tree and center layouts, while shared controls add canvas settings and image export.

Comparing Custom Arrowhead Presets Across Tree and Center Layouts

What This Example Builds

This example builds a small subsystem hierarchy viewer and turns it into an arrowhead comparison playground. The graph starts as a left-to-right tree with rectangular nodes, curved connectors, rounded line corners, and labels rendered along the line path. A floating panel lets the user switch among four custom SVG arrow presets and re-render the same hierarchy in either tree or center layout, while the shared panel shell also adds drag, minimize, settings, and image export controls.

The main point is not custom line rendering. It is a focused demonstration of how far the built-in relation-graph line renderer can be pushed by supplying marker objects through defaultLineMarker and updating those options on an already loaded graph instance.

How the Data Is Organized

The graph data is assembled inline as one RGJsonData object inside initializeGraph(). It has a fixed rootId, fifteen named nodes, and fourteen lines, all labeled Subsystem. There is no fetch step, no external normalization step, and no per-line styling payload.

The only preprocessing is the module-level myArrows array. Each entry is a raw SVG marker definition with its own viewBox, marker size, reference point, and path data. React state selects one of those definitions, and that marker becomes the graph-wide default arrowhead.

In a real system, the same data shape could represent equipment hierarchies, service dependencies, workflow stages, or any small rooted structure where a single line-end convention should be evaluated across the whole graph.

How relation-graph Is Used

index.tsx wraps the example in RGProvider, and MyGraph.tsx renders a single RelationGraph instance with one graphOptions object. Those options establish the initial left-to-right tree, set explicit node spacing, place the built-in toolbar at the bottom center, enable rounded polyline corners, and keep line labels attached to the line path.

The example relies on RGHooks.useGraphInstance() instead of rebuilding the graph component. On mount it calls setJsonData(...), then moveToCenter() and zoomToFit(). When the layout mode changes it calls updateOptions(...), doLayout(), moveToCenter(), and zoomToFit() again. When the selected arrow preset changes it updates only defaultLineMarker, which keeps the dataset intact and changes presentation in place.

This demo does not use node slots, line slots, canvas slots, or editing APIs. It stays on the built-in relation-graph renderer and customizes it through options plus a local stylesheet override. The SCSS keeps the graph under .my-graph and forces .rg-node to render with a white background, which makes the different marker shapes easier to read on a light canvas.

The floating panel is also part of the implementation pattern. DraggableWindow supplies the movable shell, SimpleUISelect supplies the chip-style selectors, and the shared CanvasSettingsPanel adds wheel-mode switching, drag-mode switching, and image export through graph-instance APIs. Those utilities are real features in the page, but they are inherited scaffolding rather than the distinctive lesson of this example.

Key Interactions

  • Selecting My Arrow 1 through My Arrow 4 updates myArrowIndex and immediately replaces defaultLineMarker on the live graph.
  • Switching between Tree and Center changes layoutName and also remaps node shape, line shape, and junction behavior before calling doLayout().
  • The floating control window can be dragged by its title bar and collapsed with the minimize button.
  • The gear button opens a shared settings overlay where wheel behavior can switch between scroll, zoom, and none, and canvas drag behavior can switch between selection, move, and none.
  • The shared settings overlay can export the current graph image by preparing the graph DOM, rendering it to a blob with modern-screenshot, and downloading the result.

Key Code Fragments

This marker array is the core customization input: the example defines raw SVG arrowheads instead of replacing relation-graph’s line renderer.

const myArrows = [
    {
        viewBox: '0 0 12 12',
        markerWidth: 30,
        markerHeight: 30,
        refX: 3,
        refY: 3,
        data: 'M 0 0, V 6, L 4 3, Z'
    },
    // ...
];

This options block shows that the initial tree layout and the custom marker are configured together on the main graph instance.

const graphOptions: RGOptions = {
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.lr,
    toolBarDirection: 'h',
    toolBarPositionH: 'center',
    toolBarPositionV: 'bottom',
    defaultPolyLineRadius: 10,
    defaultLineTextOnPath: true,
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 200,
        treeNodeGapV: 10
    },
    defaultLineMarker: myArrows[myArrowIndex]
};

This branch proves that layout switching is more than a layout name change: it also swaps node geometry and connector behavior before rerunning layout.

if (layoutName === 'center') {
    graphInstance.updateOptions({
        defaultNodeShape: RGNodeShape.circle,
        defaultLineShape: RGLineShape.StandardStraight,
        defaultJunctionPoint: RGJunctionPoint.border,
        layout: {
            layoutName: layoutName
        }
    });
}
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();

This effect shows the live marker-switching pattern: only the selected arrowhead option changes, while the loaded data stays in place.

useEffect(() => {
    graphInstance.updateOptions({
        defaultLineMarker: myArrows[myArrowIndex]
    });
}, [myArrowIndex]);

What Makes This Example Distinct

The comparison data marks arrow preset selection and layout selection as very rare features in this example set, and that combination is the main reason this page stands out. It does not just show custom line styling once. It defines four raw SVG marker objects locally, applies them through defaultLineMarker, and lets the same dataset be judged in both a curved left-to-right tree and a straight-line center layout.

Compared with custom-line-animation and custom-line-style, this example puts much more emphasis on endpoint marker geometry than on CSS class switching, animation presets, or stroke-and-label skinning. Compared with div-on-line, it is a graph-wide arrow comparison demo rather than a selected-edge geometry or DOM-overlay demo. Compared with node-style2, it starts from a similar tree baseline but turns that baseline into a two-mode presentation comparison without changing the underlying hierarchy.

The result is a more focused starting point for teams that want to test arrow readability and direction semantics before they commit to deeper custom line rendering.

Where Else This Pattern Applies

This pattern can be reused when a team needs to validate line-end semantics on one stable dataset before building more specialized rendering. Examples include workflow diagrams that need different arrowheads for approval, rejection, or handoff states; equipment and network topologies that must be reviewed in both hierarchical and hub-style layouts; and design-system demo pages that compare connector presets without rewriting graph data.

It also fits internal QA or review tools where analysts need a small option panel, export capability, and fast visual comparison between multiple connector styles. The transferable lesson is to keep the data model simple, keep the built-in renderer, and use runtime option updates to compare presentation choices on the same graph.