Node Styling Techniques
This example is a compact node-styling reference built on one static relation-graph scene. It compares per-node JSON style overrides, custom node slots, CSS-driven animation, fixed positioning, a mini view, and shared canvas utilities such as interaction-mode switching and image export.
Comparing Node Styling Techniques in One Graph
What This Example Builds
This example builds a compact relation-graph scene whose main purpose is to compare several node presentation techniques in one canvas. The viewer sees circular and rectangular nodes, nodes with explicit size overrides, fit-content nodes, one fixed-position node, a logo-backed custom node, and a CSS-animated node.
The graph is presented as a read-only styling reference rather than a domain workflow. Users can inspect the prepared variants, open the floating settings panel, switch wheel and drag behavior, drag or minimize the helper window, export the graph as an image, and use the mini view for navigation. Node and line clicks are wired, but in this example they only log the selected objects.
How the Data Is Organized
The graph data is assembled inline as one RGJsonData object inside initializeGraph(). It sets rootId: 'a', defines eleven nodes, and connects them with eleven lines before the payload is loaded through setJsonData().
The important point is that the dataset already carries many of the visual differences. Individual node records override fields such as width, height, borderWidth, borderColor, color, fontColor, nodeShape, fixed, x, y, type, and className. That makes the data layer a direct vehicle for style comparison before any slot logic runs.
In a real product, the same pattern could encode business meaning instead of demo labels. The per-node fields could represent emphasis levels, category colors, avatar or logo placeholders, pinned reference nodes, compact summary cards, or alert states that need animation.
How relation-graph Is Used
The example uses the built-in center layout and sets graph-wide defaults for circular nodes, curved lines, border junction points, and an 80 x 80 default node size. Those defaults establish a common baseline, while the JSON data overrides only the nodes that need a different treatment.
RGHooks.useGraphInstance() drives the graph lifecycle. On mount, the component loads the inline data with setJsonData(), then normalizes the viewport with moveToCenter() and zoomToFit(). The same hook is reused inside the shared settings panel so the demo can update runtime options and prepare the canvas for image export.
Customization is split across slots, shared subcomponents, and CSS. RGSlotOnNode mounts a NodeSlot renderer that switches on node.id and node.type to produce special node bodies. RGSlotOnView adds RGMiniView as an overlay. RGProvider wraps the whole example so hooks can access the active graph instance. The floating DraggableWindow contributes the description shell and hosts CanvasSettingsPanel, which reads the current store state and changes wheelEventAction or dragEventAction through setOptions(). The animated node variant comes from the imported simple-node-animations.scss stylesheet, while the image-backed and plain-text variants use inline styles inside the node slot.
Key Interactions
- The graph loads once on mount and immediately centers and fits the prepared dataset, so the full style gallery is visible without manual setup.
- Clicking a node or line triggers handlers, but those handlers only log the selected object. They are diagnostic hooks, not feature entry points.
- The floating helper window can be dragged from its header and toggled between expanded and minimized states.
- Opening the settings overlay exposes graph-level controls for wheel behavior (
scroll,zoom, ornone) and canvas drag behavior (selection,move, ornone). - The
Download Imageaction prepares the graph canvas, captures it as a blob, triggers a download, and then restores the graph state. RGMiniViewgives the example a minimap overlay, which is useful because the scene mixes differently sized nodes and includes one pinned node with explicit coordinates.
Key Code Fragments
This fragment shows that the baseline graph configuration is intentionally simple, so node-level overrides are easy to compare.
const graphOptions: RGOptions = {
layout: {
layoutName: 'center'
},
defaultNodeShape: RGNodeShape.circle,
defaultLineShape: RGLineShape.StandardCurve,
defaultJunctionPoint: RGJunctionPoint.border,
defaultNodeWidth: 80,
defaultNodeHeight: 80,
};
This fragment shows how the example encodes visual differences directly in the JSON payload before the graph renders.
const myJsonData: RGJsonData = {
rootId: 'a',
nodes: [
{ id: 'a', text: 'Border color', width: 150, height: 150, borderColor: '#f43ce5', borderWidth: 3 },
{ id: 'a1-1', text: 'Plain Text Node', borderWidth: 0, width: 0, height: 0, nodeShape: RGNodeShape.rect },
{ id: 'e', text: 'Node width/height fit-content ', width: 0, height: 0, nodeShape: RGNodeShape.rect },
{ id: 'f1', text: 'Fixed', fixed: true, x: -660, y: -160 },
{ id: 'g', type: 'my-animation', text: 'Css Animation', width: 0, height: 0, className: 'my-node-flash-style' }
],
// ...
};
This fragment shows the mount-time graph lifecycle: load the prepared data, then normalize the viewport.
const initializeGraph = async () => {
// ...
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
};
useEffect(() => {
initializeGraph();
}, []);
This fragment shows that RGSlotOnNode is not used for one uniform skin. It branches to render several unrelated node-body variants in the same graph.
const NodeSlot: React.FC<RGNodeSlotProps> = ({ node }) => {
if (node.id === 'a1-1') {
return <div style={{ color: '#ff8c00', display: 'flex', alignItems: 'center', justifyContent: 'center', width: '100%', height: '100%' }}>{node.text}</div>;
}
if (node.id === 'a1-4') {
return <div style={{ border: '#ff8c00 solid 6px', height: '100%', width: '100%', borderRadius: '40px', backgroundImage: `url(${rgLogoUrl})`, backgroundPosition: 'center center', backgroundColor: '#fff' }} />;
}
if (node.type === 'my-animation') {
return <div className="my-node-animation-01 h-32 w-32 rounded-full relative text-lg flex place-items-center justify-center overflow-hidden">{/* ... */}</div>;
}
return <div style={{ padding: '5px 10px', height: '100%', width: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', color: node.fontColor || 'inherit' }}>{node.text}</div>;
};
This fragment shows that the animated node appearance comes from a reusable CSS class rather than from repeated inline style declarations.
.my-node-animation-01 {
background: linear-gradient(
to bottom,
#00ffff,
#ff00ff,
#00ffff
);
background-size: 100% 200%;
animation: gradient-flow 6s ease-in-out infinite;
}
This fragment shows how the shared settings panel turns the example into a viewer utility shell with runtime interaction toggles and image export.
<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 }); }}
/>
<SettingRow
label="Canvas Drag Event:"
options={[
{ label: 'Selection', value: 'selection' },
{ label: 'Move', value: 'move' },
{ label: 'none', value: 'none' },
]}
value={dragMode}
onChange={(newValue: string) => { graphInstance.setOptions({ dragEventAction: newValue }); }}
/>
<SimpleUIButton onClick={downloadImage}>
Download Image
</SimpleUIButton>
What Makes This Example Distinct
Compared with nearby examples such as node-style3 and node-style4, this example is broader and less theme-driven. Those examples lean toward one coherent custom slot skin, while node uses one canvas to compare several styling paths side by side: per-node JSON overrides, targeted slot-rendered exceptions, fit-content rectangles, an image-backed node, a CSS-animated node, and a fixed-position node.
Compared with line, the overall teaching pattern is similar, but the comparison target is different. line uses the same static gallery approach for edge geometry and markers, while node redirects it toward node appearance, sizing, shape, text styling, and custom body rendering.
Compared with layout-tree, the shared viewer shell is not the main lesson. This example does include the floating helper window and RGMiniView, but the comparison data shows that its distinctive value is fixed style comparison on a stable layout, not runtime relayout or orientation switching.
The most unusual combination is that it stays on a normal viewer path while mixing many node-style techniques in one small graph. It does not present itself as a full editor, and it does not rely only on slots. The visible result comes from the combination of built-in per-node visual fields, slot-level exceptions, CSS animation, and lightweight viewer utilities.
Where Else This Pattern Applies
This pattern transfers well to design-system documentation for graph UIs. A team can use one prepared graph to compare status badges, severity colors, pinned nodes, avatar nodes, media-backed nodes, and animated warning states before those styles are applied in a production workflow.
It also fits internal tooling where stakeholders need to validate node treatments without editing graph structure. Examples include knowledge-graph theming reviews, network monitoring dashboards, dependency maps, and organization charts where the main question is how different node categories should look rather than how the layout should behave.
The same structure is also useful as a regression board. Because the graph loads fixed sample nodes with known size, shape, and slot variants, it can act as a compact visual test surface whenever a team changes relation-graph theme rules, slot renderers, or export behavior.