[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:compare-element-to-node-and-element-to-element-links":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Comparing Element-to-Node and Element-to-Element Links\n\n## What This Example Builds\n\nThis example builds a three-panel comparison board inside a single relation-graph canvas. The left panel shows location markers as real graph nodes on top of a map, the middle panel shows selectable interest-group cards, and the right panel shows the same locations as ordinary HTML markers. When a group is selected, the example redraws two animated connectors from the same list item: one goes to the graph node on the left and the other goes to the HTML marker on the right.\n\nThe visible result is less about graph topology and more about endpoint strategy. It lets developers compare, under the same coordinates and styling, what changes when a destination stays inside the graph model versus when it becomes a plain DOM target. A floating helper window adds runtime canvas settings and image export, but the main highlight is the synchronized side-by-side connector comparison.\n\n## How the Data Is Organized\n\nThe example uses a small inline dataset in `MyGraph.tsx`. Each record contains a `groupId`, a `groupName`, and a `location` object with explicit `x` and `y` coordinates. There is no `setJsonData()` call here. Instead, initialization takes the inline array, stores it in React state as `interestGroups`, and projects it into graph nodes through `addNodes(...)`.\n\nThat same `interestGroups` state is reused three ways:\n\n- to create fixed-position graph nodes with ids like `node-location-a`\n- to render the middle-column list items with connect-target ids like `group-a`\n- to render right-panel HTML markers with connect-target ids like `el-location-a`\n\nThis is the important data lesson in the example: one source record drives both graph-native endpoints and plain DOM endpoints. In a production scenario, the records could represent stores on a campus map, warehouse stations, maintenance points, event booths, or service desks where one interface needs to compare multiple anchor strategies against the same physical coordinates.\n\n## How relation-graph Is Used\n\n`RGProvider` wraps the demo so hooks can access the active graph context. The graph itself runs with `layout.layoutName = 'fixed'`, which means the nodes stay at the exact coordinates supplied during `addNodes(...)`. The options also disable debug mode, set border junctions as the default attachment mode, and start the canvas in zoom-on-wheel and move-on-drag mode.\n\nThe example relies on relation-graph in four distinct ways:\n\n- `RGHooks.useGraphInstance()` drives graph setup and runtime control. It adds the fixed-position nodes, centers the viewport, fits the scene, clears prior fake lines, adds replacement fake lines, updates runtime options, and prepares the canvas for image export.\n- `RGSlotOnNode` replaces the default node renderer with a `MapPin` marker. The node slot uses `checked` styling and forwards clicks back into the shared group-selection handler through `node.data.myGroupId`.\n- `RGSlotOnCanvas` is where the surrounding scene is composed. Instead of using a plain node-link diagram, the demo renders the left map, the middle list, and the right map as one canvas-hosted interface.\n- `RGConnectTarget` supplies attachable DOM endpoints for the list cards and the right-side HTML markers. The left-side connector targets a real node, so that fake line sets `toType = RGInnerConnectTargetType.Node`. The right-side connector uses a normal connect-target id.\n\nThe floating helper window is a shared subcomponent rather than a feature unique to this example, but it still matters technically. Its settings panel uses `RGHooks.useGraphStore()` to reflect live canvas modes and `graphInstance.setOptions(...)` to switch wheel and drag behavior at runtime. For export, it calls `prepareForImageGeneration()`, captures the canvas DOM with `modern-screenshot`, downloads the blob, and then restores the graph state.\n\nStyling is handled in the local SCSS file. The stylesheet gives group cards a purple checked halo, turns both marker types into animated pin-like targets, and makes the line labels inherit the active connector color through `var(--rg-line-color)`.\n\n## Key Interactions\n\n- A mount-time effect seeds the nodes, centers the viewport, fits the scene, and auto-selects group `a` after a short delay so the comparison is visible immediately.\n- Clicking a list card, a left-side graph marker, or a right-side HTML marker all call the same `onGroupClick(...)` function.\n- Each selection updates `activeGroupId`, clears the previous fake lines, and inserts a new pair of animated curved connectors for the chosen group.\n- The helper window can be dragged, minimized, switched into a settings overlay, and used to change wheel mode, change drag mode, or download an image of the current canvas.\n\n## Key Code Fragments\n\nThis fragment shows that the example starts from one inline dataset, then projects it into fixed-position graph nodes before any connector logic runs.\n\n```tsx\nconst myGroups = [\n  { groupId: 'a', groupName: 'Sports Group', location: { x: 260, y: 300 } },\n  // ...\n  { groupId: 'f', groupName: 'Science Research Group', location: { x: 600, y: 240 } }\n];\nsetInterestGroups(myGroups);\ngraphInstance.addNodes(myGroups.map(n => ({\n  id: 'node-location-' + n.groupId,\n  text: n.groupName,\n  x: n.location.x,\n  y: n.location.y,\n  disableDrag: true,\n  data: { myGroupId: n.groupId }\n})));\n```\n\nThis fragment proves that one selection regenerates two different fake lines: one to a real node and one to a DOM marker.\n\n```tsx\nconst myFakeLines: JsonLine[] = [\n  {\n    from: 'group-' + groupId,\n    to: 'node-location-' + groupId,\n    toType: RGInnerConnectTargetType.Node,\n    lineShape: RGLineShape.StandardCurve,\n    text: 'Element To Node',\n    animation: 2\n  },\n  {\n    from: 'group-' + groupId,\n    to: 'el-location-' + groupId,\n    lineShape: RGLineShape.StandardCurve,\n    text: 'Element To Element',\n    animation: 2\n  }\n];\ngraphInstance.clearFakeLines();\ngraphInstance.addFakeLines(myFakeLines);\n```\n\nThis fragment shows how the graph-side destination is rendered as a custom node marker and routed back into the shared selection state.\n\n```tsx\n\u003CRGSlotOnNode>\n  {({ node, checked }: RGNodeSlotProps) => (\n    \u003Cdiv\n      className={`pointer-events-auto cursor-point c-i-location ${checked ? 'c-i-location-active' : ''}`}\n      onClick={() => onGroupClick(node.data.myGroupId)}\n    >\n      \u003CMapPin className=\"transform translate-y-[-25px] translate-x-[-5px]\" size={24} />\n    \u003C/div>\n  )}\n\u003C/RGSlotOnNode>\n```\n\nThis fragment shows that the same state also drives ordinary HTML connect targets on the comparison panel to the right.\n\n```tsx\n{interestGroups.map(group => (\n  \u003Cdiv\n    key={group.groupId}\n    className=\"absolute\"\n    style={{ left: group.location.x + 'px', top: group.location.y + 'px' }}\n  >\n    \u003CRGConnectTarget targetId={`el-location-${group.groupId}`} junctionPoint={RGJunctionPoint.lr}>\n      \u003Cdiv onClick={() => onGroupClick(group.groupId)} className=\"pointer-events-auto cursor-point c-i-location\">\n        \u003CMapPin className=\"transform translate-y-[-25px] translate-x-[-5px]\" size={24} />\n      \u003C/div>\n    \u003C/RGConnectTarget>\n  \u003C/div>\n))}\n```\n\n## What Makes This Example Distinct\n\nAccording to the comparison data, this example is not just a basic element-to-node demo. Its distinctive point is that the same selected source item redraws two synchronized connectors at once: one ends on a real relation-graph node, while the other ends on a plain HTML marker. That makes it a stronger reference for attachment-strategy decisions than a single-pattern example.\n\nCompared with `element-line-edit`, this demo is less about one node-backed destination and more about side-by-side contrast. Compared with `element-lines`, it keeps one destination inside the graph model instead of putting both endpoints in ordinary HTML. Compared with broader map-backed boards such as `interest-group` and `inventory-structure-diagram`, it spends less effort on domain structure and more on making the endpoint difference easy to inspect.\n\nAnother comparison-backed distinction is the way it preserves spatial equivalence. The same `interestGroups` coordinate set is mirrored into left-side graph nodes and right-side HTML markers, so the visual difference comes from endpoint type rather than from layout drift. The combination of fixed-layout node projection, `RGSlotOnNode`, `RGSlotOnCanvas`, paired `RGConnectTarget` usage, automatic default selection, and synchronized fake-line replacement is relatively uncommon across the example set.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to products that need to evaluate whether an anchor should remain graph-native or move into plain DOM. Typical cases include campus and facility maps, logistics dashboards, seat or booth planners, retail floor systems, and monitoring panels where the same entity must be highlighted in a list and on a spatial surface at the same time.\n\nIt also works as a migration pattern. A team can keep one endpoint in the graph model for viewport-aware behavior, checked state, and slot rendering, while exposing another endpoint as ordinary HTML for easier integration with existing cards, panels, or overlays. The example is therefore useful both as a UI comparison board and as a starting point for hybrid graph-plus-DOM interfaces.\n",false,500,1782615412973]