Custom Selected Line Toolbar
This example shows how to attach a custom floating toolbar to the currently selected line in a relation-graph canvas. The toolbar follows the line's live geometry and lets users change line color, shape, and width or remove the line, while the surrounding helper window also exposes canvas settings and image export.
Custom Selected Line Toolbar
What This Example Builds
This example builds a small relation-graph editing workspace where clicking an existing line opens a floating toolbar above that line. The toolbar lets users change the selected line’s color, shape, and width, or remove the line entirely.
The visual result is intentionally editor-like rather than domain-specific. A dotted canvas background, a helper window, and a graph-attached overlay make the example read as a focused reference for custom line maintenance. The key point is that the line toolbar is not fixed screen UI; it follows the currently selected line’s live geometry as the graph moves or zooms.
How the Data Is Organized
The graph data is assembled inline as a single RGJsonData object with a rootId, a small nodes array, and a lines array. Most lines inherit the graph defaults, while line l4 is seeded with an orthogonal shape and explicit junction points so the canvas starts with both the default curved style and one preconfigured exception.
There is no separate preprocessing pipeline before setJsonData(). The only meaningful preparation is the inline definition of the sample dataset and the per-line override for l4. In a production graph, the same structure could represent workflow branches, dependency links, approval routes, or any edge-centric dataset where line styling or deletion is part of the user workflow.
How relation-graph Is Used
The demo uses RGProvider at the entry level so the nested components can consume relation-graph hooks. Inside MyGraph, RGHooks.useGraphInstance() drives the full lifecycle: it loads the inline JSON with setJsonData(), fits the view with zoomToFit(), updates line properties with updateLine(), removes lines with removeLine(), and coordinates editing targets through setEditingNodes() and setEditingLine().
Graph options are minimal but deliberate. The demo sets defaultLineShape to RGLineShape.StandardCurve, defaultJunctionPoint to RGJunctionPoint.ltrb, and defaultLineColor to #00a63e, which establishes the green curved baseline that the toolbar then overrides on demand.
The custom overlay pattern is the most important relation-graph technique here. RGSlotOnView mounts two graph-surface components: RGEditingConnectController and the custom MyLineToolbar. MyLineToolbar uses RGHooks.useEditingLine() to read the currently selected line plus its live start and end points, then computes a midpoint-based translate transform so the toolbar stays attached to that line instead of to the browser viewport.
The example also customizes the surrounding workspace. The SCSS file adds a dotted editing background that scales with graph transform variables, and the shared DraggableWindow reads graph options through RGHooks.useGraphStore(), exposes runtime changes for wheel and drag behaviors through setOptions(), and uses prepareForImageGeneration() plus restoreAfterImageGeneration() to export the graph as an image.
Key Interactions
Clicking a line makes that line the active editing target, which causes the custom toolbar to appear above it. From that moment, the toolbar becomes the main control surface for edge maintenance.
Clicking a color swatch, shape chip, or width chip applies a targeted updateLine() patch to the active line. This keeps the editing model simple: one selected line, one floating action surface, and immediate visual feedback.
Clicking the delete button removes the active line, shows a warning-style global message, clears the current editing line, and resets checked state. The delete flow therefore handles both the graph mutation and the related UI cleanup.
Node clicks and canvas gestures manage edit-target handoff. Plain node clicks replace the editing-node set, modifier-assisted node clicks toggle membership, and blank-canvas clicks clear both node and line editing state. When the canvas is in selection mode, finishing a marquee selection replaces the editing-node set with the nodes inside the selection view.
Key Code Fragments
This fragment shows the inline graph dataset and the seeded per-line exception for l4.
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'Border color' },
{ id: 'a1', text: 'No border' },
{ id: 'a2', text: 'Plain' },
{ id: 'a1-1', text: 'Text Node' },
{ id: 'a1-4', text: 'XXX' },
{ id: 'b', text: 'Font color' },
// ...
],
lines: [
{ id: 'l1', text: 'Line1 Text', from: 'a', to: 'b' },
{ id: 'l4', text: 'Line4 Text', from: 'a', to: 'a2', lineShape: RGLineShape.StandardOrthogonal, fromJunctionPoint: RGJunctionPoint.left, toJunctionPoint: RGJunctionPoint.right },
// ...
]
};
This fragment shows how click and selection events hand off editing state between nodes, lines, and the empty canvas.
const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
if ($event.shiftKey || $event.ctrlKey || ($event.metaKey && !$event.altKey)) {
graphInstance.toggleEditingNode(nodeObject);
} else {
graphInstance.setEditingNodes([nodeObject]);
}
graphInstance.setEditingLine(null);
};
const onLineClick = (lineObject: RGLine, linkObject: RGLink) => {
graphInstance.setEditingLine(lineObject);
};
This fragment shows the custom overlay being mounted directly on the graph view instead of in a separate side panel.
<RelationGraph
options={graphOptions}
onCanvasSelectionEnd={onCanvasSelectionEnd}
onCanvasClick={onCanvasClick}
onNodeClick={onNodeClick}
onLineClick={onLineClick}
>
<RGSlotOnView>
<RGEditingConnectController />
<MyLineToolbar onLinePropChange={onLinePropChange} onRemoveLine={onRemoveLine} />
</RGSlotOnView>
</RelationGraph>
This fragment shows why the toolbar can follow the selected line’s geometry and apply direct property edits.
const editingLine = RGHooks.useEditingLine();
const toolbarXyOnCanvas = {
x: (editingLine.startPoint.x + editingLine.endPoint.x) / 2,
y: Math.min(editingLine.startPoint.y, editingLine.endPoint.y),
};
return (
editingLine.line ? <div
style={{
transform: `translate(-50%, -100%) translate(${toolbarXyOnCanvas.x}px, ${toolbarXyOnCanvas.y - 20}px)`
}}
>
This fragment shows the line-action surface itself: property chips call onLinePropChange(...), while deletion calls onRemoveLine(...).
{[
{ value: RGLineShape.StandardStraight, text: 'Straight' },
{ value: RGLineShape.StandardOrthogonal, text: 'Orthogonal' },
{ value: RGLineShape.StandardCurve, text: 'Bezier' }
].map((shape) => (
<div
key={shape.value}
onClick={() => onLinePropChange(editingLine.line!, 'lineShape', shape.value)}
>
{shape.text}
</div>
))}
What Makes This Example Distinct
Compared with nearby examples such as change-line-path, change-line-vertices, and change-line-text, this demo changes the selected-line control surface rather than the selected-line editing target. Those examples keep the built-in RGEditingLineController visible for direct manipulation of route, endpoints, or labels. This one omits that controller from the rendered graph and instead uses RGSlotOnView plus RGHooks.useEditingLine() to attach a custom floating action card to the active line.
It also combines several features that are relatively uncommon together in the example set: selection-driven edit-target management, runtime line mutation through updateLine() and removeLine(), midpoint-based overlay positioning from live line geometry, and a shared helper window that exposes canvas settings and export actions. The dotted canvas and helper window are not unique on their own, but the custom selected-line toolbar pattern is the center of gravity here.
Compared with editor-button-on-line, this example is narrower and more focused. It does less overall editor work, but it goes further on selected-line property editing by offering color, shape, width, and deletion from one dedicated overlay. Compared with line-vertex-on-node, it is about maintaining an existing line after selection, not creating a new connection from a selected node.
Where Else This Pattern Applies
This pattern transfers well to graph tools where edges carry workflow meaning and need lightweight inline actions. Examples include approval-route editors, dependency maps, API call graphs, network topology maintenance tools, and process diagrams where users need to restyle, disable, or remove a connection without opening a modal.
The same approach also fits products that require branded or domain-specific edge controls. A team can keep relation-graph for layout, viewport behavior, and editing state, but replace the default selected-line UI with business-specific actions such as changing priority, assigning ownership, toggling route policies, or opening a custom edge details panel.