图谱数据流转与修改(Data Flow & CRUD)
本页说明 relation-graph 中数据如何进入图谱、如何变成运行时对象、如何查询、修改、导出,以及哪些操作会触发布局或视图变化。
relation-graph 的数据流可以概括为:
JsonNode / JsonLine / fakeLines
-> 数据提供者转换
-> RGNode / RGLine / RGFakeLine
-> 运行时索引与 RGLink
-> 可见性计算 / 布局 / 渲染
1. 三种主要数据接入方式
方式 A:setJsonData 一次性设置整图
await graphInstance.setJsonData({
rootId: 'root',
nodes: [
{ id: 'root', text: '根节点' },
{ id: 'a', text: '节点 A' }
],
lines: [
{ id: 'root-a', from: 'root', to: 'a', text: '关系' }
],
fakeLines: []
});
实际流程:
- 清空当前图谱数据。
- 加载
nodes、lines、fakeLines。 - 将树形
children展开为扁平节点和线。 - 设置
rootId。 - 调用
doLayout(rootId)。
适合:
- 首次加载完整图谱。
- 切换到另一份完整数据。
- 希望按当前
options.layout自动重新布局。
不适合:
- 高频编辑器操作。
- 用户正在拖拽、缩放或局部编辑时反复刷新整图。
- 只想改一两个字段的局部更新。
方式 B:applyInitialData 初始化并自动适配视图
await graphInstance.applyInitialData(data);
实际流程:
- 调用
setJsonData(data)。 - 调用
moveToCenter()。 - 调用
zoomToFit()。
适合首屏展示完整图谱,让用户一进页面就看到全部内容。
方式 C:增量 API 动态修改
graphInstance.addNodes([{ id: 'b', text: '节点 B' }]);
graphInstance.addLines([{ id: 'a-b', from: 'a', to: 'b' }]);
graphInstance.updateNode('b', { color: '#dcfce7' });
graphInstance.updateLine('a-b', { text: '新增关系' });
适合:
- 图谱编辑器。
- 流式加载。
- 局部更新。
- 用户交互过程中保持当前视图不跳动。
增量 API 不会自动做完整居中缩放。需要时手动调用:
await graphInstance.doLayout();
graphInstance.moveToCenter();
graphInstance.zoomToFit();
方式 D:组件 initialData
组件属性中的 initialData 适合组件初始化阶段传入初始数据。它不是推荐的响应式更新通道。
如果业务数据变化后需要更新图谱,优先使用:
setJsonData():整图替换。appendJsonData():追加一批图数据。add/update/remove:局部更新。
2. RGJsonData 数据结构
type RGJsonData = {
rootId?: string;
nodes: JsonNode[];
lines: JsonLine[];
fakeLines?: JsonLine[];
};
字段说明:
| 字段 | 说明 |
|---|---|
rootId |
根节点 id。影响 doLayout() 的布局起点。未设置时通常使用第一个节点。 |
nodes |
节点数组。可包含带 children 的树形节点。 |
lines |
普通连线数组。端点必须是节点 id。 |
fakeLines |
虚拟连线数组。端点可以是节点、连接点、HTML 元素或自定义对象。 |
历史兼容:
relations、links、edges会被兼容为lines,但会输出警告。elementLines已废弃,推荐改为fakeLines+RGInnerConnectTargetType.HTMLElementId。
3. 添加数据
添加节点
graphInstance.addNode({
id: 'n1',
text: '节点 1'
});
graphInstance.addNodes([
{ id: 'n2', text: '节点 2' },
{ id: 'n3', text: '节点 3', x: 200, y: 100 }
]);
规则:
addNode接收单个JsonNode。addNodes接收数组。- 已存在相同
id的节点会被跳过,不会覆盖。 - 如果节点没有
text,源码可能兼容使用label或id,但新代码应显式使用text。
添加普通连线
graphInstance.addLines([
{
id: 'n1-n2',
from: 'n1',
to: 'n2',
text: '关系'
}
]);
graphInstance.addLines([
{ id: 'n2-n3', from: 'n2', to: 'n3' }
]);
规则:
from/to对应节点必须存在。id重复会跳过。- 未设置
id时源码会生成,但不利于后续持久化。 source/target/label只作为旧数据兼容,不推荐新代码使用。
添加虚拟连线
graphInstance.addFakeLines([
{
id: 'node-to-port',
isFakeLine: true,
from: 'n1',
fromType: RGInnerConnectTargetType.Node,
to: 'port-1',
toType: RGInnerConnectTargetType.NodePoint,
text: '端口连接'
}
]);
也可以通过 addLines 添加带 isFakeLine: true 的对象,源码会分流到 FakeLine。
追加一份 RGJsonData
await graphInstance.appendJsonData(
{
nodes: [{ id: 'new-node', text: '新增节点' }],
lines: [{ id: 'root-new', from: 'root', to: 'new-node' }]
},
true
);
第二个参数 isRelayout:
true:追加后调用doLayout()。false:只追加数据,不自动重排,适合你自己设置x/y的场景。
4. 查询数据
基础查询
const options = graphInstance.getOptions();
const nodes = graphInstance.getNodes();
const lines = graphInstance.getLines();
const fakeLines = graphInstance.getFakeLines();
const links = graphInstance.getLinks();
const node = graphInstance.getNodeById('n1');
const line = graphInstance.getLineById('n1-n2');
const fakeLine = graphInstance.getFakeLineById('fake-1');
const link = graphInstance.getLinkByLineId('n1-n2');
选中与编辑状态查询
const checkedNode = graphInstance.getCheckedNode();
const checkedLine = graphInstance.getCheckedLine();
const selectedNodes = graphInstance.getSelectedNodes();
const editingNodes = graphInstance.getEditingNodes();
说明:
- checked 是当前焦点项,通常只有一个节点或一条线。
- selected 是对象上的字段,可用于多选。
- editingNodes 来自编辑控制器状态。
关系查询
const node = graphInstance.getNodeById('n1');
const relatedLines = graphInstance.getRelatedLinesByNode(node);
const relatedNodes = graphInstance.getNodeRelatedNodes(node);
const incomingNodes = graphInstance.getNodeIncomingNodes(node);
const outgoingNodes = graphInstance.getNodeOutgoingNodes(node);
const networkNodes = graphInstance.getNetworkNodesByNode(node);
const descendantNodes = graphInstance.getDescendantNodes(node);
方向过滤:
const onlyIncoming = graphInstance.getNodeRelatedNodes(node, {
incoming: true,
outgoing: false
});
节点集合之间的关系:
const linksBetween = graphInstance.getLinksBetweenNodes([nodeA, nodeB, nodeC]);
const linesBetween = graphInstance.getLinesBetweenNodes([nodeA, nodeB, nodeC]);
空间查询
const box = graphInstance.getNodesRectBox();
// { width, height, minX, minY, maxX, maxY }
const center = graphInstance.getNodesCenter();
// { x, y }
const nodesInSelection = graphInstance.getNodesInSelectionView(selectionView);
getNodesInSelectionView 通常配合 onCanvasSelectionEnd 使用。
5. 更新数据
更新节点
graphInstance.updateNode('n1', {
text: '新名称',
color: '#eff6ff',
borderColor: '#2563eb'
});
graphInstance.updateNodePosition('n1', 300, 160);
graphInstance.updateNodeData('n1', {
status: 'online'
});
说明:
updateNode修改节点属性。updateNodePosition专门修改坐标;当力导向布局正在运行时,会同步布局器中的节点位置缓存。updateNodeData合并node.data,适合业务字段更新。
更新普通连线
graphInstance.updateLine('n1-n2', {
text: '更新后的关系',
color: '#f97316',
lineWidth: 3
});
graphInstance.updateLineData('n1-n2', {
weight: 10
});
更新虚拟连线
graphInstance.updateFakeLine('fake-1', {
text: '新的虚拟线',
lineShape: RGLineShape.StandardOrthogonal
});
更新配置
graphInstance.updateOptions({
wheelEventAction: 'scroll',
dragEventAction: 'selection'
});
setOptions 和 updateOptions 都是运行时更新配置的方法。传入 layout 时,源码会与当前 options.layout 合并,并同步给当前布局器。
6. 删除与清空
删除节点
graphInstance.removeNodeById('n1');
graphInstance.removeNodesByIds(['n2', 'n3']);
graphInstance.removeNode(node);
graphInstance.removeNodes([nodeA, nodeB]);
删除普通连线
graphInstance.removeLineById('line-1');
graphInstance.removeLineByIds(['line-2', 'line-3']);
graphInstance.removeLine(line);
graphInstance.removeLines([lineA, lineB]);
删除虚拟连线
graphInstance.removeFakeLineById('fake-1');
graphInstance.removeFakeLine(fakeLine);
graphInstance.clearFakeLines();
清空图谱
graphInstance.clearGraph();
clearGraph() 会清空:
- 所有节点。
- 所有普通连线。
- 所有虚拟连线。
- 根节点。
- checked 状态。
- editing 节点/线状态。
7. 布局与视图控制
重新布局
await graphInstance.doLayout();
await graphInstance.doLayout('root-node-id');
await graphInstance.doLayout(rootNode);
doLayout 会:
- 根据当前
options.layout创建/使用布局器。 - 使用传入根节点,或当前 root,或第一个节点作为根。
- 调用
updateNodesVisibleProperty()。 - 非力导向布局中会短暂启用节点坐标动画。
- 根据
options.placeOtherGroup处理与主网络不连通的其他节点组。
视图移动与缩放
graphInstance.moveToCenter();
graphInstance.moveToCenter([nodeA, nodeB]);
graphInstance.zoomToFit();
graphInstance.zoomToFit([nodeA, nodeB]);
graphInstance.setZoom(120);
graphInstance.zoom(-10);
graphInstance.setCanvasCenter(0, 0);
说明:
moveToCenter只移动画布中心,不改变缩放。zoomToFit会移动并缩放,让目标内容尽量完整显示。setZoom的值是百分比,100表示 100%。- 缩放会被
minCanvasZoom/maxCanvasZoom限制。
8. 导出与转换
导出当前图数据
const jsonData = graphInstance.getGraphJsonData();
返回:
{
rootId: 'root',
nodes: [...],
lines: [...],
fakeLines: [...]
}
默认导出是相对紧凑的,会省略一些默认值和运行时字段。
转换运行时对象
const nodeJson = graphInstance.transRGNodeToJsonObject(node);
const lineJson = graphInstance.transRGLineToJsonObject(line);
const linkLineJson = graphInstance.transRGLinkToJsonObject(link);
如果需要包含已解析默认值:
const effectiveNodeJson = graphInstance.transRGNodeToJsonObject(node, {
mode: 'effective'
});
const effectiveLineJson = graphInstance.transRGLineToJsonObject(line, {
mode: 'effective'
});
9. 动态应用推荐流程
编辑器类应用
推荐:
- 初始化时
setJsonData()或applyInitialData()。 - 用户创建节点时
addNode()。 - 用户创建线时
addLines()或addFakeLines()。 - 属性面板修改时
updateNode()/updateLine()/updateFakeLine()。 - 只有在用户明确点击“自动布局”时调用
doLayout()。 - 保存时调用
getGraphJsonData()。
避免:
- 每次编辑都
setJsonData()。 - 直接替换
options响应式对象。 - 直接修改
RGNode/RGLine/RGLink后不走实例 API。
只读展示类应用
推荐:
applyInitialData()加载完整数据。- 用户筛选时更新
hidden或重新设置数据。 - 需要聚焦节点时
focusNodeById()、moveToCenter()、zoomToFit()。 - 需要导出时使用图像导出 API 或
getGraphJsonData()。
10. 常见坑位
直接改数组为什么界面没更新?
不要直接向 getNodes() 返回的数组 push 数据。使用 addNodes()、updateNode() 等实例 API,图谱才能同步索引、关系、可见性和渲染状态。
为什么 setJsonData() 后视图跳动?
setJsonData() 会清空并重新布局整图。编辑器场景应优先使用增量 API。
为什么追加节点后布局位置不准?
节点尺寸可能还没完成 DOM 测量。源码在新增节点后较短时间内调用 doLayout() 时会等待一段时间,但复杂自定义节点仍建议在节点尺寸稳定后再布局。
为什么隐藏节点后子节点或线状态不对?
如果你直接批量修改运行时对象字段,调用:
graphInstance.updateNodesVisibleProperty();
graphInstance.dataUpdated();
更推荐通过 updateNode() 修改,并让图谱自动刷新。