Line Endpoint Editing
A compact relation-graph editor that lets users click an existing edge and drag its start or end handle to a different node. The demo keeps line-text and path editing disabled, adds toast feedback around the vertex-move lifecycle, and exposes canvas settings plus image export from a floating helper window.
Retarget Existing Line Endpoints
What This Example Builds
This example builds a small editor-style graph where users can select an existing line and drag its visible start or end handle to a different node. The graph fills the viewport, uses a dotted work-surface background, and keeps a floating helper window above the canvas. The main point is narrow on purpose: it demonstrates endpoint reconnection without exposing line-text editing or path editing.
How the Data Is Organized
The graph data is declared inline as a single RGJsonData object in MyGraph.tsx. It contains one rootId, an array of nodes with id and text, and an array of labeled lines with id, text, from, and to.
There is no local preprocessing step before setJsonData(...). The component creates the final JSON literal, loads it directly into the graph instance, and then calls zoomToFit(). In a business graph, the same structure could represent dependencies, approval chains, topology links, or any record where an edge may need to be reattached to a different endpoint later.
How relation-graph Is Used
The example is wrapped in RGProvider, then RGHooks.useGraphInstance() is used as the central control surface for initialization, editing-state updates, and helper-window actions. RelationGraph receives a compact options object that sets defaultLineShape to RGLineShape.StandardCurve and defaultLineColor to #00a63e.
Editing UI is added through RGSlotOnView, where the example mounts both RGEditingConnectController and RGEditingLineController. The line controller is configured with textEditable={false} and pathEditable={false}, which keeps the interaction focused on start and end reconnection only.
The graph instance API drives the rest of the workflow:
setJsonData()andzoomToFit()load and frame the graph on mount.toggleEditingNode(),setEditingNodes(), andsetEditingLine()manage which nodes or line are currently in editing mode.getNodesInSelectionView()turns a drag-box selection into the active editing-node set.clearChecked()resets checked state when the user clicks empty canvas.addLines()handles the controller callback result after a successful endpoint move.
The floating helper window adds a second layer of relation-graph integration. Its settings panel reads the live store with RGHooks.useGraphStore(), updates wheelEventAction and dragEventAction through setOptions(), and uses prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() for image export.
Styling is intentionally light but functional. The stylesheet builds the dotted canvas background and makes checked line labels inherit the current line color, then invert to white text on a filled label when selected.
Key Interactions
- Clicking a line stores that line as the active editing line, which is what enables the built-in endpoint handles.
- Dragging a selected line endpoint triggers
onMoveLineVertexStartfor a warning toast andonMoveLineVertexEndfor success or removal feedback. - Clicking a node without modifiers replaces the editing-node set with that one node; clicking with
Shift,Ctrl, or supportedMetabehavior toggles the node into or out of the current editing set. - Drag-box selection on the canvas resolves the nodes inside the selection area and promotes them to the editing-node set.
- Clicking the empty canvas clears editing nodes, clears the active editing line, and resets checked state so the user can exit the current edit context quickly.
- The floating window can expand into a settings panel where users switch wheel behavior, switch canvas drag behavior, and download an image of the graph.
Key Code Fragments
This fragment shows that the example starts from a static inline graph payload and loads it directly into relation-graph without local transformation.
const initializeGraph = async () => {
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'Border color' },
{ id: 'a1', text: 'No border' },
// nodes and lines omitted
]
};
await graphInstance.setJsonData(myJsonData);
graphInstance.zoomToFit();
};
This fragment shows the selection-driven handoff between editing nodes and the selected line.
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 endpoint-edit lifecycle callbacks that attach custom feedback to the built-in line editor.
const onMoveLineVertexStart = (type: RGLineEditPoint, line: RGLine) => {
SimpleGlobalMessage.warning('MoveLineVertexStart:' + type+ ':' + line.text);
};
const onMoveLineVertexEnd = (fromNode: RGNode | RGLineTarget | RGPosition, toNode: RGNode | RGLineTarget | RGPosition, newLineJson?: JsonLine) => {
if (newLineJson && newLineJson.from && newLineJson.to) {
graphInstance.addLines([newLineJson]);
SimpleGlobalMessage.success('LineVertexChanged:' + newLineJson.text);
} else {
SimpleGlobalMessage.error('Line Removed!');
}
};
This fragment shows that the visible line-edit overlay is constrained to endpoint work only.
<RGSlotOnView>
{/* Node connection point selector component */}
<RGEditingConnectController />
<RGEditingLineController
textEditable={false}
pathEditable={false}
onMoveLineVertexStart={onMoveLineVertexStart}
onMoveLineVertexEnd={onMoveLineVertexEnd}
/>
</RGSlotOnView>
This fragment shows the small style override that makes selected line labels read as part of the current editing target.
.rg-line-peel {
.rg-line-label {
color: var(--rg-line-color);
}
}
.rg-line-peel.rg-line-checked {
.rg-line-label {
background-color: var(--rg-line-color);
color: #fff;
}
}
What Makes This Example Distinct
Compared with nearby editing demos, this example is unusually strict about scope. The comparison data shows that its main differentiator is an endpoint-only workflow: it uses the built-in selected-line editor, but explicitly disables both line-text editing and line-path editing.
It also emphasizes the edit lifecycle more than many similar compact demos. onMoveLineVertexStart and onMoveLineVertexEnd are not just passive hooks here; they are used to surface warning, success, and failure messages, and to append the callback-provided newLineJson when the edit resolves to valid endpoints.
The comparison file also places this example close to change-line-text, change-line-path, customize-line-toolbar, and line-vertex-on-node, but for different reasons:
- Compared with
change-line-text, this is the narrower reference when the user should reconnect an edge without also editing its label. - Compared with
change-line-path, it focuses on endpoint retargeting instead of route reshaping. - Compared with
customize-line-toolbar, it relies on built-in endpoint handles instead of a custom line action bar. - Compared with
line-vertex-on-node, it edits an existing edge instead of authoring a new connection from a selected node.
That makes it a strong starting point for teams that need reconnection of existing relationships while keeping the editing surface smaller than a full graph authoring tool.
Where Else This Pattern Applies
This pattern transfers well to products where users need to repair or redirect existing relationships without opening broader edge-authoring controls.
- Dependency graphs where a service, package, or pipeline step needs to be reconnected to a new upstream or downstream node.
- Workflow maintenance tools where a reviewer or approver edge must be reassigned without exposing custom route editing.
- Network or infrastructure diagrams where a link needs to be moved from one device endpoint to another.
- Knowledge-graph curation tools where curators should be allowed to retarget an existing relation but not rewrite every visual detail of the edge.