数据库表外键关系图
这是一个固定布局的数据库结构查看器,将每张表渲染为自定义卡片,并用曲线假连线连接具体字段行。它先把表、字段和关系元数据预处理为插槽节点内容与 `RGConnectTarget` 端点 id,再叠加可拖拽辅助窗提供画布设置和图片导出。
固定数据库表卡片中的列级外键连线
这个示例构建了什么
这个示例在一个全高的 relation-graph 画布中构建了一个固定布局的数据库结构查看器。界面上会显示六张橙色表卡片,每张卡片都列出字段和数据类型,紫色或蓝色的弧形关系线会连接到具体的字段行,而不仅仅是连接整个节点。
用户可以查看预设好的 schema,拖动或最小化浮动辅助窗口,打开设置面板来修改滚轮和画布拖拽行为,并将当前图谱导出为图片。这里的主要技术点是列级连线:自定义表格标记不只是装饰,它还为 relation-graph 提供了外键连线所需的精确 DOM 目标。
数据是如何组织的
数据集在 initializeGraph() 中以内联方式声明为三个数组:tables、tableCols 和 columnRelations。tables 定义了表 id、可见标签,以及手工编写的 x 和 y 坐标。tableCols 保存每张表的字段列表,columnRelations 则描述源列、目标列和关系类型。
在调用 setJsonData(...) 之前,这个示例会先执行一次小型预处理。它先把 tables 映射为包含 data.columns 的图节点,再把 columnRelations 映射为 fakeLines,其中 from 和 to 会指向生成的端点 id,例如 col-name-SYS_USER-dept_id。普通的 lines 会保持为空,因为这些关系是附着在每个节点槽位内部渲染出的 DOM 元素上,而不是附着在默认节点锚点上。
在真实应用中,同样的数据结构可以来自数据库元数据、ORM 模型定义、API schema 注册表、字段映射目录,或数据血缘记录。固定坐标既可以继续由人工维护,用于生成经过整理的参考图,也可以在最终载荷传入 relation-graph 之前由其他地方生成。
relation-graph 是如何使用的
index.tsx 用 RGProvider 包裹这个示例,MyGraph.tsx 则通过 RGHooks.useGraphInstance() 在挂载后加载图谱。图谱配置让 relation-graph 保持 layoutName: 'fixed',设置 defaultJunctionPoint: RGJunctionPoint.border,移除默认节点边框,设置橙色默认节点颜色,并定义一个自定义三角形连线标记。JSON 加载完成后,示例会调用 moveToCenter() 和 zoomToFit(),让预设 schema 立即铺满视口。
主要定制发生在 RGSlotOnNode 中。每个节点都会变成一个 300 像素的表卡片,包含一个表头和一个字段 HTML 表格。在每个列名单元格内部,RGConnectTarget 会注册一个由表 id 和列名推导出的 targetId。随后 fakeLines 载荷就会连接到这些 id,这正是让外键曲线准确落到对应字段行上的关键。
这个示例并没有实现编辑、编排或运行时图谱变更。节点和连线点击处理器虽然存在,但只会记录被点击的对象。额外的实用行为来自共享的 DraggableWindow 和 CanvasSettingsPanel:设置面板通过 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-event 和 layout-center 相比,这里的浮动设置面板只是次要部分。那些相邻示例主要关注画布行为调节或运行时样式调整,而这个示例则借助同样的共享工具外壳来讲解一个更专门的主题:把 schema 关系连到自定义节点内容中的精确行。
与 node 和 use-d3-layout 相比,这里的槽位 HTML 是结构性的,而不是纯视觉性的。自定义标记存在的目的,是让 RGConnectTarget 能暴露每一列的端点,并让 fakeLines 落到这些端点上,而不是主要为了展示皮肤样式或在外部重新布局过程中继续生效。与 adv-dynamic-data 相比,这里的复杂度集中在一个预处理步骤中,即把表元数据转换为一个固定快照;挂载后不会再进行分阶段增长。
在已整理的分析中,那个少见的组合才是最重要的结论:layoutName = 'fixed'、重复出现的表卡片节点槽位、列级 DOM 端点、带颜色区分的弧形 fakeLines、平铺的技术风格画布、图例,以及共享的导出或画布设置工具共同组成了一个只读查看器。这个组合使得该示例非常适合作为 schema 风格图示的起点,特别是在关系必须连接到嵌入字段、而不是整个节点的场景中。
这种模式还适用于哪里
这种模式很适合用于数据库文档页面、实体关系查看器,以及需要人工整理固定排布而不是自动布局的内部 schema 浏览器。同样的预处理方式也可以适配 API 载荷对比、ETL 字段映射、权限到资源矩阵,或数据血缘视图,这些场景都需要让连线终止在较大卡片内部的命名行上。
当产品需要的是技术参考界面而不是编辑器时,这种模式也很有用。团队可以保留这种行级端点技术,替换成不同的元数据,同时继续使用浮动导出或画布设置工具,用于分析师工作台、架构评审,或支持文档快照。