图谱状态管理(RGHooks / graphStoreMixin)
RGHooks 用于读取 relation-graph 的运行时响应式状态。它适合构建工具栏、属性面板、编辑控制器、缩略信息、状态栏等 UI。
核心原则:
- 图谱数据修改走实例 API,例如
updateNode()、addLines()、setOptions()。 RGHooks负责读取状态并驱动 UI。- Hooks 返回的大多数对象是运行时状态,不建议作为业务数据持久化。
1. 状态来源
relation-graph 内部维护:
RGOptionsFull:全局配置和运行时状态。shouldRenderNodes、shouldRenderLines、shouldRenderFakeLines:当前应该渲染的数据集合。- 编辑器状态:正在创建节点、正在创建线、正在编辑节点/线、正在框选等。
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,在 RGProvider 或 RelationGraph 下层组件中通过 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.show 和 editingNodes.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。以当前包导出的类型和示例为准,但字段语义一致。