JavaScript is required

数据库表外键关系图

这是一个固定布局的数据库结构查看器,将每张表渲染为自定义卡片,并用曲线假连线连接具体字段行。它先把表、字段和关系元数据预处理为插槽节点内容与 `RGConnectTarget` 端点 id,再叠加可拖拽辅助窗提供画布设置和图片导出。

固定数据库表卡片中的列级外键连线

这个示例构建了什么

这个示例在一个全高的 relation-graph 画布中构建了一个固定布局的数据库结构查看器。界面上会显示六张橙色表卡片,每张卡片都列出字段和数据类型,紫色或蓝色的弧形关系线会连接到具体的字段行,而不仅仅是连接整个节点。

用户可以查看预设好的 schema,拖动或最小化浮动辅助窗口,打开设置面板来修改滚轮和画布拖拽行为,并将当前图谱导出为图片。这里的主要技术点是列级连线:自定义表格标记不只是装饰,它还为 relation-graph 提供了外键连线所需的精确 DOM 目标。

数据是如何组织的

数据集在 initializeGraph() 中以内联方式声明为三个数组:tablestableColscolumnRelationstables 定义了表 id、可见标签,以及手工编写的 xy 坐标。tableCols 保存每张表的字段列表,columnRelations 则描述源列、目标列和关系类型。

在调用 setJsonData(...) 之前,这个示例会先执行一次小型预处理。它先把 tables 映射为包含 data.columns 的图节点,再把 columnRelations 映射为 fakeLines,其中 fromto 会指向生成的端点 id,例如 col-name-SYS_USER-dept_id。普通的 lines 会保持为空,因为这些关系是附着在每个节点槽位内部渲染出的 DOM 元素上,而不是附着在默认节点锚点上。

在真实应用中,同样的数据结构可以来自数据库元数据、ORM 模型定义、API schema 注册表、字段映射目录,或数据血缘记录。固定坐标既可以继续由人工维护,用于生成经过整理的参考图,也可以在最终载荷传入 relation-graph 之前由其他地方生成。

relation-graph 是如何使用的

index.tsxRGProvider 包裹这个示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 在挂载后加载图谱。图谱配置让 relation-graph 保持 layoutName: 'fixed',设置 defaultJunctionPoint: RGJunctionPoint.border,移除默认节点边框,设置橙色默认节点颜色,并定义一个自定义三角形连线标记。JSON 加载完成后,示例会调用 moveToCenter()zoomToFit(),让预设 schema 立即铺满视口。

主要定制发生在 RGSlotOnNode 中。每个节点都会变成一个 300 像素的表卡片,包含一个表头和一个字段 HTML 表格。在每个列名单元格内部,RGConnectTarget 会注册一个由表 id 和列名推导出的 targetId。随后 fakeLines 载荷就会连接到这些 id,这正是让外键曲线准确落到对应字段行上的关键。

这个示例并没有实现编辑、编排或运行时图谱变更。节点和连线点击处理器虽然存在,但只会记录被点击的对象。额外的实用行为来自共享的 DraggableWindowCanvasSettingsPanel:设置面板通过 RGHooks.useGraphStore() 读取当前拖拽和滚轮模式,再通过 graphInstance.setOptions(...) 更新这些配置,并使用 prepareForImageGeneration()restoreAfterImageGeneration() 将图谱导出为图片。本地样式表则补充了平铺背景、选中态强调效果,以及橙色表格样式。

关键交互

  • 图谱会在挂载时初始化,然后重新居中并适配预设 schema,使查看器打开时就能看到完整图示。
  • 浮动辅助窗口可以通过标题栏拖动,也可以最小化,这样说明和工具就能移动,而不会固定压在画布某个区域上。
  • 打开设置面板后,可以实时切换滚轮模式和拖拽模式,每次选择都会立即更新已挂载的 relation-graph 实例。
  • 下载操作会先为截图准备图谱,把画布 DOM 渲染成图片 blob,下载之后再恢复图谱状态。
  • 点击节点或关系线只会记录相关对象,因此这个示例仍然是一个只读查看视图,而不是 schema 编辑器。

关键代码片段

这段代码展示了如何把表元数据转换为固定位置的图节点,并将每张表的列数据附加到 node.data 上:

const graphNodes = tables.map(table => {
    const { tableName, tableComents, x, y } = table;
    return {
        id: tableName,
        text: tableComents,
        x,
        y,
        nodeShape: RGNodeShape.rect,
        nodeShape: RGNodeShape.rect,
        data: {
            columns: tableCols.filter(col => col.tableName === table.tableName)
        }
    };
});

这段代码展示了关键的预处理步骤:每个外键定义都会变成一条弧形 fakeLine,其端点是列级目标 id:

const myFakeLines = columnRelations.map((relation, index) => {
    return {
        id: `rel-line-${index}`,
        from: `col-name-${relation.sourceTableName}-${relation.sourceColumnName}`,
        to: `col-name-${relation.targetTableName}-${relation.targetColumnName}`,
        color: relation.type === 'ONE_TO_ONE' ? 'rgba(29,169,245,0.76)' : 'rgba(159,23,227,0.65)',
        text: '',
        fromJunctionPoint: RGJunctionPoint.left,
        toJunctionPoint: RGJunctionPoint.lr,
        lineShape: RGLineShape.StandardCurve,
        lineWidth: 3
    };
});

这段代码展示了已加载的载荷会刻意让普通 lines 保持为空,转而依赖 fakeLines

const myJsonData: RGJsonData = {
    nodes: graphNodes,
    lines: [],
    fakeLines: myFakeLines
};

await graphInstance.setJsonData(myJsonData);
graphInstance.moveToCenter();
graphInstance.zoomToFit();

这段代码展示了固定布局的图谱配置,以及 schema 查看器使用的自定义连线标记:

const graphOptions: RGOptions = {
    debug: false,
    defaultJunctionPoint: RGJunctionPoint.border,
    defaultNodeColor: '#f39930',
    defaultNodeBorderWidth: 0,
    defaultLineMarker: {
        markerWidth: 20,
        markerHeight: 20,
        refX: 3,
        refY: 3,
        viewBox: '0 0 6 6',
        data: "M 0 0, V 6, L 4 3, Z"
    },
    layout: {
        layoutName: 'fixed'
    }
};

这段代码展示了自定义节点槽位如何渲染表格行,并把每个列名注册为连线端点:

<RGConnectTarget
    targetId={`col-name-${node.id}-${column.columnName}`}
    junctionPoint={RGJunctionPoint.lr}
>
    <div className="w-fit px-2">{column.columnName}</div>
</RGConnectTarget>

这段代码展示了运行时设置模式:共享面板会读取当前图谱 store 中的值,并通过 setOptions(...) 推送更新:

const { options } = RGHooks.useGraphStore();
const dragMode = options.dragEventAction;
const wheelMode = options.wheelEventAction;

<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 }); }}
/>

这段代码展示了导出流程:先让 relation-graph 为截图做好准备,再渲染画布 DOM,最后恢复图谱状态:

const canvasDom = await graphInstance.prepareForImageGeneration();
let graphBackgroundColor = graphInstance.getOptions().backgroundColor;
if (!graphBackgroundColor || graphBackgroundColor === 'transparent') {
    graphBackgroundColor = '#ffffff';
}
const imageBlob = await domToImageByModernScreenshot(canvasDom, {
    backgroundColor: graphBackgroundColor
});
await graphInstance.restoreAfterImageGeneration();

这个示例有什么独特之处

结合对比数据后,可以更清楚地看到差异。与 drag-and-wheel-eventlayout-center 相比,这里的浮动设置面板只是次要部分。那些相邻示例主要关注画布行为调节或运行时样式调整,而这个示例则借助同样的共享工具外壳来讲解一个更专门的主题:把 schema 关系连到自定义节点内容中的精确行。

nodeuse-d3-layout 相比,这里的槽位 HTML 是结构性的,而不是纯视觉性的。自定义标记存在的目的,是让 RGConnectTarget 能暴露每一列的端点,并让 fakeLines 落到这些端点上,而不是主要为了展示皮肤样式或在外部重新布局过程中继续生效。与 adv-dynamic-data 相比,这里的复杂度集中在一个预处理步骤中,即把表元数据转换为一个固定快照;挂载后不会再进行分阶段增长。

在已整理的分析中,那个少见的组合才是最重要的结论:layoutName = 'fixed'、重复出现的表卡片节点槽位、列级 DOM 端点、带颜色区分的弧形 fakeLines、平铺的技术风格画布、图例,以及共享的导出或画布设置工具共同组成了一个只读查看器。这个组合使得该示例非常适合作为 schema 风格图示的起点,特别是在关系必须连接到嵌入字段、而不是整个节点的场景中。

这种模式还适用于哪里

这种模式很适合用于数据库文档页面、实体关系查看器,以及需要人工整理固定排布而不是自动布局的内部 schema 浏览器。同样的预处理方式也可以适配 API 载荷对比、ETL 字段映射、权限到资源矩阵,或数据血缘视图,这些场景都需要让连线终止在较大卡片内部的命名行上。

当产品需要的是技术参考界面而不是编辑器时,这种模式也很有用。团队可以保留这种行级端点技术,替换成不同的元数据,同时继续使用浮动导出或画布设置工具,用于分析师工作台、架构评审,或支持文档快照。