Class Members and Interest Group Relations
This example builds a school activity relationship board inside `RGSlotOnCanvas` instead of rendering ordinary graph nodes. It connects group cards, member cards, and map pins through `RGConnectTarget` ids, then regenerates fake lines from the current selection while a shared floating panel controls canvas behavior and image export.
Bidirectional Interest Group Exploration With Canvas-Slot DOM Anchors
What This Example Builds
This example builds a school activity relationship board instead of a conventional node-link diagram. A semi-transparent school map, clickable location pins, a left column of interest groups, and a right column of class members are all rendered inside one relation-graph canvas.
Users can click a group card or its map pin to highlight matching members and draw a thicker animated line back to the group location. They can also click a member card to redraw the connector layer from that member to every joined group. The main point is that relation-graph is acting as a connection engine for custom HTML surfaces rather than as the visible node renderer.
How the Data Is Organized
The data lives in two inline arrays loaded into React state by loadDataFromRemote(). interestGroups stores a groupId, a display name, and { x, y } coordinates for the map pin, while classMembers stores a member name plus a joinedGroups array of group ids.
There is no real remote fetch or separate normalization step in this example. Instead, the important preprocessing happens when UI state is translated into fake-line definitions: activeGroupId and activeMemberName determine whether updateMyLines() builds member-to-group lines plus one group-to-location line, or member-to-multiple-group lines. In a production system, the same shape could represent students and clubs, employees and teams, volunteers and activities, or any other two-sided membership table with optional spatial anchors.
How relation-graph Is Used
index.tsx wraps the demo in RGProvider, and MyGraph.tsx gets the live graph instance through RGHooks.useGraphInstance(). The graph itself runs with debug: false, defaultJunctionPoint: RGJunctionPoint.lr, and layout.layoutName = 'fixed', which fits a scene made of absolutely positioned HTML rather than auto-laid-out graph nodes.
The actual interface is rendered through RGSlotOnCanvas. Inside that slot, the example places the background map, map pins, the group list, and the member list. Every interactive HTML surface is wrapped with RGConnectTarget and assigned a stable target id in one of three namespaces: location-*, group-*, or member-*. Those ids are then reused by addFakeLines() when the active selection changes.
The graph instance APIs are used as a rendering lifecycle tool. On startup, the demo loads inline data, preselects the music group, then calls moveToCenter() and zoomToFit(). On every selection change it waits with sleep(100), clears prior graph content, adds an invisible fake-root node, and redraws fake lines. The shared floating window uses RGHooks.useGraphStore() plus setOptions(), prepareForImageGeneration(), getOptions(), and restoreAfterImageGeneration() to change wheel or drag behavior at runtime and export the current canvas as an image. Visual customization comes from my-relation-graph.scss, which gives groups purple cards, members blue cards, and map pins a pulsing halo.
Key Interactions
- Clicking a group card or a map pin selects that group, clears any active member, highlights the matching members through regenerated blue curves, and adds an animated purple curve from the group card to its map location.
- Clicking a member card selects that member, clears any active group, and redraws purple curves from that member to every joined group.
- The floating description window can be dragged, minimized, and switched into a settings overlay.
- The settings overlay changes wheel behavior between scroll, zoom, and none, changes canvas drag behavior between selection, move, and none, and exports the current graph as an image.
Key Code Fragments
This fragment shows the fixed-layout graph configuration that lets the canvas host an absolute-positioned HTML scene.
const graphOptions: RGOptions = {
debug: false,
defaultJunctionPoint: RGJunctionPoint.lr,
layout: {
layoutName: 'fixed'
}
};
This fragment shows how the selected entity is translated into fake-line definitions instead of storing static edges in graph JSON.
if (activeGroupId) {
classMembers.forEach((member, index) => {
if (member.joinedGroups.includes(activeGroupId)) {
myFakeLines.push({
id: `line-member-${index}`,
from: 'member-' + member.name,
to: 'group-' + activeGroupId,
color: 'rgba(29,169,245,0.76)',
lineShape: RGLineShape.StandardCurve
});
}
});
}
This fragment shows the redraw cycle: wait for slot DOM to exist, clear prior connectors, add a placeholder node, and inject the new fake lines.
await graphInstance.sleep(100);
// ...
graphInstance.clearGraph();
graphInstance.addNodes([{ id: 'fake-root', text: '', opacity: 0, x: 0, y: 0 }]);
graphInstance.addFakeLines(myFakeLines);
This fragment shows how ordinary HTML inside RGSlotOnCanvas becomes a graph endpoint through RGConnectTarget.
<RGConnectTarget
key={group.groupId}
targetId={`group-${group.groupId}`}
junctionPoint={RGJunctionPoint.lr}
disableDrag={true}
disableDrop={true}
>
<div
className={`w-64 pointer-events-auto c-i-group cursor-point ${activeGroupId === group.groupId ? 'c-i-group-checked' : ''}`}
onClick={() => onGroupClick(group.groupId)}
>
This fragment shows that runtime canvas controls and image export come from shared helper code, not from the main example component.
<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 identifies inventory-structure-diagram, element-connect-to-node, element-lines, and scene-network-use-canvas-slot as its nearest neighbors. The distinctive step here is bidirectional many-to-many browsing: a group selection fans in from matching members and adds a group-to-location line, while a member selection fans out to multiple groups. That is broader than a one-way detail panel or a one-to-one endpoint-attachment demo.
This example also wires three endpoint families inside one composed scene: location-*, group-*, and member-*. Compared with element-lines and element-connect-to-node, the emphasis shifts from proving DOM endpoint attachment to coordinating three visible surfaces with one selection model. Compared with scene-network-use-canvas-slot, the connector layer is not a static board prepared at mount time; it is rebuilt repeatedly from current selection state. The floating utility window is part of the overall combination, but it is shared infrastructure rather than a unique feature of this example.
Where Else This Pattern Applies
This pattern transfers well to interfaces where teams already have custom HTML cards or map markers and only need relation-graph to supply the connective layer. Examples include staff-to-project boards, volunteer-to-event matching screens, warehouse-to-product membership views, customer-to-segment explorers, or campus and floor maps that need list-to-location highlighting.
It is also a strong starting point for dashboards where one selection should synchronize multiple panels without converting the whole interface into standard graph nodes. The reusable idea is the combination of fixed canvas composition, stable RGConnectTarget ids, and selection-driven fake-line regeneration.