Pattern-Switchable Tape Lines
This example replaces standard relation-graph edges with slot-rendered ribbon paths on a fixed tree layout. It combines a root CSS fill variable, reusable SVG pattern defs, click-preserving custom line rendering, shared canvas controls, and screenshot export so the same ribbons can switch between solid and animated fills at runtime.
Switchable Pattern-Filled Ribbon Lines on a Tree Graph
What This Example Builds
This example builds a small tree viewer in which every relation is rendered as a filled ribbon instead of a standard stroked edge. The canvas uses a dark grid background, magenta rectangular nodes, and a floating helper window that lets the user restyle all visible ribbons at runtime.
Users can switch the line fill between a plain translucent color and multiple SVG pattern presets, including animated stripes, waves, arrows, and a rainbow flow. The most important detail is that the example does not just decorate built-in lines. It replaces the visible edge surface with a custom slot-rendered SVG path while still preserving relation-graph’s normal line-click behavior.
How the Data Is Organized
The graph data is declared inline inside initializeGraph() as one RGJsonData object with rootId, a flat nodes array, and a flat lines array. The sample tree contains nine nodes and eight from/to links, all loaded in one call to setJsonData().
There is no explicit preprocessing step before the JSON is loaded. The custom geometry work happens later, after relation-graph provides runtime slot data. MyLineContent receives lineConfig.from, lineConfig.to, and the runtime line.data offset fields, and generateLinePath4Curve(...) uses those values to build a closed ribbon path between node boundaries.
In a real system, the same structure could represent subsystem dependencies, service relationships, product modules, or process stages. The static node labels can be replaced with business entities, while the runtime line geometry can support thicker route bands, channel-like dependencies, or branded relationship surfaces.
How relation-graph Is Used
index.tsx wraps the example in RGProvider, so the graph canvas and the floating utility window share one relation-graph context. MyGraph.tsx defines the base RelationGraph options: rectangular nodes, border junction points, default colors, and a tree layout with fixed horizontal and vertical gaps.
RGHooks.useGraphInstance() drives initialization. On mount, the example loads the inline dataset with setJsonData(), then recenters the graph and fits it into the viewport with moveToCenter() and zoomToFit(). The graph itself stays structurally static after that.
The core customization happens through RGSlotOnLine. Instead of leaving edge output to the built-in renderer, the slot passes each line into MyLineContent, which computes a closed SVG path with generateLinePath4Curve(...), applies CSS variables for fill and state, and forwards clicks back into graphInstance.onLineClick(...).
Runtime theming is split across React state, SVG defs, and SCSS. fillStyleId is stored in component state, then exposed as the root CSS variable --rg-line-fill-style. MySvgDefs injects reusable <pattern> and <linearGradient> definitions, and my-relation-graph.scss applies that fill to .rg-line, adds a magenta outline for checked lines, brightens hovered ribbons, and restyles the canvas and nodes.
The example also reuses a shared DraggableWindow utility. Its settings overlay reads current graph options through RGHooks.useGraphStore(), updates wheel and drag behavior with setOptions(...), and exports the canvas through prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration().
Key Interactions
The main interaction is the fill-style selector in the floating window. Choosing a different option updates fillStyleId, which immediately rewrites the root CSS fill reference that every custom ribbon path uses.
Clicking a ribbon still participates in relation-graph’s line interaction flow. MyLineContent stops DOM bubbling on the custom SVG path and forwards the native event to graphInstance.onLineClick(...), so checked-state feedback still works even though the visible edge is custom.
The floating window can be dragged, minimized, and switched into a settings overlay. Inside that overlay, the user can change wheel behavior, change canvas drag behavior, and download a screenshot of the current graph.
Key Code Fragments
This options block shows that the example keeps layout and default node settings inside relation-graph while preparing the graph for a fixed tree presentation.
defaultNodeShape: RGNodeShape.rect,
defaultJunctionPoint: RGJunctionPoint.border, // Use enums
defaultLineColor: '#2E74B5',
defaultNodeColor: '#ffffff',
defaultNodeBorderWidth: 2,
defaultNodeBorderColor: '#2E74B5',
// Layout is recommended to be configured in options for automatic setJsonData effect
layout: {
layoutName: 'tree', // Example layout, adjust as needed
treeNodeGapH: 200,
treeNodeGapV: 50
}
This initialization fragment proves that the data stays inline and that the graph is centered and fitted immediately after loading.
// 5. Set data
// setJsonData includes logic for addNodes, addLines and doLayout
await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();
This wrapper style is the runtime theming switch. One state value controls the fill source for all custom line surfaces.
<div
className="my-graph"
style={{
height: '100vh',
'--rg-line-fill-style': (fillStyleId ? `url(#${fillStyleId})` : 'rgba(255,255,255,0.47)')
}}
>
This render fragment shows where relation-graph hands each line to the custom slot renderer.
<RelationGraph options={graphOptions}>
{/* 6. Use RGSlotOnLine component to define line slots */}
<RGSlotOnLine>
{({ lineConfig, checked }: RGLineSlotProps) => (
<MyLineContent lineConfig={lineConfig} checked={checked} />
)}
</RGSlotOnLine>
</RelationGraph>
This custom-line fragment shows both important behaviors: forwarding clicks back to relation-graph and replacing the visible edge with a generated path.
const onLineClick = (e: React.MouseEvent | React.TouchEvent) => {
// 阻止冒泡,并将事件传递给 graphInstance
e.stopPropagation();
graphInstance.onLineClick(line, e.nativeEvent as RGUserEvent);
};
const pathId = graphInstanceId + '-' + line.id;
const path = generateLinePath4Curve(
lineConfig.from,
lineConfig.to,
line,
1
);
This helper code is what turns two node anchors into one closed ribbon body instead of a single stroke.
const toLeft = fx > tx;
const curve1 = toLeft ? createLinePathForCurve(fx + 1, fy, tx + tw - 1, ty, zoomRate, toLeft) : createLinePathForCurve(fx + fw - 1, fy, tx + 1, ty, zoomRate, toLeft);
const line1 = toLeft ? `L ${Math.round(tx + tw - 1)},${Math.round(ty + th)}` : `L ${Math.round(tx + 1)},${Math.round(ty + th)}`;
const curve2 = toLeft ? createLinePathForCurve(tx + tw, ty + th, fx + 1, fy + fh, zoomRate, !toLeft) : createLinePathForCurve(tx, ty + th, fx + fw - 1, fy + fh, zoomRate, !toLeft);
const path = `M ${curve1.substring(2)} ${line1} ${curve2.substring(2)} Z`;
return path;
This SVG defs fragment shows that some line skins are animated patterns rather than static colors.
<pattern id="pat-stripe" width="40" height="40" patternUnits="userSpaceOnUse" patternTransform="rotate(45)">
<rect width="40" height="40" fill="#fbbf24"/>
<rect width="20" height="40" fill="#000"/>
<animateTransform attributeName="patternTransform" type="translate" from="0 0" to="40 0" dur="1s" repeatCount="indefinite" additive="sum"/>
</pattern>
This settings fragment shows that viewer behavior changes are applied through graph-instance options, not by rebuilding the page.
<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 }); }}
/>
What Makes This Example Distinct
The comparison data places this example closest to adv-line-slot2, custom-line-style, custom-line-animation, line-style2, and ever-changing-tree, but its emphasis is narrower than all of them. Its distinctive lesson is not just that line appearance can change at runtime. It shows how to replace the visible relation surface with a slot-rendered closed ribbon path and then reskin that surface through one CSS fill variable plus reusable SVG defs.
Compared with adv-line-slot2, this example is the more fixed tree-layout reference. It keeps rectangular nodes, a dark grid canvas, and curve-based ribbon geometry, so the main lesson stays on switchable tape-like edges rather than centered topology presentation or node-shape mutation.
Compared with custom-line-style and custom-line-animation, it intervenes one layer deeper. Those examples restyle relation-graph’s native lines, while this one replaces the visible edge geometry itself through RGSlotOnLine.
Compared with line-style2 and ever-changing-tree, it is less about checked-line-only emphasis or parameter tuning and more about graph-wide ribbon fill switching on a stable viewer. The rare combination of a fixed tree layout, magenta rectangular nodes, dark grid background, custom ribbon paths, animated SVG textures, and shared export utilities makes it a focused reference for custom edge surfaces.
Where Else This Pattern Applies
This pattern transfers well to subsystem maps, dependency diagrams, route-band visualizations, and process trees where relationships need to feel like channels or tapes instead of thin connectors. It is especially useful when teams want one renderer that can switch among branded, status-driven, or animated fills without rebuilding graph data.
It also fits demo tools and white-label products where a design team wants multiple edge skins from one implementation. The same structure can be adapted for energy flow maps, network capacity views, manufacturing pipelines, or logistics corridors where the relationship surface itself carries visual meaning.