Node and Line Click Highlighting
This example builds a read-only people relationship map where node clicks highlight connected lines, line clicks isolate the selected relation's endpoints, and canvas clicks restore the baseline styles. It is a focused reference for reversible interaction-driven graph styling with portrait node slots, saved original styles, and a force layout that settles before inspection begins.
Node and Line Click Highlighting on a People Relationship Map
What This Example Builds
This example builds a read-only people relationship map on a full-height canvas. Each person appears as a circular portrait with a color-coded name label, and the graph starts in a force layout before settling into a stable inspection view.
The main behavior is click-driven emphasis. Clicking a node highlights every connected relation in red. Clicking a line highlights that line, dims unrelated nodes, and keeps only the two endpoint nodes fully visible. Clicking the empty canvas restores the original styling. The most useful part of the demo is that these three states share one reversible reset pipeline instead of rebuilding the graph.
How the Data Is Organized
The data comes from a local jsonData object in mock-data-api.ts, exposed through an async fetchJsonData() wrapper that resolves after 200 ms. The payload uses standard RGJsonData structure with rootId, nodes, and lines. In this example, the dataset contains 21 nodes and 42 lines. Nodes store id, text, color, borderColor, and a data object with portrait URLs and person attributes. Lines store from, to, text, color, fontColor, and a data.type value such as friend, couple, relative, or collusion.
There is important preprocessing before setJsonData(). MyGraph.tsx copies each node’s original color and border-width fallback into node.data, copies each line’s original color and width into line.data, and generates synthetic line-${index} ids for lines that do not already have an id. That preparation is what makes later reset and re-highlight operations reliable. In a real application, the same structure could represent investigation subjects, account relationships, supplier dependencies, or any entity graph where both nodes and links need reversible emphasis states.
How relation-graph Is Used
index.tsx wraps the demo in RGProvider, and MyGraph.tsx retrieves the active instance through RGHooks.useGraphInstance(). The example uses that instance for the full lifecycle: loading data with setJsonData(), stopping the force layout with stopAutoLayout(), fitting the final view with zoomToFitWithAnimation(), querying current graph items with getNodes() and getLines(), looking up node neighbors with getRelatedLinesByNode(), resolving a clicked line back to its endpoints with getLinkByLine(), and applying transient styling through updateNode(), updateLine(), and updateLineData().
The graph options define a compact viewer configuration rather than an editor. Nodes default to circular shapes with a border width of 2, lines default to RGLineShape.StandardStraight, and edge anchors use RGJunctionPoint.border. The layout is force, but the graph does not stay in continuous motion: after the data loads, the example waits one second, stops auto layout, and then runs an animated fit-to-view. That makes the graph feel like a stable relationship viewer instead of an always-moving simulation.
The main visual override is RGSlotOnNode. Instead of the default node body, the slot renders a portrait tile using node.data.icon as a background image and places the node text below it, colored with the node’s current color. The SCSS file mainly supports that custom node presentation by sizing the avatar, rounding it into a circle, and styling the caption. Most of the interactive styling does not come from CSS classes; it comes from relation-graph instance updates at runtime.
Key Interactions
- Clicking a node resets prior emphasis and then highlights every connected line in red with a thicker stroke.
- Clicking a line resets prior emphasis, dims all nodes to
0.2opacity, highlights the selected line in red, and restores full opacity only for the two endpoint nodes. - Clicking the canvas runs the shared reset routine and clears the graph’s checked state.
- The force layout is allowed to settle briefly and is then frozen, so later interaction happens on a stable view rather than a moving one.
Key Code Fragments
This fragment shows the preprocessing step that saves baseline node and line styles before the graph is loaded.
const myJsonData: RGJsonData = await fetchJsonData();
myJsonData.nodes.forEach(n => {
if (!n.data) n.data = {};
n.data.orignColor = n.color;
n.data.originBorderWidth = n.originBorderWidth || 3;
});
myJsonData.lines.forEach(l => {
if (!l.data) l.data = {};
l.data.orignColor = l.color;
l.data.orignLineWidth = l.lineWidth || 2;
});
This block proves that missing line ids are synthesized before loading and that the force layout is explicitly frozen after the first second.
myJsonData.lines.forEach((line, index) => {
if (!line.id) line.id = `line-${index}`;
});
await graphInstance.setJsonData(myJsonData);
setTimeout(async() => {
graphInstance.stopAutoLayout();
await graphInstance.zoomToFitWithAnimation();
}, 1000);
This shared reset routine is the core of the reversible emphasis pattern: it restores node opacity, node color, border width, line color, label color, and line width from saved values.
const resetNodeAndLineStyle = () => {
graphInstance.getNodes().forEach(node => {
const originColor = node.data?.orignColor || '#cccccc';
const originBorderWidth = node.data?.originBorderWidth || 2;
graphInstance.updateNode(node.id, {
opacity: 1,
color: originColor,
borderWidth: originBorderWidth,
});
});
graphInstance.getLines().forEach(line => {
const originColor = line.data?.orignColor || '#cccccc';
const originWidth = line.data?.orignLineWidth || 2;
graphInstance.updateLine(line.id, {
color: originColor,
fontColor: originColor,
lineWidth: originWidth
});
});
};
This node-click handler shows the neighbor-aware lookup that turns one clicked person into a highlighted set of related links.
const onNodeClick = (nodeObject: RGNode, $event: RGUserEvent) => {
resetNodeAndLineStyle();
const relatedLines = graphInstance.getRelatedLinesByNode(nodeObject);
relatedLines.forEach(line => {
if (!line.data?.orignColor) {
graphInstance.updateLineData(line.id, {
orignColor: line.color,
orignLineWidth: line.lineWidth
});
}
graphInstance.updateLine(line.id, {
color: '#ff0000',
fontColor: '#ff0000',
lineWidth: 3
});
});
};
This line-click flow demonstrates the focus-plus-context behavior by dimming all nodes first and then restoring visibility only for the clicked line’s endpoints.
const onLineClick = (line: RGLine, linkObject: RGLink, $event: RGUserEvent) => {
resetNodeAndLineStyle();
graphInstance.getNodes().forEach(node => {
graphInstance.updateNode(node, {
opacity: 0.2
});
});
graphInstance.updateLine(line, {
color: '#ff0000',
fontColor: '#ff0000',
lineWidth: 3
});
const link = graphInstance.getLinkByLine(line);
This fragment completes the line-click behavior by restoring full opacity only for the two linked endpoint nodes.
if (link) {
[link.fromNode, link.toNode].forEach(node => {
graphInstance.updateNode(node, {
opacity: 1
});
});
}
This slot fragment proves that the example replaces the default node body with portrait-backed content and a caption whose color tracks the node state.
<RGSlotOnNode>
{({ node }: RGNodeSlotProps) => (
<div className="h-12 w-12">
<div
className="my-node-style"
style={{ backgroundImage: `url(${node.data?.icon})` }}
/>
<div className="c-node-name" style={{ color: node.color }}>
{node.text}
</div>
</div>
)}
</RGSlotOnNode>
What Makes This Example Distinct
The comparison data makes this example stand out as a focused reference for reversible click-driven styling. Its clearest distinctive trait is that it combines getRelatedLinesByNode(...) and getLinkByLine(...) in the same canvas, so node clicks and line clicks produce two different inspection behaviors without changing datasets or modes.
Compared with line-hightlight-pro and line-hightlight, this example teaches both node-initiated and line-initiated emphasis instead of concentrating on one trigger. Compared with scene-relationship and search-and-focus, it puts much more weight on in-canvas reversible styling than on external filters, search panels, or toolbar-led exploration. The comparison data also highlights a less obvious detail: this demo snapshots baseline node and line styles and generates line ids up front, which makes repeated highlight-reset cycles safe without reloading the graph.
Within avatar-based people relationship viewers, it also leans unusually hard on red relation emphasis plus canvas-reset restoration. That makes it a better starting point for developers who need temporary inspection states on a stable graph view, not a broader control panel or a full investigation dashboard.
Where Else This Pattern Applies
This pattern transfers well to investigation graphs, fraud or risk maps, dependency viewers, incident correlation diagrams, and partner or supplier networks where the user needs to inspect one entity’s neighborhood and then quickly reset the canvas. It is especially useful when node clicks and line clicks need different meanings, such as “show everything connected to this entity” versus “focus on this exact relationship.”
The same structure also works in read-only organization charts or account-network viewers where the layout should settle once and then behave as a stable inspection surface. The demo uses portrait-based people data, but the interaction pattern is generic: preserve baseline styles, apply temporary emphasis through instance APIs, and keep reset behavior explicit.