[{"data":1,"prerenderedAt":7},["ShallowReactive",2],{"example-markdown-content:en:database-foreign-key-diagram":3},{"markdown":4,"isPaidExample":5,"isTruncated":5,"charLimit":6},"# Column-Level Foreign Key Lines in Fixed Database Table Cards\n\n## What This Example Builds\n\nThis example builds a fixed-layout database schema viewer inside a full-height relation-graph canvas. The screen shows six orange table cards, each card lists columns and data types, and curved purple or blue relationship lines connect specific field rows instead of connecting only to whole nodes.\n\nUsers can inspect the prepared schema, drag or minimize the floating helper window, open a settings panel to change wheel and canvas-drag behavior, and export the current graph as an image. The main technical point is the column-level wiring: custom table markup is not just decorative, it gives relation-graph exact DOM targets for foreign-key lines.\n\n## How the Data Is Organized\n\nThe dataset is declared inline inside `initializeGraph()` as three arrays: `tables`, `tableCols`, and `columnRelations`. `tables` defines the table ids, visible labels, and authored `x` and `y` coordinates. `tableCols` holds the per-table field list, and `columnRelations` describes source column, target column, and relation type.\n\nBefore `setJsonData(...)`, the example performs a small preprocessing pass. It maps `tables` into graph nodes with `data.columns`, then maps `columnRelations` into `fakeLines` whose `from` and `to` values point at generated endpoint ids such as `col-name-SYS_USER-dept_id`. Ordinary `lines` stay empty because the relationships are attached to DOM elements rendered inside each node slot, not to default node anchors.\n\nIn a real application, the same structure could come from database metadata, ORM model definitions, API schema registries, field-mapping catalogs, or data lineage records. The fixed coordinates could remain authored for a curated reference diagram or be generated elsewhere before the final payload is passed into relation-graph.\n\n## How relation-graph Is Used\n\n`index.tsx` wraps the example in `RGProvider`, and `MyGraph.tsx` uses `RGHooks.useGraphInstance()` to load the graph after mount. The graph options keep relation-graph in `layoutName: 'fixed'`, set `defaultJunctionPoint: RGJunctionPoint.border`, remove the default node border, set an orange default node color, and define a custom triangular line marker. After loading the JSON, the example calls `moveToCenter()` and `zoomToFit()` so the prepared schema fills the viewport immediately.\n\nThe main customization happens through `RGSlotOnNode`. Each node becomes a 300-pixel table card with a header and an HTML table of fields. Inside each column-name cell, `RGConnectTarget` registers a `targetId` derived from the table id and column name. The `fakeLines` payload then connects to those ids, which is what makes the foreign-key curves land on exact field rows.\n\nThe example does not implement editing, authoring, or runtime graph mutation. Node and line click handlers are present, but they only log the clicked objects. Additional utility behavior comes from the shared `DraggableWindow` and `CanvasSettingsPanel`: the settings panel reads current drag and wheel modes with `RGHooks.useGraphStore()`, updates them with `graphInstance.setOptions(...)`, and uses `prepareForImageGeneration()` plus `restoreAfterImageGeneration()` to export the graph as an image. The local stylesheet adds the tiled background, checked-state emphasis, and the orange table styling.\n\n## Key Interactions\n\n- The graph initializes on mount, then recenters and fits the authored schema so the viewer opens on a complete diagram.\n- The floating helper window can be dragged by its title bar and minimized, which keeps the description and tools movable instead of fixed over one part of the canvas.\n- Opening the settings panel exposes live wheel-mode and drag-mode switches, and each choice updates the mounted relation-graph instance immediately.\n- The download action prepares the graph for capture, renders the canvas DOM into an image blob, downloads it, and restores the graph state afterward.\n- Clicking a node or a relationship line only logs the related object, so the example remains a read-only inspection view rather than a schema editor.\n\n## Key Code Fragments\n\nThis fragment shows how table metadata is converted into fixed-position graph nodes with per-table column data attached to `node.data`:\n\n```tsx\nconst graphNodes = tables.map(table => {\n    const { tableName, tableComents, x, y } = table;\n    return {\n        id: tableName,\n        text: tableComents,\n        x,\n        y,\n        nodeShape: RGNodeShape.rect,\n        nodeShape: RGNodeShape.rect,\n        data: {\n            columns: tableCols.filter(col => col.tableName === table.tableName)\n        }\n    };\n});\n```\n\nThis fragment shows the crucial preprocessing step: each foreign-key definition becomes a curved `fakeLine` whose endpoints are column-level target ids:\n\n```tsx\nconst myFakeLines = columnRelations.map((relation, index) => {\n    return {\n        id: `rel-line-${index}`,\n        from: `col-name-${relation.sourceTableName}-${relation.sourceColumnName}`,\n        to: `col-name-${relation.targetTableName}-${relation.targetColumnName}`,\n        color: relation.type === 'ONE_TO_ONE' ? 'rgba(29,169,245,0.76)' : 'rgba(159,23,227,0.65)',\n        text: '',\n        fromJunctionPoint: RGJunctionPoint.left,\n        toJunctionPoint: RGJunctionPoint.lr,\n        lineShape: RGLineShape.StandardCurve,\n        lineWidth: 3\n    };\n});\n```\n\nThis fragment shows that the loaded payload intentionally leaves ordinary `lines` empty and relies on `fakeLines` instead:\n\n```tsx\nconst myJsonData: RGJsonData = {\n    nodes: graphNodes,\n    lines: [],\n    fakeLines: myFakeLines\n};\n\nawait graphInstance.setJsonData(myJsonData);\ngraphInstance.moveToCenter();\ngraphInstance.zoomToFit();\n```\n\nThis fragment shows the fixed-layout graph configuration and the custom line marker used by the schema viewer:\n\n```tsx\nconst graphOptions: RGOptions = {\n    debug: false,\n    defaultJunctionPoint: RGJunctionPoint.border,\n    defaultNodeColor: '#f39930',\n    defaultNodeBorderWidth: 0,\n    defaultLineMarker: {\n        markerWidth: 20,\n        markerHeight: 20,\n        refX: 3,\n        refY: 3,\n        viewBox: '0 0 6 6',\n        data: \"M 0 0, V 6, L 4 3, Z\"\n    },\n    layout: {\n        layoutName: 'fixed'\n    }\n};\n```\n\nThis fragment shows how the custom node slot renders table rows and registers each column name as a line endpoint:\n\n```tsx\n\u003CRGConnectTarget\n    targetId={`col-name-${node.id}-${column.columnName}`}\n    junctionPoint={RGJunctionPoint.lr}\n>\n    \u003Cdiv className=\"w-fit px-2\">{column.columnName}\u003C/div>\n\u003C/RGConnectTarget>\n```\n\nThis fragment shows the runtime settings pattern: the shared panel reads current graph-store values and pushes updates through `setOptions(...)`:\n\n```tsx\nconst { options } = RGHooks.useGraphStore();\nconst dragMode = options.dragEventAction;\nconst wheelMode = options.wheelEventAction;\n\n\u003CSettingRow\n    label=\"Wheel Event:\"\n    options={[\n        { label: 'Scroll', value: 'scroll' },\n        { label: 'Zoom', value: 'zoom' },\n        { label: 'None', value: 'none' },\n    ]}\n    value={wheelMode}\n    onChange={(newValue: string) => { graphInstance.setOptions({ wheelEventAction: newValue }); }}\n/>\n```\n\nThis fragment shows the export flow that prepares relation-graph for capture, renders the canvas DOM, and restores the graph afterward:\n\n```tsx\nconst canvasDom = await graphInstance.prepareForImageGeneration();\nlet graphBackgroundColor = graphInstance.getOptions().backgroundColor;\nif (!graphBackgroundColor || graphBackgroundColor === 'transparent') {\n    graphBackgroundColor = '#ffffff';\n}\nconst imageBlob = await domToImageByModernScreenshot(canvasDom, {\n    backgroundColor: graphBackgroundColor\n});\nawait graphInstance.restoreAfterImageGeneration();\n```\n\n## What Makes This Example Distinct\n\nThe comparison data makes the distinction clear. Compared with `drag-and-wheel-event` and `layout-center`, the floating settings panel is secondary here. Those neighbors are mainly about canvas-behavior tuning or runtime restyling, while this example uses the same shared utility shell to support a more specialized lesson: wiring schema relationships to exact rows inside custom node content.\n\nCompared with `node` and `use-d3-layout`, the slotted HTML is structural rather than cosmetic. The custom markup exists so `RGConnectTarget` can expose per-column endpoints and `fakeLines` can land on those endpoints, not mainly to showcase skinning or survive an external relayout pass. Compared with `adv-dynamic-data`, the complexity is concentrated in one preprocessing step that turns table metadata into a fixed snapshot; there is no staged growth after mount.\n\nThe rare combination identified in the prepared analysis is the important takeaway: `layoutName = 'fixed'`, repeated table-card node slots, column-level DOM endpoints, color-coded curved `fakeLines`, a tiled technical canvas, a legend, and shared export or canvas-settings utilities in one read-only viewer. That combination makes this example a strong starting point for schema-style diagrams where relationships must attach to embedded fields instead of whole nodes.\n\n## Where Else This Pattern Applies\n\nThis pattern transfers well to database documentation pages, entity-relationship viewers, and internal schema explorers where teams need a curated fixed arrangement instead of an automatic layout. The same preprocessing approach can also be adapted for API payload comparisons, ETL field mappings, permission-to-resource matrices, or data-lineage views where links must terminate on named rows inside a larger card.\n\nIt is also useful when a product needs a technical reference screen rather than an editor. A team can keep the row-level endpoint technique, swap in different metadata, and preserve the floating export or canvas-settings tools for analyst workspaces, architecture reviews, or support documentation snapshots.\n",false,500,1782615397720]