JavaScript is required

Custom Node Enterprise Service Promo Card

This example uses a fixed-layout `RelationGraph` as a presentation canvas for one oversized enterprise promo card rendered through `RGSlotOnNode`. It also reuses the shared floating helper window for runtime wheel and drag mode switching plus image export, making it a focused reference for graph-hosted branded UI rather than layout variation.

Use relation-graph as a Fixed Promo Card Canvas

What This Example Builds

This example builds a full-screen RelationGraph view that behaves more like a presentation board than a relationship map. The shipped data contains one root node and no lines, and that root node is replaced with a 1200 by 600 enterprise-membership card that includes branded copy, service bullets, outbound links, and a secondary 3D inset card.

Users can drag or minimize the floating helper window, open a settings overlay, switch wheel and canvas-drag behavior, and export the current canvas as an image. The main thing worth studying is not layout variety, but the way a fixed-layout graph is used as a host surface for one oversized custom React composition.

How the Data Is Organized

The graph data is declared inline inside initializeGraph() as a small RGJsonData object with rootId: 'root', one node, and an empty lines array. There is no fetch step, no external transformation layer, and no derived positioning pass before the graph is rendered.

The only real preprocessing step is sequencing: the code waits for setJsonData() to finish, then calls moveToCenter() and zoomToFit() so the viewport is adjusted after relation-graph has measured the rendered node. In a production system, the same structure could represent a landing-card-style product panel, a branded portal entry point, a dashboard hero tile, or any other single focal card that still benefits from graph viewport controls and export utilities.

How relation-graph Is Used

index.tsx wraps the page with RGProvider, which lets both the graph component and the floating helper window use relation-graph hooks from the same graph context. Inside MyGraph.tsx, RGHooks.useGraphInstance() is the main integration point: it loads the inline data, centers the view, and fits the viewport after mount.

The graph options keep the canvas in layoutName: 'fixed' and use rectangular nodes, orthogonal default lines, top-bottom junction points, zero node borders, and neutral fallback colors. That fixed layout is important because the example is not asking relation-graph to compute a network arrangement. It is using the graph as a stable canvas for authored content.

The main rendering customization is RGSlotOnNode. When the slot sees the root node, it renders RootNodeContent; otherwise it falls back to a simple bordered text block. There are no active canvas or view slots in the shipped render path, even though some related symbols are imported.

Shared viewer utilities come from DraggableWindow and its CanvasSettingsPanel. That helper reads current interaction modes from RGHooks.useGraphStore(), updates them at runtime through graphInstance.setOptions(...), and exports the graph through prepareForImageGeneration(), getOptions(), restoreAfterImageGeneration(), and the shared modern-screenshot wrapper. Styling is split between the example SCSS, which rethemes graph internals such as line labels and the logo fill, and the reusable Simple3DCard styles, which add hover tilt, glow, and hologram-grid effects to the inset tile.

Key Interactions

  • The floating helper window can be dragged by its title bar.
  • The helper window can be minimized and expanded.
  • The settings button opens an overlay panel inside the helper window, and clicking the translucent backdrop closes it.
  • The settings panel switches wheel behavior between scroll, zoom, and none.
  • The settings panel switches canvas drag behavior between selection, move, and none.
  • The Download Image action prepares the graph DOM, captures the current view, downloads it, and restores the graph state.
  • The custom root card contains outbound links to https://relation-graph.com/services.
  • Node and line click handlers are wired, but in the shipped dataset they only log objects, and there are no lines to interact with.

Key Code Fragments

This fragment shows that the example intentionally keeps relation-graph in fixed mode and uses it as a manually composed surface.

const graphOptions: RGOptions = {
    debug: false,
    layout: {
        layoutName: 'fixed'
    },
    defaultNodeShape: RGNodeShape.rect,
    defaultLineShape: RGLineShape.StandardOrthogonal,
    defaultJunctionPoint: RGJunctionPoint.tb,
    defaultNodeBorderWidth: 0,
    defaultLineColor: '#666',
    defaultNodeColor: '#fff'
};

This block proves that the dataset is built locally and loaded before the viewport is centered and fit.

const initializeGraph = async () => {
    const myJsonData: RGJsonData = {
        rootId: 'root',
        nodes: [
            { id: 'root', text: 'Node' }
        ],
        lines: []
    };

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

This slot renderer is the core technique: replace the root node with a full custom React layout while keeping a fallback renderer for other nodes.

<RGSlotOnNode>
    {({ node }) => {
        return (
            node.id === 'root' ? (
                <RootNodeContent />
            ) : (
                <div className="px-2 min-w-[100px] rounded border border-gray-500 flex items-center justify-center w-full h-full text-sm text-slate-800 font-medium select-none">
                    {node.text}
                </div>
            )
        );
    }}
</RGSlotOnNode>

This excerpt shows that the helper overlay changes relation-graph interaction modes on the live instance instead of rebuilding graph data.

<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 export flow shows how the example prepares relation-graph for capture, hands the canvas DOM to the screenshot helper, 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

According to the comparison data, this example is unusual because it keeps the graph topology extremely small while concentrating nearly all of the complexity inside one slot-rendered root node. The distinctive combination is fixed-layout initialization, mount-time setJsonData() plus center-and-fit, full-node replacement with RGSlotOnNode, embedded outbound links, the reusable Simple3DCard, and the shared floating settings and export shell on the same screen.

Compared with table-relationship, the custom HTML here is not structural and does not serve row-level endpoints or visible data links. Compared with css-theme, the emphasis is not on switching graph skins at runtime, but on one precomposed branded card. Compared with use-dagre-layout-2, the fixed layout is only a container; there is no second-pass layout algorithm, spacing control, or minimap-driven workflow.

The comparison record also makes an important negative point: this should not be described as a real multi-layout demo. The shipped implementation uses one root node under layoutName: 'fixed', and the durable lesson is graph-as-presentation-canvas composition rather than layout variation.

Where Else This Pattern Applies

This pattern transfers well to cases where a graph canvas needs to host one dominant piece of custom interface instead of a dense relationship map. Examples include enterprise upgrade panels, product capability overviews, launch or campaign cards, onboarding checkpoints, and dashboard hero modules that still benefit from graph panning, zoom behavior, and image export.

It is also useful when teams want to introduce graph infrastructure early, even before a richer topology exists. A single-card fixed-layout example like this can serve as a branded entry state, a premium-feature explainer, or a portal tile today, then grow into a more connected graph experience later without changing the underlying viewer shell.