JavaScript is required

Preset Toolbar Node and Line Creation

This example shows a fixed-layout relation-graph editor that uses a view-mounted preset palette to drag-create styled nodes and click-create styled lines. It also demonstrates mixed node rendering through a custom card slot, active creation-state feedback, a shared floating settings panel, and image export helpers.

Preset Toolbar Node and Line Creation on a Fixed Graph Canvas

What This Example Builds

This example builds a lightweight graph-authoring canvas rather than a read-only graph viewer. The screen shows a fixed-position starter graph on a dotted background, an orange preset palette mounted inside the graph view, a shared floating help window, and support for both plain text nodes and a richer card-style node.

The main user actions are structural edits. Users drag node presets from the left palette onto the canvas to create new nodes, click line presets to enter guided connection mode, and complete links by selecting existing nodes. One preset creates a myCard node that renders through a custom slot instead of the default text node body.

The most important design idea is that node creation and line creation share the same in-view preset workflow. The example is therefore a compact reference for template-driven graph building with mixed node styles.

How the Data Is Organized

The initial graph data is declared inline in initializeGraph() as one RGJsonData object with rootId, a flat nodes array, and a flat lines array. The seed nodes already contain explicit x and y positions because the graph uses a fixed layout, and individual nodes override border, fill, and font settings to show different style outcomes.

There are two additional data layers beyond the seed graph. nodeTemplates holds reusable partial node definitions for rectangles, circles, and the myCard variant, while lineTemplates holds reusable partial line definitions for straight, curved, and orthogonal links. Before the starter graph is loaded, missing line ids are normalized. Before a new node or line is created at runtime, the selected template is deep-cloned so the live graph mutation does not reuse the same object reference.

The runtime insertion step also performs small preprocessing. New nodes derive their size from the selected template and fall back to 96x96 when the template omits dimensions, then the final x and y are shifted so the dropped node is centered around the pointer position. In a real product, the same structure could represent workflow steps, equipment symbols, topology devices, organization roles, or any other curated set of allowed graph elements.

How relation-graph Is Used

The entry file wraps the demo in RGProvider, and both MyGraph and the toolbar access the live graph through RGHooks.useGraphInstance(). The main graph options set layout.layoutName to fixed, keep the default node shape rectangular, use straight lines and border junction points by default, set the default line color and width, and place the built-in toolbar on the right while the custom preset palette is mounted separately on the left.

The example uses two slot extension points. RGSlotOnNode switches rendering by node type: normal nodes show a simple label, while myCard nodes render MyCardNodeContent, which itself wraps the shared Simple3DCard visual shell. RGSlotOnView mounts DragToCreateToolbar directly inside the graph viewport so creation tools stay attached to the canvas rather than living in an external page layout.

The graph instance API drives both initialization and editing. setJsonData() and moveToCenter() load the starter graph, while startCreatingNodePlot(...), startCreatingLinePlot(...), generateNewUUID(), addNodes(...), and addLines(...) implement runtime authoring. The toolbar also checks getNodes() before allowing line creation, so the line tool is gated by existing visible nodes.

Hooks are used for editor state, not only for graph access. useCreatingNode() and useCreatingLine() reflect the active creation mode back into the preset chip styling, and the shared floating DraggableWindow uses useGraphStore() plus setOptions() to expose wheel and drag behavior switches. The same window runs the export flow with prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration().

Styling is split across the example stylesheet and shared visual helpers. my-relation-graph.scss creates the scale-aware dotted canvas and fills selected line labels with the current line color, DragToCreateToolbar.scss defines the translucent orange sidebar and active chip halo, and Simple3DCard.scss supplies the glow, tilt, and hologram treatment reused by both the preset preview and the rendered card node.

Key Interactions

Dragging a node preset is the primary creation flow. Pressing a node chip starts startCreatingNodePlot(...), and releasing on the canvas inserts a new node whose style comes from the chosen template.

Clicking a line preset is the second creation flow. The toolbar first checks whether at least one visible node exists; otherwise it shows an error message and refuses to enter line mode.

When line mode is allowed, the UI shows a success message prompting the user to click nodes. The line is only persisted if the completed target resolves to a node id, so unfinished connections to empty space are ignored.

The palette also exposes mode feedback. While relation-graph reports that node or line creation is active, the corresponding chips receive the active styling class.

The floating helper window adds secondary interaction. It can be dragged, minimized, switched into a settings panel, used to change wheel behavior between scroll, zoom, and none, used to change canvas drag behavior between selection, move, and none, and used to export the current graph as an image.

Key Code Fragments

This fragment shows that the graph is configured as a fixed-layout editing surface with explicit default node and line behavior.

const graphOptions: RGOptions = {
    debug: false,
    defaultLineColor: '#43a2f1',
    defaultLineWidth: 2,
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardStraight,
    defaultJunctionPoint: RGJunctionPoint.border,
    toolBarPositionH: 'right',
    layout: {
        layoutName: 'fixed'
    }
};

This fragment shows the inline seed graph and the normalization step that assigns ids before setJsonData().

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'Border color', borderColor: '#43a2f1', x: -48, y: -48 },
        { id: 'a1', text: 'No border', borderWidth: -1, color: '#ff8c00', x: 271.2, y: -48 },
        { id: 'a2', text: 'Plain', borderWidth: 3, borderColor: '#ff8c00', fontColor: '#ff8c00', color: 'transparent', x: 71.15, y: 243.9 }
    ],
    lines: [{ from: 'a', to: 'a1' }, { from: 'a', to: 'a2' }]
};

myJsonData.lines.forEach((line, index) => {
    if (!line.id) line.id = `line-${index}`;
});

This fragment proves that node creation is template-driven and that new nodes are centered around the drop point.

graphInstance.startCreatingNodePlot(event, {
    templateText: tempNode.text,
    templateNode: JSON.parse(JSON.stringify(tempNode)),
    onCreateNode: (x: number, y: number) => {
        const nodeSize = { width: (tempNode.width || 96), height: (tempNode.height || 96) };
        const newId = graphInstance.generateNewUUID();
        graphInstance.addNodes([Object.assign({}, tempNode, {
            id: 'newNode-' + newId,
            text: 'New node' + newId,
            x: x - (nodeSize.width / 2),
            y: y - (nodeSize.height / 2)
        })]);
    }
});

This fragment shows that guided line creation is validated before the new edge is appended to the graph.

if (!graphInstance.getNodes().some((node: RGNode) => !node.opacity || node.opacity > 0)) {
    return SimpleGlobalMessage.showMessage({ type: 'error', message: 'Please create the node first!' });
}

graphInstance.startCreatingLinePlot(event, {
    template: JSON.parse(JSON.stringify(template)),
    onCreateLine: (from: RGNode, to: RGNode | RGPosition, finalTemplate: JsonLine) => {
        if (to.id) {
            const newLineId = graphInstance.generateNewUUID();
            graphInstance.addLines([Object.assign({}, finalTemplate, {
                from: from.id,
                to: to.id
            })]);
        }
    }
});

This fragment shows how the example combines a custom node slot with a view-mounted toolbar in the same RelationGraph.

<RelationGraph options={graphOptions}>
    <RGSlotOnNode>
        {({ node }: RGNodeSlotProps) => {
            if (node.type === 'myCard') {
                return <MyCardNodeContent name={node.text} />;
            }
            return <div className="rg-node-text">{node.text}</div>;
        }}
    </RGSlotOnNode>
    <RGSlotOnView>
        <DragToCreateToolbar />
    </RGSlotOnView>
</RelationGraph>

This fragment shows that the floating settings panel mutates live canvas behavior and runs the image export flow through the graph instance.

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

According to the comparison data, the most distinctive part is the single preset palette that handles both authoring modes inside the graph view. Users drag styled node chips to create nodes and click styled line chips to start guided edge creation from the same left-side toolbar. Nearby editing examples also mutate the graph at runtime, but they do not emphasize this compact palette-driven workflow in the same way.

Compared with amount-summarizer, this example is narrower and more generic. Both examples use preset-driven creation, but amount-summarizer layers in editing logic such as recomputation, deletion flows, and relayout, while this one stays focused on styled creation patterns and mixed node rendering.

Compared with create-object-from-menu, the reusable lesson here is a persistent in-canvas palette rather than a context menu. Compared with freely-draw-lines-on-canvas and edit-node-text, the emphasis is reusable style presets and runtime graph growth, not freehand geometry capture or inline label editing on a static graph.

Another distinct combination comes from the visuals and slot usage: a fixed dotted canvas, an orange preset sidebar, active-mode chip highlighting through useCreatingNode() and useCreatingLine(), plain text nodes mixed with a myCard renderer, and shared 3D card styling reused in both the palette preview and the final node output.

Where Else This Pattern Applies

This pattern transfers well to workflow builders, system architecture editors, topology sketchers, process designers, and any other tool where users should compose graphs from a controlled library of approved node and edge styles instead of creating arbitrary shapes.

A production version could keep the same preset tables and slot-based rendering while replacing the inline sample data with server data, attaching business metadata to each template, enforcing connection rules before addLines(...), or adding persistence and undo history. The core reusable idea is stable: keep graph editing lightweight by turning curated templates into guided relation-graph creation flows.