Connect Targets Across Canvas Elements, Nodes, and Grid Items
This example builds a fixed-layout playground where RGConnectTarget is attached to canvas widgets, whole nodes, internal node ports, and a reflowing grid of DOM cards in the same scene. It seeds representative fake lines, intercepts user-created connections with onLineBeCreated, and shows a toast instead of persisting new lines automatically.
Mixed Connect Targets Across Canvas Widgets, Nodes, and Grid Cards
What This Example Builds
This example builds a fixed-layout connect-target playground instead of a domain-specific graph. The scene mixes two standalone canvas widgets, two positioned graph nodes, internal node ports, and a grid of connectable cards inside the same RelationGraph instance.
What the user sees is a light full-screen sandbox with color-coded curved demo links, a green node that exposes multiple input and output ports, a blue fallback node with one internal target, two differently styled canvas elements, and a white control panel that changes the grid layout. The important behavior is that one page demonstrates several endpoint scopes at once and also intercepts newly created lines without automatically saving them.
How the Data Is Organized
The data is assembled inline rather than loaded through one large setJsonData(...) payload. initializeGraph() creates a small nodes array with explicit x and y coordinates, then creates a fakeLines array whose endpoints refer to ids such as canvas-el-a, n2, n1-input-1, and grid-item-6. Separately, component state stores gridCols, gridGap, and a six-item gridItems array for the connectable grid.
There is no separate preprocessing pass before setJsonData(...) or doLayout(...), but there is a strict id contract. Every line endpoint id must match either a graph node id, a node-internal RGConnectTarget, or a canvas-slot RGConnectTarget. Whole-node endpoints are marked with RGInnerConnectTargetType.Node, while internal ports and grid cards keep the default connect-target behavior. In a real application, the same structure could represent workflow widgets, device panels, field-mapping targets, scheduling slots, or dashboard cards that need to connect across mixed UI surfaces.
How relation-graph Is Used
RGProvider wraps the demo so RGHooks.useGraphInstance() can access the active graph instance. The graph runs in fixed layout mode, which means relation-graph does not compute placement from relationships; the example supplies node coordinates directly. The options also remove the default node border and fill, set the default line shape to RGLineShape.StandardCurve, and enable the built-in toolbar.
The example uses both major slot types. RGSlotOnNode replaces the default node body with custom JSX: node n1 becomes a green card with five internal targets, while the fallback node renderer exposes one extra internal target on the blue node. RGSlotOnCanvas renders two free-positioned canvas widgets and a dynamic CSS-grid block whose cells are also wrapped in RGConnectTarget. This is the core relation-graph technique here: connector routing is handled by the graph engine, but the actual endpoints live inside ordinary DOM rendered through node and canvas slots.
The graph instance API drives initialization. On mount, the demo calls addNodes(...), waits briefly for the DOM to exist, recenters the viewport, fits the scene, and finally seeds the demo links with addFakeLines(...). The onLineBeCreated callback is the manual persistence handoff. When a user creates a new line, the demo shows a toast message with the source and target ids, but it does not call addLines(...) to store that line in graph data. Styling is minimal and targeted: the SCSS file mainly makes line labels inherit the current line color so each seeded connection case stays readable.
Key Interactions
- The built-in toolbar and normal graph viewport controls are enabled because
showToolBaris set totrue. - The scene starts with seeded fake lines that demonstrate canvas-to-canvas, canvas-to-node, canvas-to-internal-port, node-to-internal-port, internal-to-internal, and grid-to-grid connections.
- Two number inputs inside the canvas slot update
gridColsandgridGap, so the grid of endpoint cards reflows at runtime while remaining connectable. - When the user creates a new line,
onLineBeCreatedshows a success toast naming the source and target ids, but the new line is not persisted automatically.
Key Code Fragments
This fragment shows the graph configured as a fixed scene with transparent node chrome, curved default lines, and the built-in toolbar enabled.
const graphOptions: RGOptions = {
debug: false,
layout: {
layoutName: 'fixed' // Use fixed layout for easier manual positioning
},
defaultNodeBorderWidth: 0,
defaultNodeColor: 'transparent',
defaultLineShape: RGLineShape.StandardCurve,
showToolBar: true
};
This fragment shows the mixed endpoint typing pattern: one fake line targets a whole node through RGInnerConnectTargetType.Node, while another lands on a named internal port.
{
id: 'f-2', from: 'canvas-el-a',
to: 'n2',
toType: RGInnerConnectTargetType.Node,
text: 'Element to Node', color: '#9c27b0'
},
{ id: 'f-3', from: 'canvas-el-b', fromJunctionPoint: RGJunctionPoint.ltrb, to: 'n1-input-1', text: 'To Internal Target', color: '#009688' },
{ id: 'f-801', from: 'canvas-el-b', fromJunctionPoint: RGJunctionPoint.ltrb, to: 'n2', toType: RGInnerConnectTargetType.Node, text: 'Element to Node', color: '#9c27b0' },
This fragment shows the mount-time initialization flow through the graph instance API.
graphInstance.addNodes(nodes);
await graphInstance.sleep(200); // Wait for nodes to finish rendering
graphInstance.moveToCenter(); // Center based on elements in the canvas slot
graphInstance.zoomToFit(); // Zoom to a suitable scale based on elements in the canvas slot
graphInstance.addFakeLines(fakeLines);
This fragment shows that newly created lines are intercepted and surfaced immediately, but not persisted.
const onLineBeCreated = (lineInfo: {
lineJson: JsonLine,
fromNode: RGLineTarget | RGNode,
toNode: RGLineTarget | RGNode
}) => {
SimpleGlobalMessage.success(`New line created! From ${lineInfo.fromNode.id} to ${lineInfo.toNode.id}`);
// English:Here you need to add the new line to the graph yourself through the graphInstance.addLines method.
}
This fragment shows a real internal port living inside custom node content.
<RGConnectTarget
targetId="n1-input-1"
junctionPoint={RGJunctionPoint.left}
>
<div className="bg-orange-500 text-white text-[10px] px-2 py-1 rounded cursor-pointer hover:bg-orange-600 transition-colors">
Input 1
</div>
</RGConnectTarget>
This fragment shows the canvas-slot grid pattern: ordinary HTML cards are mapped into RGConnectTargets, so they behave like endpoints even while the grid layout changes.
<div
className="p-4 bg-gray-200"
style={{
display: 'grid',
gridTemplateColumns: `repeat(${gridCols}, 1fr)`,
gap: `${gridGap}px`
}}
>
{gridItems.map(item => (
<RGConnectTarget key={item.id} targetId={item.id}>
This fragment shows the SCSS override that makes line labels inherit each line’s current color.
.rg-line-label {
background-color: var(--rg-line-color);
color: #fff;
font-size: 10px;
}
What Makes This Example Distinct
The comparison data identifies this example as a mixed-endpoint reference rather than a scene-specific viewer. Its strongest distinction is breadth: it demonstrates RGConnectTarget across standalone canvas widgets, whole graph nodes, internal node ports, and grid items rendered inside ordinary canvas-slot HTML. That is a rarer combination than examples that stay only in RGSlotOnCanvas or only inside node content.
Compared with scene-network-use-canvas-slot, this demo is not limited to a canvas-only DOM board. It adds RGSlotOnNode ports, explicit whole-node typing through RGInnerConnectTargetType.Node, and onLineBeCreated as the handoff point for user-made connections. Compared with node-content-lines, the lesson is not precise node-internal anchoring alone; it is interoperability between node-slot targets, whole nodes, and canvas-slot endpoints. Compared with mix-layout-2 and interest-group, the focus is not relayout orchestration or selection-driven browsing. It works more like a compact laboratory for comparing connection behaviors across several host types in one scene.
The live grid controls also make it unusual inside the connect-target examples. The grid can change column count and gap at runtime, but its cards remain valid endpoints. Combined with the non-persisted line-creation callback, that makes this example a strong starting point when a team needs to test how mixed DOM targets behave before deciding how to store user edits.
Where Else This Pattern Applies
This pattern transfers well to applications that mix graph nodes with freeform interface widgets. Examples include workflow builders with node ports plus floating control panels, device dashboards that connect racks and canvas-level alert widgets, and mapping tools where users need to connect form fields, cards, or grid cells rather than only whole nodes.
It is also useful as a migration pattern when a team already has HTML fragments that should stay as ordinary DOM. By wrapping those fragments in RGConnectTarget and using node or canvas slots as needed, the team can add visible relationship routing without redesigning every object as a standard graph node body.