思维导图关系数据开发——组件relation-graph
市面上常见的思维导图库 jsMind、GoJS、MindMup等。
以下介绍的是在vue3中使用的relation-graph
使用:
npm install --save relation-graph-vue3 //
注意:使用时import的方式与vue2不一样:import RelationGraph from
‘relation-graph-vue3’
relation-graph
官方链接: https://www.relation-graph.com/#/docs/graph
relation-graph是支持Vue2、Vue3、React的关系数据展示组件,支持通过【插槽】让使用者使用"普通HTML元素、Vue组件、React组件"来完全自定义图形元素,并提供实用的API接口让使用者轻松构建可交互的图形应用。
非会员功能——官方提供展示功能、双击编辑功能。
<RelationGraph
v-show="treeVisible"
ref="graphRefTree"
:options="graphOptionsTree"
@node-click="onTreeNodeClick"
@line-click="onTreeLineClick"
:onContextmenu=“onTreeContextmenu”
>
<template #node="{ node }">
<addNodeDialog
v-if="isEditLink"
:nodeData="node"
:rootId="rootIdTree"
:graph-instance="graphInstanceTree"
:enable-editing-mode="true"
@on-node-text-change="onNodeTextChange"
/>
</template>
</RelationGraph>
设置图谱的选项和事件:
运行时属性
: options:
图谱会根据options生成一个包含默认值的完整配置对象,它就是options,
可以通过this.$refs.graphRef.getInstance().options来获取;
你还可以从这个对象中获取当前图谱的可见区域大小、画布大小、画布偏移量、当前布局器等运行时对象。
节点点击事件
@node-click=“onTreeNodeClick” (nodeObject, $event)
连线点击事件
@line-click=“onTreeLineClick” (nodeObject, $event)
点击下载图片按钮
时触发 onImageDownload (cavansDom, $event)
可通过此事件接收到一个图谱的dom对象,你可以根据此对象来生成图片,如果要阻止默认的下载动作,此方法需要返回false。注意:请使用<graph :on-image-download=functionName />
的方式来绑定关系线事件。
在图谱中点击右键
:onContextmenu=“onTreeContextmenu” ($event, objectType:canvas|node|link|line, object:Node|Link|Line|undefined)
右键可分别返回canvas、node、link 右键点击类型及节点与连线对象。
点击画布
onCanvasClick ($event)
节点拖动结束时
onNodeDragEnd (nodeObject, $event)
画布拖动结束时
onCanvasDragEnd ($event)
节点展开收缩事件
,。。。
插槽:
node:节点插槽
<template #node="{node}">
xxx
</template>
line:自定义连线内容的插槽
<MyLine
slot="line"
/>
graph-plug: 自定义图谱区域内容的插槽
,通过此功能可以在图谱中显示一些内容,比如筛选区域,图例说明等。
<template #graph-plug>
xxx
</template>
或者
<div slot="graph-plug" ></div>
canvas-plug:自定义图谱画布区域内容的插槽
,通过此功能可以在画布中显示一些内容,这些内容会和节点、线条一样可以被缩放、根据画布被拖动。
示例:
<RelationGraph
ref="graphRef"
:options="graphOptions">
<MyLine
v-if="line.isHide === false"
slot="line"
slot-scope="{line, link, relationGraph}"
:line="line"
:link="link"
:relation-graph="relationGraph"
/>
<template #graph-plug>
<div class="c-my-panel">
<div>
以下图谱中的线条以及线条上的花,是通过连线插槽slot=line实现的。注意:node插槽/画布插槽/图谱插槽都可以使用常规方式来编写,但连线插槽必须通过svg来编写。
</div>
</div>
</template>
</RelationGraph>
方法:
https://www.relation-graph.com/#/docs/graph
getInstance() : 获取图谱实例,通过this.$refs.graphRef.getInstance()来获取。
setOptions(options, callback) : 设置/重新设置图谱的选项,options。更改设置:this.$refs.graphRef.setOptions(newOptions, callback);
setJsonData(jsonData, callback): 设置/重新设置图谱中的数据,jsonData。
this.$refs.graphRef.setJsonData(jsonData, callback)方法是一个方便使用的方法,它和以下代码等效:
const graphInstance = this.$refs.graphRef.getInstance();
graphInstance.addNodes(jsonData.nodes);
graphInstance.addLines(jsonData.lines);
graphInstance.rootNode = graphInstance.getNodeById(jsonData.rootId);
await graphInstance.doLayout(); // 使用graphOptions中设置的布局器布局
await graphInstance.moveToCenter(); // 根据节点分布找到画面中心并居中
await graphInstance.zoomToFit(); // 缩放到合适大小,以让所有节点可以在可见区域展示
await callback();
appendJsonData(jsonData, callback):向图谱中追加数据,jsonData。
this.$refs.graphRef.appendJsonData(jsonData, callback)方法是一个方便使用的方法,它和以下代码等效:
const graphInstance = this.$refs.graphRef.getInstance();
graphInstance.addNodes(jsonData.nodes);
graphInstance.addLines(jsonData.lines);
await graphInstance.doLayout(); // 使用graphOptions中设置的布局器布局
await callback();
refresh(): 刷新布局,你可以通过getNodes()获取当前图谱中的节点,并通过节点的isHide属性隐藏一些节点,再调用refresh()方法可以根据依然显示的节点重新布局图谱;或者在动态向图谱中添加数据候刷新布局;当你的图片默认默认状态是不可见的时,在切换到可见状态下后可能会显示不正常,这时你调用一下refresh()方法可以让图片正确显示。
总之一句话:当图谱中的节点看起来不正常时,你都可以调用refresh方法来让布局器重新为节点分配位置。
getNodeById(nodeId): 根据节点id获取节点对象。
removeNodeById(nodeId):移除指定id对应的节点,彻底移除,移除element和数据对象。
删除连线扩展方法待验证 deleteLine: (line: Line) => {
store.dispatch(“asyncDeleteLine”, line).then(() => {
// relationGraphView?.value?.getInstance()?.removeLinkById(line.from, line.to);
});
},
getGraphJsonData(): 获取当前图谱的节点、关系数据的json数据。
getGraphJsonOptions(): 获取当前图谱的完整的配置信息。
getNodes():获取图谱中所有的节点对象,可以直接修改该对象的属性,这些对象不能用于JSON序列化。
getLinks(): 获取图谱中所有的关系对象,这些对象不能用于JSON序列化。
所有【线条】可以通过以下代码获取:
const links = this.$refs.graphRef.getLinks();
const lines = links.reduce((currentLines, link) => currentLines.concat(...link.relations), []);
downloadAsImage(format, fileName):下载图片。调用:this.$refs.graphRef.getInstance().downloadAsImage(a,b)
dataUpdated(): 有时候在更改数据后视图并没有同步(比如直接修改对象属性更改了线条color属性值后,但图谱上的线条颜色没有变化时),可以调用此方法。(relationGraph.value?.updateView();待验证)
focusNodeById(nodeId):根据节点id在图谱中选中该节点并居中。
。。。
节点实例方法------
const clickTestButton = () => {
const setGraphInstance = treeVisible.value
? graphRefTree.value?.getInstance()
: graphRef.value?.getInstance();
if (setGraphInstance) {
setGraphInstance.refresh(); // 刷新画布
setGraphInstance.doLayout(); // 重新布局(节点位置)
setGraphInstance.zoomToFit(); // 缩放至适应画布(大小适应)
setGraphInstance.moveToCenter();
}
};
扩展:节点添加
graphInstance.addNodes([ nodeObj ]);
graphInstance.addLines([ lineObj
]); await graphInstance.doLayout(); // 使用graphOptions中设置的布局器布局
// 确认添加弹窗
const confirmHandel = async () => {
let addId = 0;
await getRelationAddId()
.then((res) => {
addId = res.retdata || 0;
})
.catch((error) => {
ElMessage({
message: `${error}`,
type: 'error',
});
});
if (!addId) {
ElMessage({
message: `id异常`,
type: 'error',
});
addNodeVisible.value = false;
resetForm();
return;
}
if (props.nodeData && addId) {
const graphInstance = props.graphInstance;
if (graphInstance) {
const newNode = {
id: String(addId), // uuidv4()
text: formData.name || '添加节点',
x: props.nodeData.x + 100,
y: props.nodeData.y,
};
graphInstance.addNodes([newNode]);
graphInstance.addLines([
{
from: props.nodeData.id,
to: newNode.id,
text: formData.line || 'line',
},
]);
await graphInstance.doLayout();
addNodeVisible.value = false;
resetForm();
}
}
};
扩展:节点删除
graphInstance.removeNode(item);
// 删除节点
const deleteNode = async () => {
console.log('删除节点:', props.nodeData);
console.log('关系实例', props.graphInstance);
if (props.nodeData) {
const graphInstance = props.graphInstance;
if (graphInstance) {
// ---深度循环删除节点
let arr = [];
const removeDeepNode = (obj, isRoot = false) => {
let length = obj.targetTo.length;
if (isRoot) {
arr.push(obj);
}
if (obj.targetTo && length > 0) {
for (let i = 0; i < length; i++) {
if (obj.targetTo[i].id != graphInstance?.graphData?.rootNode?.id) {
arr.push(obj.targetTo[i]);
removeDeepNode(obj.targetTo[i]);
}
}
}
return arr;
};
let removeArr = await removeDeepNode(props.nodeData, true);
removeArr.forEach((item) => {
graphInstance.removeNode(item);
});
}
}
};
扩展:连线编辑
直接更改对象属性text值
const onLineClick = (lineObject, linkObject, $event) => {
if (props.isEditLink) {
lineObj.value = lineObject;
lineText.value = lineObject.text || '';
editLineVisible.value = true;
}
};