Graph CSS Theme Switcher
This example keeps one left-to-right relation-graph tree and switches it among eight CSS skins at runtime. It combines wrapper-class theming, icon-slot nodes, a themed `RGMiniView`, and a `getNodes()` plus `updateNode(...)` pass so the minimap stays synchronized with the active accent. A shared floating panel also exposes wheel mode, drag mode, and image export utilities.
Switching Eight CSS Themes on One Relation Graph Tree
What This Example Builds
This example renders a left-to-right hierarchy of circular icon nodes and lets the user switch the same graph among eight visual themes at runtime. The graph data, layout, and RelationGraph instance stay in place while the wrapper class changes from my-theme-1 to my-theme-8, so the map background, node surfaces, line colors, checked states, toolbar chrome, and minimap all change together.
The page is a full-height viewer with a floating utility window above the canvas. Users can drag that window, minimize it, open a settings overlay, choose a theme swatch, change wheel and drag behavior, and export the current graph as an image.
The main point is not custom layout logic. It is a graph-wide CSS skinning pattern that also includes one important runtime update step so RGMiniView stays color-synchronized with the active theme.
How the Data Is Organized
The graph data is declared inline inside initializeGraph() as one RGJsonData object. It uses a single rootId, a flat nodes array, and a flat lines array. Each node stores its icon key in node.data.icon, and several line records carry their own rendering overrides such as useTextOnPath, lineShape, fromJunctionPoint, and toJunctionPoint.
There is no preprocessing step before layout. The code sends the JSON directly to graphInstance.setJsonData(), then recenters and fits the viewport with moveToCenter() and zoomToFit().
In a real product, the same structure could represent a capability tree, service hierarchy, product taxonomy, site map, or workflow summary. The data.icon field can map to category or status icons, while per-line overrides can highlight special relationship types without changing the overall layout strategy.
How relation-graph Is Used
index.tsx wraps the demo in RGProvider, so the graph component and the shared helper window both resolve the same relation-graph context. Inside MyGraph.tsx, RelationGraph is rendered once with a built-in tree layout that grows from left to right. The layout spacing is intentionally large, with treeNodeGapH: 310 and treeNodeGapV: 70, so the icon nodes and themed line labels have room to breathe.
The graph options are set up to hand visual control to CSS. defaultLineColor, defaultNodeColor, and defaultNodeBorderColor are all set to auto, while defaultNodeShape is a circle and the default connectors use RGLineShape.StandardCurve with RGJunctionPoint.lr. That means the renderer still handles graph geometry, but the visible palette comes from the theme-specific SCSS rules.
The example uses RGHooks.useGraphInstance() in two places. In MyGraph, it loads the JSON, recenters the graph, fits the zoom, reads the live nodes with getNodes(), and applies updateNode(...) after each theme switch. In CanvasSettingsPanel, it updates runtime options with setOptions(...) and runs the image export flow with prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration(). RGHooks.useGraphStore() is used there as well so the settings panel reflects the current wheel and drag modes.
Two slots customize the viewer. RGSlotOnNode replaces the default node body with a large Lucide icon chosen from node.data.icon, and RGSlotOnView mounts RGMiniView above the canvas. The active styling comes from my-relation-graph.scss, where each .my-theme-* wrapper class targets built-in relation-graph surfaces such as .rg-map, .rg-node, .rg-line, .rg-toolbar, and .rg-miniview.
This is still a viewer-oriented example. It does not add editing tools for nodes or lines; the runtime APIs are used for loading, skin switching, canvas behavior, and export.
Key Interactions
The central interaction is theme switching. Clicking a swatch in ThemeSelect changes themeId, which swaps the wrapper class and also updates every node’s color property so the minimap picks up the same accent color as the main graph.
The floating utility window can be dragged to a new position and minimized to save space. That keeps the graph canvas clear while leaving the theme picker available.
The settings button opens the shared CanvasSettingsPanel. From there, users can switch the mouse wheel between scroll, zoom, and none; switch canvas dragging between selection, move, and none; and download the currently themed graph as an image.
The SCSS also defines checked-state styles for nodes and lines. The example does not add custom selection handlers, but when relation-graph applies its checked classes, those visuals inherit the active theme.
Key Code Fragments
This options block shows that the example keeps layout and geometry inside relation-graph while leaving visible color decisions to CSS.
const graphOptions: RGOptions = {
debug: false,
defaultLineColor: 'auto',
defaultNodeColor: 'auto',
defaultNodeBorderColor: 'auto',
defaultNodeShape: RGNodeShape.circle,
defaultPolyLineRadius: 10,
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.lr,
layout: {
layoutName: 'tree',
from: 'left',
treeNodeGapH: 310,
treeNodeGapV: 70
}
};
This data fragment shows that the demo uses one inline tree dataset, stores icon keys on nodes, and allows individual lines to override the default connector style.
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'a', data: { icon: 'align_bottom' } },
{ id: 'b', text: 'b', data: { icon: 'basketball' } },
// ...
],
lines: [
{ from: 'b', to: 'b1', text: 'useTextOnPath: true', useTextOnPath: true },
{
from: 'c', to: 'c1', text: 'lineShape:RGLineShape.StandardStraight',
lineShape: RGLineShape.StandardStraight, fromJunctionPoint: RGJunctionPoint.border, toJunctionPoint: RGJunctionPoint.border
}
]
};
This theme-update function proves that the minimap synchronization is not CSS-only; it explicitly rewrites node colors through the graph instance.
const setTheme = (id: string, mainColor: string) => {
setThemeId(id);
const nodes = graphInstance.getNodes();
nodes.forEach(node => {
graphInstance.updateNode(node, {
color: mainColor
});
});
};
This render block shows the two live extension points: a wrapper class that drives theme selection and slots that replace node content and add a minimap.
<div className={`my-theme-${themeId}`} style={{ height: '100vh', width: '100%', position: 'relative' }}>
<DraggableWindow>
<div className="c-theme-desc">Line style when selected</div>
<div className="c-option-name">Switch Theme:</div>
<ThemeSelect themeId={themeId} setTheme={setTheme} />
</DraggableWindow>
<RelationGraph options={graphOptions}>
<RGSlotOnNode>{/* icon slot */}</RGSlotOnNode>
<RGSlotOnView><RGMiniView /></RGSlotOnView>
</RelationGraph>
</div>
This SCSS fragment shows that one theme wrapper reaches into built-in relation-graph surfaces instead of replacing the renderer.
.my-theme-1 {
.relation-graph {
.rg-map {
background-color: rgba(91, 5, 241, 0.1);
}
.rg-node-peel {
.rg-node {
background-color: rgba(91, 5, 241, 0.61);
border: 2px solid rgba(91, 5, 241, 0.3);
color: #ffffff;
}
}
}
}
This export helper shows how the shared settings panel captures the currently themed graph without changing the graph data.
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 places this example near canvas-bg2, custom-line-style, node-style3, use-dagre-layout, and generate-image-with-background, but its emphasis is broader than any one of those neighbors. Its clearest distinguishing feature is that it keeps one left-to-right relation-graph tree and switches it among eight CSS skins without changing the data, layout, or graph instance.
Compared with canvas-bg2, this example goes further as a graph-theme gallery. It keeps the same wrapper-class idea, but adds more theme variants, circular icon-slot nodes, a themed minimap, and an explicit getNodes() plus updateNode(...) pass so the minimap does not fall out of sync with the selected accent color.
Compared with custom-line-style, the reusable lesson here is whole-surface theme coordination rather than line-family design. Nodes, lines, toolbar chrome, checked states, and minimap surfaces all change together under one theme selection.
Compared with node-style3, the custom node slot is supporting infrastructure rather than the main subject. Compared with use-dagre-layout and generate-image-with-background, the floating helper shell, runtime mode switches, and image export are available but secondary; the durable lesson is branded runtime skinning of a live graph viewer.
Where Else This Pattern Applies
This pattern fits white-label or multi-tenant products where one graph implementation needs different branded skins for different customers or workspaces. It also fits internal dashboards that need multiple presentation modes, such as operations views, environment-specific consoles, or light and dark display presets.
It is also useful for presentation-oriented graph viewers where auxiliary surfaces must stay visually aligned with the main canvas. Examples include demo environments, executive dashboards, kiosk displays, and overview screens where the minimap, toolbar, and checked states need to inherit the same theme as the graph itself.