JavaScript is required

图谱数据流转与修改(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: []
});

实际流程:

  1. 清空当前图谱数据。
  2. 加载 nodeslinesfakeLines
  3. 将树形 children 展开为扁平节点和线。
  4. 设置 rootId
  5. 调用 doLayout(rootId)

适合:

  • 首次加载完整图谱。
  • 切换到另一份完整数据。
  • 希望按当前 options.layout 自动重新布局。

不适合:

  • 高频编辑器操作。
  • 用户正在拖拽、缩放或局部编辑时反复刷新整图。
  • 只想改一两个字段的局部更新。

方式 B:applyInitialData 初始化并自动适配视图

await graphInstance.applyInitialData(data);

实际流程:

  1. 调用 setJsonData(data)
  2. 调用 moveToCenter()
  3. 调用 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 元素或自定义对象。

历史兼容:

  • relationslinksedges 会被兼容为 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,源码可能兼容使用 labelid,但新代码应显式使用 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'
});

setOptionsupdateOptions 都是运行时更新配置的方法。传入 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. 动态应用推荐流程

编辑器类应用

推荐:

  1. 初始化时 setJsonData()applyInitialData()
  2. 用户创建节点时 addNode()
  3. 用户创建线时 addLines()addFakeLines()
  4. 属性面板修改时 updateNode() / updateLine() / updateFakeLine()
  5. 只有在用户明确点击“自动布局”时调用 doLayout()
  6. 保存时调用 getGraphJsonData()

避免:

  • 每次编辑都 setJsonData()
  • 直接替换 options 响应式对象。
  • 直接修改 RGNode/RGLine/RGLink 后不走实例 API。

只读展示类应用

推荐:

  1. applyInitialData() 加载完整数据。
  2. 用户筛选时更新 hidden 或重新设置数据。
  3. 需要聚焦节点时 focusNodeById()moveToCenter()zoomToFit()
  4. 需要导出时使用图像导出 API 或 getGraphJsonData()

10. 常见坑位

直接改数组为什么界面没更新?

不要直接向 getNodes() 返回的数组 push 数据。使用 addNodes()updateNode() 等实例 API,图谱才能同步索引、关系、可见性和渲染状态。

为什么 setJsonData() 后视图跳动?

setJsonData() 会清空并重新布局整图。编辑器场景应优先使用增量 API。

为什么追加节点后布局位置不准?

节点尺寸可能还没完成 DOM 测量。源码在新增节点后较短时间内调用 doLayout() 时会等待一段时间,但复杂自定义节点仍建议在节点尺寸稳定后再布局。

为什么隐藏节点后子节点或线状态不对?

如果你直接批量修改运行时对象字段,调用:

graphInstance.updateNodesVisibleProperty();
graphInstance.dataUpdated();

更推荐通过 updateNode() 修改,并让图谱自动刷新。

11. 下一步阅读