JavaScript is required

Built-in Toolbar Position and Theme Switching

This example keeps relation-graph's built-in toolbar visible on a minimal one-node graph and lets users switch its direction, horizontal alignment, vertical alignment, and three CSS themes from a floating control window. It uses `updateOptions(...)` for live placement changes and wrapper-class SCSS overrides for `.rg-toolbar`, while the shared helper window also provides canvas settings and image export.

Runtime Built-in Toolbar Placement and Theme Switching

What This Example Builds

This example builds a full-height graph viewer whose main subject is relation-graph’s built-in toolbar rather than the graph data itself. The canvas loads a single node, keeps the default toolbar visible, and places a floating white control window above the graph.

Users can switch the toolbar between vertical and horizontal orientation, move it across three horizontal and three vertical anchor positions, and apply three wrapper-driven color themes. The same helper window can also be dragged, minimized, opened into a shared canvas-settings overlay, and used to export an image. The main point is that the toolbar is customized in place instead of being replaced by custom slot content.

How the Data Is Organized

The graph data is created inline inside initializeGraph() as one RGJsonData object with rootId: 'a', one node, and no lines. There is no external fetch, no derived layout payload, and no preprocessing stage beyond assembling that literal object.

After setJsonData() runs, the graph instance recenters the view with moveToCenter() and zoomToFit(). Every later change operates on toolbar options or wrapper classes, not on nodes or edges. In a real application, the same pattern could front a larger dataset, but this nearly empty graph keeps attention on toolbar behavior and CSS skinning.

How relation-graph Is Used

index.tsx wraps the demo in RGProvider, and MyGraph.tsx reads the active instance with RGHooks.useGraphInstance(). The example keeps its graph options narrow: debug: false, plus toolBarDirection, toolBarPositionH, and toolBarPositionV derived from React state. No custom layout, node slot, line slot, or editing API is introduced, because the graph content is intentionally minimal.

Initialization happens through graph-instance APIs. The component assembles one inline dataset, calls setJsonData(), then moveToCenter() and zoomToFit(). A second effect watches the toolbar direction and position states and pushes those values into the live graph through graphInstance.updateOptions(...).

Toolbar styling is handled outside the options object. The outer wrapper class my-toolbar-style-* changes with local state, and the SCSS file scopes red, green, and blue overrides to .rg-toolbar. The floating helper window comes from the shared DraggableWindow component. That helper uses RGHooks.useGraphStore() and graphInstance.setOptions() for wheel and drag settings, and it uses prepareForImageGeneration() plus restoreAfterImageGeneration() for image export. Those utilities are real interactions in this demo, but they are shared scaffolding rather than the unique lesson of this example.

Key Interactions

  • The style selector switches between the default toolbar skin and three wrapper-driven theme variants.
  • The horizontal, vertical, and direction selectors update React state, and a dependent effect pushes the new toolbar placement options into the live graph with updateOptions(...).
  • The floating control window can be dragged by its title bar, minimized, and opened into a settings overlay.
  • The shared settings overlay can change wheel behavior, change canvas drag behavior, and download an image of the current graph.

Key Code Fragments

This options block shows that the example’s own graph configuration is almost entirely about the built-in toolbar.

const graphOptions: RGOptions = {
    debug: false,
    toolBarDirection: toolBarDirection as 'v' | 'h',
    toolBarPositionH: toolBarPositionH as 'left' | 'center' | 'right',
    toolBarPositionV: toolBarPositionV as 'top' | 'center' | 'bottom'
};

This initialization code proves that the dataset is a one-node inline payload and that the viewport is normalized immediately after load.

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'Set Toolbar Position' },
    ],
    lines: [
    ]
};

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

This effect pair is the core runtime pattern: load once, then update toolbar direction and placement on the existing graph instance.

const updateGraphOptions = () => {
    graphInstance.updateOptions({
        toolBarDirection: toolBarDirection as 'v' | 'h',
        toolBarPositionH: toolBarPositionH as 'left' | 'center' | 'right',
        toolBarPositionV: toolBarPositionV as 'top' | 'center' | 'bottom'
    });
};

useEffect(() => {
    updateGraphOptions();
}, [toolBarDirection, toolBarPositionH, toolBarPositionV]);

This render fragment shows that the control surface lives outside the graph and that style switching happens by changing the wrapper class suffix.

<div className={`my-graph my-toolbar-style-${toolBarStyle}`} style={{ height: '100vh' }}>
    <DraggableWindow initialLeft={200} initialTop={150}>
        <div className="c-option-name">Toolbar Style:</div>
        <SimpleUISelect
            data={[
                { value: '', text: 'Default' },
                { value: '1', text: 'Style 1' },
                { value: '2', text: 'Style 2' },
                { value: '3', text: 'Style 3' }
            ]}
            currentValue={toolBarStyle}
            onChange={(newValue: string) => setToolBarStyle(newValue)}
        />

This SCSS fragment is the proof that theme changes target the built-in .rg-toolbar instead of replacing the toolbar with custom markup.

&.my-toolbar-style-1 .relation-graph {
    .rg-toolbar {
        background-color: rgba(203, 7, 7, 0.2);
        color: #d9001b;
    }
}

&.my-toolbar-style-2 .relation-graph {
    .rg-toolbar {
        background-color: rgba(61, 150, 2, 0.8);
        color: #ffffff;
    }
}

This shared helper code shows how image export is implemented through graph-instance APIs rather than through a custom canvas wrapper.

const downloadImage = async () => {
    const canvasDom = await graphInstance.prepareForImageGeneration();
    let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
    if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
        graphBackgroundColor = '#ffffff';
    }
    const imageBlob = await domToImageByModernScreenshot(canvasDom, {
        backgroundColor: graphBackgroundColor
    });
    if (imageBlob) {
        downloadBlob(imageBlob, 'my-image-name');
    }
    await graphInstance.restoreAfterImageGeneration();
};

What Makes This Example Distinct

The comparison record places this demo near layout-center, gee-thumbnail-diagram, diy-line-arrow, center-layout-options, and line-style2, but it teaches a different layer of customization. Against gee-thumbnail-diagram, it keeps the built-in toolbar visible and repositions that widget directly, instead of hiding the toolbar and mounting a separate RGMiniView overlay.

Against layout-center, center-layout-options, and diy-line-arrow, the runtime controls do not relayout the graph, change line markers, or batch-update nodes and edges. They only update toolBarDirection, toolBarPositionH, and toolBarPositionV on an already loaded graph. The comparison and rarity records also call out the nearly empty one-node canvas as part of the point: it removes graph-structure noise so toolbar behavior is easier to inspect.

Against line-style2, the wrapper classes target .rg-toolbar rather than checked-line selectors. That makes this example a stronger starting point when a project wants to keep relation-graph’s default toolbar, move it at runtime, and reskin it with scoped CSS instead of replacing it with custom slot content.

Where Else This Pattern Applies

  • White-label graph embeds that need brand-specific toolbar skins without rebuilding the toolbar.
  • Dashboards or admin tools where toolbar position must move to avoid overlapping other floating panels or legends.
  • Internal playgrounds used to validate interaction defaults such as wheel zoom, drag mode, and export behavior around a standard graph viewer.
  • Documentation or design-system pages that need a minimal dataset so teams can judge control chrome without layout noise.