JavaScript is required

Connect List Items to Location Nodes

This example connects a list of interest groups to fixed-position location nodes on a school map. It uses RGConnectTarget on the list side, real graph nodes on the map side, and one animated fake line that is replaced whenever the active group changes.

Connect List Cards to Fixed Location Nodes on a Map

What This Example Builds

This example builds a location-assignment view on top of a school map. The left side of the canvas shows fixed-position map pins, and the right side shows six interest-group cards. One animated connector is always used to highlight the currently selected relationship between a list item and its matching map location.

Users can switch the active relationship by clicking either the card or the pin. The scene also includes a floating helper window that can be dragged, minimized, opened into a settings panel, and used to export the current canvas as an image.

The main point of the example is not generic node editing. It is a focused DOM-to-node linking pattern: the list side is ordinary DOM wrapped with RGConnectTarget, while the destination side stays inside the relation-graph model as real nodes.

How the Data Is Organized

The data starts as a small inline array in MyGraph.tsx. Each record has a groupId, a display name, and a location object with explicit x and y coordinates.

Before anything is rendered, that array is used in two ways:

  • It is copied into React state as interestGroups, which drives the right-side card list.
  • It is projected into graph nodes with ids such as location-a, plus data.myGroupId, and then inserted with addNodes(...).

There is no setJsonData(...) call in this example. The graph content is assembled imperatively after mount, and the same source records feed both the DOM endpoints and the map-side nodes. In a real system, the same shape could represent departments on a campus map, booths in an event hall, equipment on a floor plan, or service points in a facility.

How relation-graph Is Used

The graph runs in fixed layout mode, so every projected node stays at the exact coordinates from the source records. The initial options also set defaultJunctionPoint to border, the mouse wheel to zoom mode, and canvas dragging to move mode.

RGProvider supplies graph context, and RGHooks.useGraphInstance() is the main control surface. The component uses that instance to add nodes, center the viewport, fit the zoom, clear the previous fake line, and add the new active fake line. The shared helper window also uses the graph instance to change runtime options and to run the image-export workflow.

Two slots define the visible scene:

  • RGSlotOnNode replaces normal node rendering with a clickable MapPin marker.
  • RGSlotOnCanvas renders the school map and the list of RGConnectTarget cards inside the same canvas layer.

RGConnectTarget is the key integration point on the list side. Each card gets a stable endpoint id like group-a, and the selected group is linked to its matching graph node by a fake line whose target type is explicitly set to RGInnerConnectTargetType.Node.

The floating helper window is not example-specific graph logic, but it matters to the complete behavior. It reads current graph options through RGHooks.useGraphStore(), updates wheelEventAction and dragEventAction with setOptions(...), and exports the graph after prepareForImageGeneration() and restoreAfterImageGeneration().

Key Interactions

  • The example auto-selects group a shortly after mount, so the first connector appears without manual input.
  • Clicking a list card or a map pin routes into the same onGroupClick(...) handler, updates the active group id, and redraws exactly one fake line.
  • The helper text and node setup indicate that the map-side pins are intended to be draggable in the live graph. The code does not write changed coordinates back into React state, so any repositioning is transient within the running canvas.
  • The floating helper window supports dragging, minimizing, opening the settings overlay, switching wheel and drag behaviors, and downloading a screenshot.

Key Code Fragments

This fragment shows how one inline record set becomes both UI state and fixed-position graph nodes.

const myGroups = [
    { groupId: 'a', groupName: 'Sports Group', location: { x: 260, y: 300 } },
    { groupId: 'b', groupName: 'Music Group', location: { x: 350, y: 100 } },
    // ...
];
setInterestGroups(myGroups);
graphInstance.addNodes(myGroups.map(n => ({
    id: 'location-' + n.groupId,
    x: n.location.x,
    y: n.location.y,
    data: { myGroupId: n.groupId }
})));

This fragment proves that selection does not rebuild the whole scene. It only replaces one active fake line.

const myFakeLines: JsonLine[] = [{
    id: `fl-${groupId}`,
    from: 'group-' + groupId,
    to: 'location-' + groupId,
    toType: RGInnerConnectTargetType.Node,
    color: 'rgba(159,23,227,0.65)',
    lineShape: RGLineShape.StandardCurve,
    fromJunctionPoint: RGJunctionPoint.lr,
    toJunctionPoint: RGJunctionPoint.border,
    animation: 2
}];
graphInstance.clearFakeLines();
graphInstance.addFakeLines(myFakeLines);

This fragment shows that the destination side remains a real graph node, even though it is rendered as a pin-style marker.

<RGSlotOnNode>
    {({ node, checked }: RGNodeSlotProps) => {
        return (
            <div
                className={`pointer-events-auto cursor-point c-i-location ${checked ? 'c-i-location-active' : ''}`}
                onClick={() => onGroupClick(node.data.myGroupId)}
            >
                <MapPin className='transform translate-y-[-25px] translate-x-[-5px]' size={24} />
            </div>
        );
    }}
</RGSlotOnNode>

This fragment shows how the list side is turned into connectable DOM endpoints.

<RGConnectTarget
    key={group.groupId}
    targetId={`group-${group.groupId}`}
    junctionPoint={RGJunctionPoint.lr}
    disableDrag={true}
    disableDrop={true}
>
    <div
        className={`w-full pointer-events-auto c-i-group cursor-point ${activeGroupId === group.groupId ? 'c-i-group-checked' : ''}`}
        onClick={() => onGroupClick(group.groupId)}
    >

This fragment shows how the shared helper panel changes runtime behavior and captures the graph canvas as an image.

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 between pure element-line demos and map-based node demos. Its clearest distinguishing point is that the list side stays ordinary DOM, while the destination side stays inside relation-graph as real fixed-position nodes. That makes it a focused reference for DOM-to-node connections rather than DOM-to-DOM wiring or a general-purpose graph editor.

Compared with element-lines, this example is more useful when the destination must keep graph identity, metadata, and built-in node behavior. Compared with element-connect-to-node, it is narrower and easier to reuse because it does not compare multiple strategies side by side. It keeps one map, one list, and one active connector, so the example concentrates on the selection-and-highlight workflow itself.

The comparison output also highlights the feature combination as unusually strong: fixed-layout map nodes, custom node rendering, canvas-composed list UI, draggable graph-backed pins, and per-selection clearFakeLines() plus addFakeLines() replacement of a single animated connector. That combination makes it a practical starting point for compact assignment boards where one current relationship should remain visually dominant.

Where Else This Pattern Applies

This pattern can be transferred to any interface where one side of the relationship belongs to the graph model and the other side belongs to ordinary page UI.

  • Assigning teams, devices, or tasks to fixed places on a floor plan or campus map.
  • Linking warehouse items or stations in a sidebar to spatial anchors on a storage layout.
  • Connecting event booths, service desks, or emergency points to positions on a venue map.
  • Building review tools where operators pick one record from a list and immediately see its mapped destination.

The reusable idea is the split responsibility: keep meaningful spatial anchors as graph nodes, keep surrounding workflow UI as DOM, and redraw only the currently relevant connector.