JavaScript is required

图谱状态管理(RGHooks / graphStoreMixin

RGHooks 用于读取 relation-graph 的运行时响应式状态。它适合构建工具栏、属性面板、编辑控制器、缩略信息、状态栏等 UI。

核心原则:

  • 图谱数据修改走实例 API,例如 updateNode()addLines()setOptions()
  • RGHooks 负责读取状态并驱动 UI。
  • Hooks 返回的大多数对象是运行时状态,不建议作为业务数据持久化。

1. 状态来源

relation-graph 内部维护:

  • RGOptionsFull:全局配置和运行时状态。
  • shouldRenderNodesshouldRenderLinesshouldRenderFakeLines:当前应该渲染的数据集合。
  • 编辑器状态:正在创建节点、正在创建线、正在编辑节点/线、正在框选等。

React、Vue3、Svelte 通过 RGHooks 读取这些状态;Vue2 通过 graphStoreMixin 读取。

所有使用 hooks/mixin 的组件都必须位于同一个 RGProvider<RelationGraph> 上下文中。需要特别注意:在 Vue/React/Svelte 中,组件的脚本逻辑不能消费“自己模板里即将渲染的子 <RelationGraph>”提供的上下文;应把使用 hooks 的工具栏、面板、控制器做成 <RelationGraph> 插槽内的子组件,或把整个图谱区域包在外层 RGProvider 中。

2. Hook 列表

Hook / 状态 返回内容 典型用途
useGraphInstance() RelationGraphInstance 调用图谱 API,增删改查、布局、缩放、导出。
useCreatingLine() RGCreatingLine 显示正在拖拽创建的线、控制连线提示 UI。
useCreatingNode() RGCreatingNode 显示正在创建/拖入的节点模板。
useEditingNodes() RGEditingNodes 显示节点编辑框、批量属性面板、尺寸控制器。
useEditingLine() RGEditingLine 显示连线编辑器、路径控制点、线文本编辑器。
useConnectingNode() RGConnectingNode 显示节点附近连接控制器或连线锚点 UI。
useViewInformation() RGViewInformation 显示缩放比例、画布偏移、全屏状态、视图尺寸。
useSelection() RGSelectionView & { show?: boolean } 显示框选区域或读取当前框选矩形。
useCheckedItem() RGCheckedItem 根据当前 checked 节点/线更新工具栏可用状态。
useGraphOptions() Ref<RGOptionsFull> Vue3 侧当前导出,用于读取完整 options/ref。React/Svelte 当前主要通过 useGraphStore() 或其他状态 hook 读取。
useGraphStore() 平台相关 store/context 高级场景直接读取底层 store。
useAutoUpdateView() 自动刷新辅助 React 中用于视图自动更新的辅助 hook。

注意:现有旧文档或示例中可能写 useSelectionView(),源码当前导出的名称是 useSelection()。如果你的当前安装包额外导出了别名,可以按包版本使用;写新代码建议优先以实际导出为准。

3. React 用法

import {
  RelationGraph,
  RGHooks,
  RGSlotOnView
} from '@relation-graph/react';

function GraphStatusBar() {
  const graphInstance = RGHooks.useGraphInstance();
  const checkedItem = RGHooks.useCheckedItem();
  const view = RGHooks.useViewInformation();

  const focusCheckedNode = () => {
    if (checkedItem.checkedNodeId) {
      graphInstance.focusNodeById(checkedItem.checkedNodeId);
    }
  };

  return (
    <div className="graph-status-bar">
      <span>{view.canvasZoom}%</span>
      <button disabled={!checkedItem.checkedNodeId} onClick={focusCheckedNode}>
        聚焦
      </button>
    </div>
  );
}

function MyGraph() {
  return (
    <RelationGraph options={graphOptions}>
      <RGSlotOnView>
        <GraphStatusBar />
      </RGSlotOnView>
    </RelationGraph>
  );
}

4. Vue3 用法

以下示例应写在 <RelationGraph> 下层组件中,例如放到 #view 插槽里的 GraphPanel.vue,或放到外层 RGProvider 包裹的子组件中。

<script setup lang="ts">
import { RGHooks } from '@relation-graph/vue';

const graphInstance = RGHooks.useGraphInstance();
const checkedItem = RGHooks.useCheckedItem();
const viewInformation = RGHooks.useViewInformation();
const editingNodes = RGHooks.useEditingNodes();

const removeCheckedNode = () => {
  if (checkedItem.value.checkedNodeId) {
    graphInstance.removeNodeById(checkedItem.value.checkedNodeId);
  }
};
</script>

<template>
  <div class="graph-panel">
    <span>{{ viewInformation.canvasZoom }}%</span>
    <button
      :disabled="!checkedItem.checkedNodeId"
      @click="removeCheckedNode"
    >
      删除节点
    </button>
    <span v-if="editingNodes.show">
      正在编辑 {{ editingNodes.nodes.length }} 个节点
    </span>
  </div>
</template>

当前源码中,Vue3 useGraphInstance() 直接返回图谱实例;useCheckedItem()useViewInformation()useEditingNodes() 等状态类 hook 返回 Ref/computed Ref。因此在 <script setup> 中读取状态要使用 .value,模板中通常会自动解包。

5. Svelte 用法

源码当前 Svelte 侧导出:

  • useGraphInstance()
  • useGraphStore()
  • useCreatingLine()
  • useCreatingNode()
  • useEditingNodes()
  • useEditingLine()
  • useViewInformation()
  • useSelection()
  • useConnectingNode()
  • useCheckedItem()

示意:

<script lang="ts">
  import { RGHooks } from '@relation-graph/svelte';

  const graphInstance = RGHooks.useGraphInstance();
  const checkedItem = RGHooks.useCheckedItem();
  const viewInformation = RGHooks.useViewInformation();

  $: zoomText = `${$viewInformation.canvasZoom}%`;

  function clearChecked() {
    graphInstance.clearChecked();
  }
</script>

<div class="graph-status">
  <span>{zoomText}</span>
  <button on:click={clearChecked} disabled={!$checkedItem.checkedNodeId && !$checkedItem.checkedLineId}>
    清除选中
  </button>
</div>

6. Vue2:graphStoreMixin

Vue2 不使用 Composition API hooks。源码提供 graphStoreMixin,在 RGProviderRelationGraph 下层组件中通过 this 读取状态。

可读取字段:

字段 说明
this.graphInstance 图谱实例。
this.shouldRenderNodes 当前渲染节点集合。
this.shouldRenderLines 当前渲染普通连线集合。
this.shouldRenderFakeLines 当前渲染虚拟连线集合。
this.creatingLine 正在创建的连线状态。
this.creatingNode 正在创建的节点状态。
this.editingNodes 正在编辑的节点控制器状态。
this.editingLine 正在编辑的连线控制器状态。
this.connectingNode 正在连接的节点/目标控制器状态。
this.viewInformation 当前视图状态。
this.selectionView 当前框选区域状态,包含 show
this.checkedItem 当前 checked 节点/线/拖拽节点 id。

示意:

import { graphStoreMixin } from '@relation-graph/vue2';

export default {
  mixins: [graphStoreMixin],
  computed: {
    canDelete() {
      return Boolean(this.checkedItem.checkedNodeId || this.checkedItem.checkedLineId);
    }
  },
  methods: {
    deleteChecked() {
      if (this.checkedItem.checkedNodeId) {
        this.graphInstance.removeNodeById(this.checkedItem.checkedNodeId);
      }
      if (this.checkedItem.checkedLineId) {
        this.graphInstance.removeLineById(this.checkedItem.checkedLineId);
      }
    }
  }
};

如果组件没有放在图谱上下文内,mixin 会抛出错误。

7. 状态对象字段说明

RGCreatingLine

表示当前是否正在创建一条线。

type RGCreatingLine =
  | {
      creating: true;
      fromTarget?: RGLineTarget;
      toTarget?: RGLineTarget;
      lineJson?: JsonLine;
    }
  | {
      creating: false;
    };

字段说明:

字段 说明
creating 是否正在拖拽/创建连线。
fromTarget 当前起点目标。可能是节点、连接点或画布点。
toTarget 当前终点目标。拖拽过程中可能是临时位置。
lineJson 当前待创建连线的模板数据。

适合:

  • 显示“正在连线”的提示。
  • 根据起点/终点类型限制 UI 操作。
  • 自定义连线创建过程中的预览。

不要:

  • 直接改 lineJson 来创建最终连线。创建完成应走事件回调或实例 API。

RGCreatingNode

表示当前是否正在创建节点。

type RGCreatingNode =
  | {
      creating: true;
      nodeJson?: JsonNode;
    }
  | {
      creating: false;
    };

字段说明:

字段 说明
creating 是否正在创建节点。
nodeJson 当前节点模板。

适合拖拽节点模板到画布的 UI。

RGEditingNodes

表示当前节点编辑控制器状态。

type RGEditingNodes = {
  show: boolean;
  nodes: RGNode[];
  x: number;
  y: number;
  width: number;
  height: number;
};

字段说明:

字段 说明
show 是否显示节点编辑控制器。
nodes 当前正在编辑的节点集合。
x/y 编辑框在视图坐标系中的位置。
width/height 编辑框在视图坐标系中的尺寸。

配套 API:

graphInstance.setEditingNodes([nodeA, nodeB]);
graphInstance.addEditingNode(nodeC);
graphInstance.removeEditingNode(nodeA);
graphInstance.toggleEditingNode(nodeB);
graphInstance.updateEditingControllerView();

RGEditingLine

表示当前连线编辑控制器状态。

type RGEditingLine = {
  show: boolean;
  line: RGLine | null;
  startPoint: RGPosition;
  endPoint: RGPosition;
  text: {
    show: boolean;
    x: number;
    y: number;
    width: number;
    height: number;
  };
  ctrlPoints: RGPosition[];
  selectedLines: string[];
  line44Splits: RGCtrlPointForLine44[];
  line49Points: RGPosition[];
  ctrlPoint1: RGPosition;
  ctrlPoint2: RGPosition;
  toolbar: RGPosition;
};

字段说明:

字段 说明
show 是否显示连线编辑控制器。
line 当前正在编辑的线。
startPoint/endPoint 线起止点在视图中的位置。
text 线文本编辑区域位置和尺寸。
ctrlPoints 通用控制点集合。
selectedLines 当前被编辑器选中的多条线 id。
line44Splits StandardOrthogonal 线的分段控制信息。
line49Points HardOrthogonal 线的控制点。
ctrlPoint1/ctrlPoint2 曲线控制点。
toolbar 连线编辑工具栏位置。

这是高级编辑器状态。普通展示类应用通常只需要读取 show/line

RGConnectingNode

表示当前节点连接控制器状态。

type RGConnectingNode = {
  show: boolean;
  node: RGNode | RGLineTarget | RGRectTarget;
  x: number;
  y: number;
  width: number;
  height: number;
};

适合节点附近的“添加关系”“拖出连线”悬浮控制器。

RGViewInformation

表示当前视图与画布变换状态。

type RGViewInformation = {
  viewSize: { width: number; height: number };
  fullscreen: boolean;
  canvasSize: { width: number; height: number };
  canvasZoom: number;
  canvasOffset: { x: number; y: number };
  showEasyView?: boolean;
};

字段说明:

字段 说明
viewSize 图谱视口尺寸。
fullscreen 是否处于全屏状态。
canvasSize 画布尺寸。
canvasZoom 缩放百分比,100 表示 100%。
canvasOffset 画布在视图中的偏移。
showEasyView 性能模式下是否切换到简化视图。

适合:

  • 状态栏显示缩放比例。
  • 控制缩放按钮 disabled 状态。
  • 自定义小地图或辅助坐标显示。

RGSelectionView

type RGSelectionView = {
  x: number;
  y: number;
  width: number;
  height: number;
};

Vue2 mixin 返回值会额外带 show,表示是否正在框选。

x/y/width/height 是视图坐标系下的框选矩形。需要转换到画布坐标时,可使用实例坐标转换 API,或直接调用:

const nodes = graphInstance.getNodesInSelectionView(selectionView);

RGCheckedItem

type RGCheckedItem = {
  checkedLineId?: string;
  checkedNodeId?: string;
  draggingNodeId?: string;
};

字段说明:

字段 说明
checkedNodeId 当前 checked 节点 id。
checkedLineId 当前 checked 普通线或 FakeLine id。
draggingNodeId 当前正在拖拽的节点 id。

配套 API:

graphInstance.setCheckedNode('node-a');
graphInstance.setCheckedLine('line-a-b');
graphInstance.clearChecked();

8. Hooks 与实例 API 的分工

推荐写法是用 hooks 读取状态,用实例 API 改变图谱。React 中 hooks 返回普通对象,写法如下:

const checkedItem = RGHooks.useCheckedItem();
const graphInstance = RGHooks.useGraphInstance();

function setNodeWarning() {
  if (!checkedItem.checkedNodeId) return;

  graphInstance.updateNode(checkedItem.checkedNodeId, {
    color: '#fef3c7',
    borderColor: '#f59e0b'
  });
}

Vue3 <script setup> 中状态类 hook 返回 Ref,应写作:

const checkedItem = RGHooks.useCheckedItem();
const graphInstance = RGHooks.useGraphInstance();

function setNodeWarning() {
  const nodeId = checkedItem.value.checkedNodeId;
  if (!nodeId) return;

  graphInstance.updateNode(nodeId, {
    color: '#fef3c7',
    borderColor: '#f59e0b'
  });
}

不要把 hooks 状态当作唯一数据源:

// 不推荐:直接改 hooks 返回对象
checkedItem.checkedNodeId = 'node-a';

正确方式:

graphInstance.setCheckedNode('node-a');

9. 常见 UI 场景

工具栏按钮可用性

React 写法:

const checkedItem = RGHooks.useCheckedItem();

const canDelete = Boolean(
  checkedItem.checkedNodeId || checkedItem.checkedLineId
);

Vue3 <script setup> 中应写作:

import { computed } from 'vue';

const checkedItem = RGHooks.useCheckedItem();

const canDelete = computed(() => {
  return Boolean(
    checkedItem.value.checkedNodeId || checkedItem.value.checkedLineId
  );
});

节点属性面板

React 写法:

const editingNodes = RGHooks.useEditingNodes();

if (editingNodes.show && editingNodes.nodes.length === 1) {
  const node = editingNodes.nodes[0];
  // 显示单节点属性
}

Vue3 <script setup> 中应读取 editingNodes.value.showeditingNodes.value.nodes;模板中可直接写 editingNodes.show

缩放状态栏

React 写法:

const view = RGHooks.useViewInformation();

const zoomText = `${view.canvasZoom}%`;

Vue3 <script setup> 中应写作 view.value.canvasZoom;模板中可直接写 view.canvasZoom

框选后批量编辑

const onCanvasSelectionEnd = (selectionView) => {
  const nodes = graphInstance.getNodesInSelectionView(selectionView);
  graphInstance.setEditingNodes(nodes);
};

10. 常见问题

Hook 报错找不到图谱实例?

组件必须放在 RGProvider<RelationGraph> 上下文内部。对于 React,通常把工具栏放进 RGSlotOnView;对于 Vue/Svelte,放进对应 #view/slot 范围内。

为什么 hooks 状态变化了,但我的业务 store 没同步?

Hooks 是图谱运行时状态。业务 store 应通过事件、实例 API 调用结果或你自己的同步逻辑更新,不建议直接把 hooks 状态作为业务持久化状态。

为什么 selectionView 是视图坐标,不是画布坐标?

框选是用户在屏幕视口中拖出来的矩形,所以它天然是视图坐标。需要匹配节点时使用 getNodesInSelectionView()

React/Vue/Svelte 的返回值形态为什么不完全一样?

不同框架的响应式机制不同。React hook 通常返回当前值并触发组件重渲染;Vue 可能返回响应式对象/ref;Svelte 常见为 store。以当前包导出的类型和示例为准,但字段语义一致。

11. 下一步阅读