JavaScript is required

Node CSS Skin and Checked State

This example shows a small left-to-right tree that keeps relation-graph's built-in rectangular text nodes and refines them with graph options plus scoped SCSS. It is mainly a reference for default-node CSS skinning, readable single-line labels, restrained checked-state styling, and the shared floating settings and image-export utilities.

Default Tree Nodes with CSS Skinning and Checked State

What This Example Builds

This example builds a small left-to-right tree viewer that keeps relation-graph’s built-in rectangular nodes and restyles them with a minimal white presentation. The canvas fills the screen, links use restrained dark curves, node chrome is mostly removed, and the remaining visual identity comes from black single-line labels plus a custom checked-state fill.

Users mainly inspect a prepared hierarchy rather than edit it. A floating helper window can be dragged, minimized, opened into a settings panel, and used to export the current graph as an image. The main lesson is how far the default node renderer can be pushed with graph options and scoped SCSS before moving to slot-rendered node bodies.

How the Data Is Organized

The graph data is declared inline as one RGJsonData object with a rootId, a flat nodes array, and a flat lines array. The sample content uses generic ids and labels such as a, b1-4, and c3, which makes the structure intentionally neutral so the styling pattern is easier to isolate.

There is no preprocessing before setJsonData(...). The example sends the static dataset directly into relation-graph, then centers and fits it. In a production application, the same shape could represent organization levels, approval trees, product categories, decision branches, or any other lightweight hierarchy where the default text-node renderer is still sufficient.

How relation-graph Is Used

RGProvider wraps the page, and RGHooks.useGraphInstance() supplies the live graph instance. The local graphOptions configure a tree layout that grows from the left, widen node spacing for readability, switch the node shape to RGNodeShape.rect, keep the node fill and border transparent, route links with RGLineShape.StandardCurve, and place the built-in toolbar horizontally at the bottom-right edge.

The example does not define RGSlotOnNode or any custom node-body component. Instead, it keeps relation-graph on the default renderer and changes the result with options plus scoped SCSS. The stylesheet forces .rg-node-text onto one line, sets the text color to black, and overrides .rg-node-peel.rg-node-checked so checked nodes use a translucent gray fill instead of the default emphasis.

The shared helper window adds the rest of the runtime behavior. CanvasSettingsPanel reads the current graph options from RGHooks.useGraphStore(), calls graphInstance.setOptions(...) to switch wheel and drag behavior, and uses prepareForImageGeneration() together with restoreAfterImageGeneration() to capture the graph canvas as an image. Those controls are useful utilities, but the local example-specific technique is the default-node skinning approach.

Key Interactions

  • The graph loads once on mount, then calls moveToCenter() and zoomToFit() so the full tree is visible immediately.
  • Users inspect a fixed tree in viewer mode; the local source does not add structure editing, node-authoring tools, or custom click-driven state changes.
  • The floating helper window can be dragged around the page, minimized, and reopened into a settings overlay.
  • The settings overlay switches wheelEventAction between scroll, zoom, and none, and switches dragEventAction between selection, move, and none.
  • The helper can export the current canvas as an image through the shared screenshot pipeline.
  • The stylesheet defines a checked-node appearance, but the local example code does not add its own checked-state handler.

Key Code Fragments

This options block shows that the example stays on relation-graph’s default renderer and uses option choices to create a restrained tree-viewer baseline.

const graphOptions: RGOptions = {
    backgroundColor: '#ffffff',
    defaultLineColor: '#444',
    defaultNodeColor: 'transparent',
    defaultNodeBorderWidth: 0,
    defaultNodeBorderColor: 'transparent',
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardCurve,
    defaultJunctionPoint: RGJunctionPoint.lr,
    toolBarDirection: 'h',
    toolBarPositionH: 'right',
    toolBarPositionV: 'bottom',
    layout: {
        layoutName: 'tree',
        from: 'left',
        treeNodeGapH: 310,
        treeNodeGapV: 70,
    }
};

This fragment shows that the dataset is a direct inline tree with no preprocessing layer before it reaches the graph.

const myJsonData: RGJsonData = {
    rootId: 'a',
    nodes: [
        { id: 'a', text: 'a' },
        { id: 'b', text: 'b' },
        { id: 'b1', text: 'b1' },
        { id: 'b1-1', text: 'b1-1' }
        // ...
    ],
    lines: [
        { from: 'a', to: 'b', text: '' },
        { from: 'b', to: 'b1', text: '' },
        { from: 'b1', to: 'b1-1', text: '' }
        // ...
    ]
};

This initialization code proves that the example’s lifecycle is simple: load the prepared tree, center it, and fit it to the viewport.

const initializeGraph = async () => {
    await graphInstance.setJsonData(myJsonData);
    graphInstance.moveToCenter();
    graphInstance.zoomToFit();
};

useEffect(() => {
    initializeGraph();
}, []);

This SCSS fragment is the core styling technique: keep the default node markup, then refine text behavior and checked-state feedback through scoped selectors.

.relation-graph {
    --rg-checked-item-bg-color: rgba(63, 62, 62, 0.34);

    .rg-node {
        .rg-node-text {
            white-space: nowrap;
            color: #000000;
        }
    }

    .rg-node-peel.rg-node-checked {
        .rg-node {
            box-shadow: none;
            background-color: var(--rg-checked-item-bg-color);
        }
    }
}

This shared settings fragment shows how the helper window turns graph-instance APIs into runtime viewing controls.

<SettingRow
    label="Wheel Event:"
    options={[
        { label: 'Scroll', value: 'scroll' },
        { label: 'Zoom', value: 'zoom' },
        { label: 'None', value: 'none' },
    ]}
    value={wheelMode}
    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}
/>

This image-export routine shows the shared screenshot flow that prepares the graph DOM, captures it, downloads the blob, and restores the graph afterward.

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 data makes the strongest distinction clear: this example keeps the built-in rectangular text nodes and still reaches a custom-looking result mainly through options and SCSS. Compared with node-style4, it does not introduce RGSlotOnNode, icon templates, or a stronger branded theme. Compared with node-style3, it stays on an ordered tree layout with ordinary text labels instead of switching to a force layout and metadata-driven icon nodes.

That narrower choice gives it a specific retrieval value. It is a better starting point when a team wants low-friction CSS skinning on relation-graph’s default node renderer, especially for readable non-wrapping labels and restrained checked-state feedback. The rarity data also supports the combination of a white utility shell, wide left-to-right tree spacing, transparent node chrome, dark curved links, and a translucent checked fill as an uncommon mix in the example set.

The shared helper window, runtime interaction switches, and image export are part of the experience, but they are not the unique claim here because nearby examples reuse the same scaffolding. The more defensible distinction is the emphasis on typography and checked-state refinement without leaving the default renderer.

Where Else This Pattern Applies

This pattern transfers well to internal hierarchy viewers, approval or responsibility trees, product-category explorers, and rule or decision trees where the graph needs to look more polished than the default theme but does not justify a custom node component system.

It is also a useful starting point for teams that need selection feedback and label control without adding per-node templates. If the domain data is already close to id, text, and from/to relationships, the same approach can deliver a readable branded tree with limited implementation cost.