推荐系统初始化
This commit is contained in:
145
web/src/components/graph/Graph.vue
Normal file
145
web/src/components/graph/Graph.vue
Normal file
@@ -0,0 +1,145 @@
|
||||
<template>
|
||||
<div :id="id" style="width: 100%; height: 100%"></div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request';
|
||||
import {config} from '@/components/graph/config';
|
||||
import G6 from '@antv/g6';
|
||||
|
||||
var graphObj = {};
|
||||
|
||||
/**
|
||||
* 格式约定
|
||||
* node{
|
||||
* id,
|
||||
* name,
|
||||
* label,
|
||||
* }
|
||||
* edge{
|
||||
* source,
|
||||
* target,
|
||||
* name,
|
||||
* label,
|
||||
* }
|
||||
*/
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
id: "graph" + Math.round(Math.random() * 10000000).toString(),// 随机ID的目的是为了一个页面同时存在多个画布
|
||||
graph: null,
|
||||
graphData: null
|
||||
}
|
||||
},
|
||||
created() {
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
initGraph: function (cmd, flag, clickFun, ifNotDestroy) {
|
||||
let graph = graphObj[this.id];
|
||||
this.$nextTick(() => {
|
||||
if (!ifNotDestroy) {
|
||||
this.destroyGraph();
|
||||
}
|
||||
|
||||
let _width = document.getElementById(this.id).clientWidth;
|
||||
let _height = document.getElementById(this.id).clientHeight;
|
||||
// 创建 G6 图实例
|
||||
if (graph == undefined || graph == null) {
|
||||
graph = new G6.Graph(config({width: _width, height: _height, id: this.id}));
|
||||
|
||||
graph.on('node:click', (e) => {
|
||||
let node = e.item._cfg.model;
|
||||
if (clickFun) {
|
||||
clickFun(node);
|
||||
}
|
||||
});
|
||||
|
||||
graphObj[this.id] = graph;
|
||||
|
||||
}
|
||||
if (flag) {
|
||||
this.importData(cmd);
|
||||
} else {
|
||||
this.queryData(cmd);
|
||||
}
|
||||
});
|
||||
},
|
||||
importData({nodes, edges}) { // 查询图空间内节点关系
|
||||
let graph = graphObj[this.id];
|
||||
this.graphData = {
|
||||
nodes: nodes,
|
||||
edges: edges
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(this.graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
},
|
||||
queryData({path, qo}) { // 查询图空间内节点关系
|
||||
let graph = graphObj[this.id];
|
||||
request({
|
||||
url: path,
|
||||
method: 'post',
|
||||
data: qo
|
||||
}).then(res => {
|
||||
// 处理节点的属性,要适应组件的要求
|
||||
res.data.nodes.forEach(node => {
|
||||
node.id = node.vid;
|
||||
node.label = node.properties.labelName;
|
||||
node.name = node.properties.name;
|
||||
});
|
||||
res.data.relations.forEach(edge => {
|
||||
edge.source = edge.srcId;
|
||||
edge.target = edge.dstId;
|
||||
edge.name = edge.properties.name;
|
||||
edge.label = edge.properties.labelName;
|
||||
if (edge.srcId == edge.dstId) {
|
||||
edge.type = 'loop';
|
||||
}
|
||||
});
|
||||
this.graphData = {
|
||||
nodes: res.data.nodes,
|
||||
edges: res.data.relations
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(this.graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
});
|
||||
},
|
||||
destroyGraph() {
|
||||
let graph = graphObj[this.id];
|
||||
try {
|
||||
if (graph != undefined && graph != null) {
|
||||
graph.clear();
|
||||
graph = null;
|
||||
document.getElementById(this.id).innerHTML = "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDragedNodePosition(e) {
|
||||
const model = e.item.get('model');
|
||||
model.fx = e.x;
|
||||
model.fy = e.y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.g6-tooltip {
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #545454;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px 8px;
|
||||
box-shadow: rgb(174 174 174) 0px 0px 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
314
web/src/components/graph/Graph614.vue
Normal file
314
web/src/components/graph/Graph614.vue
Normal file
@@ -0,0 +1,314 @@
|
||||
<template>
|
||||
<div class="graph-main">
|
||||
<div class="legend-box" v-show="!legendHide">
|
||||
<div class="item-row" v-for="(value, key) in legendList" :key="key">
|
||||
<div class="point" :style="'background-color:' + value.color"></div>
|
||||
<div class="label">{{ value.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div :id="id" style="width: 100%; height: 100%"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request';
|
||||
import G6 from '@antv/g6';
|
||||
|
||||
var graphObj = {};
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
ontologyId: '',
|
||||
id: Math.round(Math.random() * 10000000).toString(),
|
||||
graph: null,
|
||||
graphData: null,
|
||||
legendHide: false,
|
||||
legendList: {
|
||||
"armyType": {label: '军种', color: 'rgb(76,142,218)'},
|
||||
"control": {label: '控制系统', color: 'rgb(236,181,201)'},
|
||||
"engine": {label: '发动机', color: 'rgb(241,102,103)'},
|
||||
"fault": {label: '故障信息', color: 'rgb(141,204,147)'},
|
||||
"faultCase": {label: '故障案例', color: 'rgb(156,33,234)'},
|
||||
"oriPlane": {label: '原飞机', color: 'rgb(255,196,84)'},
|
||||
"part": {label: '部件', color: 'rgb(86,148,128)'},
|
||||
"place": {label: '发生地', color: 'rgb(217,200,174)'},
|
||||
"plane": {label: '飞机', color: 'rgb(87,199,227)'}
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.ontologyId = this.$route.query.id;
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
methods: {
|
||||
initGraph: function (cmd, flag, clickNodeFun, legendHide) { // 是否隐藏图例
|
||||
if(legendHide) {
|
||||
this.legendHide = true;
|
||||
}
|
||||
let graph = graphObj[this.id];
|
||||
this.$nextTick(() => {
|
||||
this.destroyGraph();
|
||||
let _width = document.getElementById(this.id).clientWidth;
|
||||
let _height = document.getElementById(this.id).clientHeight;
|
||||
// 创建 G6 图实例
|
||||
if (graph == undefined || graph == null) {
|
||||
graph = new G6.Graph({
|
||||
container: this.id, // 指定图画布的容器 id,与第 9 行的容器对应
|
||||
// 画布宽高
|
||||
width: _width,
|
||||
height: _height,
|
||||
layout: {
|
||||
type: 'force',
|
||||
nodeStrength: -0,
|
||||
nodeSize: 80,
|
||||
preventOverlap: true,
|
||||
edgeStrength: 0.1
|
||||
},
|
||||
modes: {
|
||||
// Defualt mode
|
||||
default: ['drag-node',
|
||||
{
|
||||
type: 'drag-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'zoom-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'tooltip',
|
||||
formatText: function formatText(model) {
|
||||
let name = '';
|
||||
let argumentsStr = '';
|
||||
if (model.label) name = model.label + '<br/>';
|
||||
if (model.arguments && model.arguments.length > 0) {
|
||||
model.arguments.forEach(arg => {
|
||||
argumentsStr += arg.role + ":" + arg.argument + '<br/>';
|
||||
});
|
||||
}
|
||||
const text = `${name} ${argumentsStr}`;
|
||||
return text;
|
||||
},
|
||||
|
||||
shouldUpdate: function shouldUpdate() {
|
||||
return true;
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
defaultNode: {
|
||||
size: 20,
|
||||
color: '#4b4b4b',
|
||||
label: 'name',
|
||||
style: {
|
||||
lineWidth: 2,
|
||||
fill: '#68BDF6'
|
||||
},
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
style: {
|
||||
fontSize: 14,
|
||||
fill: '#454545'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultEdge: {
|
||||
style: {
|
||||
stroke: '#b3b3b3',
|
||||
lineAppendWidth: 10, // Enlarge the range the edge to be hitted
|
||||
},
|
||||
labelCfg: {
|
||||
autoRotate: false,
|
||||
style: {
|
||||
fontSize: 14,
|
||||
fill: '#848484'
|
||||
}
|
||||
},
|
||||
},
|
||||
nodeStateStyles: {
|
||||
yourStateName: {
|
||||
stroke: '#f00',
|
||||
lineWidth: 3,
|
||||
},
|
||||
},
|
||||
edgeStateStyles: {
|
||||
yourStateName: {
|
||||
stroke: '#f00',
|
||||
lineWidth: 3,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
/*拖动节点-start*/
|
||||
const forceLayout = graph.get('layoutController').layoutMethod;
|
||||
graph.on('node:dragstart', function (e) {
|
||||
graph.layout();
|
||||
refreshDragedNodePosition(e);
|
||||
});
|
||||
graph.on('node:drag', function (e) {
|
||||
if (forceLayout) {
|
||||
forceLayout.execute();
|
||||
}
|
||||
refreshDragedNodePosition(e);
|
||||
});
|
||||
graph.on('node:dragend', function (e) {
|
||||
e.item.get('model').fx = null;
|
||||
e.item.get('model').fy = null;
|
||||
});
|
||||
/*拖动节点-end*/
|
||||
|
||||
graph.on('node:click', (e) => {
|
||||
let node = e.item._cfg.model;
|
||||
if (clickNodeFun) {
|
||||
clickNodeFun(node);
|
||||
}
|
||||
this.clearStates();
|
||||
graph.setItemState(e.item, 'yourStateName', true);
|
||||
});
|
||||
|
||||
graph.on('canvas:click', (e) => {
|
||||
this.clearStates();
|
||||
});
|
||||
|
||||
graphObj[this.id] = graph;
|
||||
|
||||
}
|
||||
if (flag) {
|
||||
this.importData(cmd);
|
||||
} else {
|
||||
this.queryData(cmd);
|
||||
}
|
||||
});
|
||||
},
|
||||
importData({nodes, edges}) { // 查询图空间内节点关系
|
||||
let graph = graphObj[this.id];
|
||||
// 更新颜色数据
|
||||
nodes.forEach(node => {
|
||||
node.style = {
|
||||
lineWidth: 2,
|
||||
fill: this.legendList[node._label].color || "#68BDF6"
|
||||
};
|
||||
});
|
||||
|
||||
this.graphData = {
|
||||
nodes: nodes,
|
||||
edges: edges
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(this.graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
},
|
||||
queryData({path, qo}) { // 查询图空间内节点关系
|
||||
let graph = graphObj[this.id];
|
||||
request({
|
||||
url: path,
|
||||
method: 'post',
|
||||
data: qo
|
||||
}).then(res => {
|
||||
res.data.nodes.forEach(node => {
|
||||
node.label = node.properties.name;
|
||||
node.id = node.id.toString();
|
||||
});
|
||||
res.data.relations.forEach(edge => {
|
||||
delete edge.id;
|
||||
edge.source = edge.source.toString();
|
||||
edge.target = edge.target.toString();
|
||||
if (edge.source === edge.target) {
|
||||
edge.type = 'loop';
|
||||
} else {
|
||||
delete edge.type;
|
||||
}
|
||||
});
|
||||
this.importData({
|
||||
nodes: res.data.nodes,
|
||||
edges: res.data.relations
|
||||
});
|
||||
// this.graphData = {
|
||||
// nodes: res.data.nodes,
|
||||
// edges: res.data.relations
|
||||
// };
|
||||
// // 读取数据
|
||||
// graph.data(this.graphData);
|
||||
// // 渲染图
|
||||
// graph.render();
|
||||
});
|
||||
},
|
||||
clearStates() { // 清空选中状态
|
||||
let graph = graphObj[this.id];
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
},
|
||||
destroyGraph() {
|
||||
let graph = graphObj[this.id];
|
||||
try {
|
||||
if (graph != undefined && graph != null) {
|
||||
graph.clear();
|
||||
graph = null;
|
||||
document.getElementById("mountNode").innerHTML = "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDragedNodePosition(e) {
|
||||
const model = e.item.get('model');
|
||||
model.fx = e.x;
|
||||
model.fy = e.y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.g6-tooltip {
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 4px;
|
||||
font-size: 12px;
|
||||
color: #545454;
|
||||
background-color: rgba(255, 255, 255, 0.9);
|
||||
padding: 10px 8px;
|
||||
box-shadow: rgb(174 174 174) 0px 0px 10px;
|
||||
}
|
||||
|
||||
.graph-main {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.legend-box {
|
||||
position: absolute;
|
||||
|
||||
top: 10px;
|
||||
left: 10px;
|
||||
}
|
||||
|
||||
.item-row {
|
||||
float: left;
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.item-row .point {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 40px;
|
||||
border: 2px solid #4b4b4b;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.item-row .label {
|
||||
line-height: 20px;
|
||||
height: 20px;
|
||||
float: right;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
</style>
|
||||
1105
web/src/components/graph/GraphModel.vue
Normal file
1105
web/src/components/graph/GraphModel.vue
Normal file
File diff suppressed because it is too large
Load Diff
782
web/src/components/graph/GraphModel20220927.vue
Normal file
782
web/src/components/graph/GraphModel20220927.vue
Normal file
@@ -0,0 +1,782 @@
|
||||
<template>
|
||||
<div class="node-box" style="position: relative">
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="infoVisible" class="graph-info-box">
|
||||
<div class="graph-info-child">
|
||||
<div v-show="selectObj.type == 'edge'">
|
||||
<noumenon-edge-modify ref="noumenonEdgeModify"></noumenon-edge-modify>
|
||||
</div>
|
||||
<div v-show="selectObj.type == 'node'">
|
||||
<noumenon-modify ref="noumenonModify"></noumenon-modify>
|
||||
</div>
|
||||
<i class="el-icon-d-arrow-left" @click="infoVisible = false"></i>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="treeInfoVisible" class="tree-graph-info">
|
||||
<div class="graph-info-child">
|
||||
<tree-graph-info ref="treeGraphInfo"></tree-graph-info>
|
||||
<i class="el-icon-d-arrow-right" @click="treeInfoVisible = false"></i>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 为图谱准备一个具备大小(宽高)的Dom -->
|
||||
<div class="tree-container">
|
||||
<div class="graph-name-box">
|
||||
<label>本体名称: {{ graphName }};</label>
|
||||
<label>节点数量:{{ nodeCount }};</label>
|
||||
<label>关系属性数量:{{ edgeCount }};</label>
|
||||
<label>值属性数量:{{ valueCount }};</label>
|
||||
</div>
|
||||
<!-- <div class="tips-box">-->
|
||||
<!-- <label></label>-->
|
||||
<!-- </div>-->
|
||||
<div class="icon-box">
|
||||
<div class="icon icon-tree" @click="showTreeCom">
|
||||
<i title="树形图谱"></i>
|
||||
</div>
|
||||
<div class="icon icon-add" @click="showAddNodeDialog">
|
||||
<i title="新增概念"></i>
|
||||
</div>
|
||||
<div title="按住shift点选两节点可新增关系" :class="['icon icon-arrow', createEdgeModelFlag ? 'active' : '']" @click="changeCreateEdgeModel">
|
||||
<i title="按住shift点选两节点可新增关系"></i>
|
||||
</div>
|
||||
<div class="icon icon-remove" @click="removeNode">
|
||||
<i title="删除"></i>
|
||||
</div>
|
||||
<div class="icon icon-save" @click="backGraph">
|
||||
<i title="返回"></i>
|
||||
</div>
|
||||
</div>
|
||||
<div id="mountNode" style="width: 100%; height: 1000px"></div>
|
||||
</div>
|
||||
<el-dialog
|
||||
title="新增概念"
|
||||
:visible.sync="addNodeDialogVisible"
|
||||
width="40%">
|
||||
<el-form ref="addNodeForm" label-width="80px" :rules="addNodeRules" :model="addNodeCmd">
|
||||
<el-form-item label="概念名" prop="tagName">
|
||||
<el-input v-model="addNodeCmd.tagName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="概念描述">
|
||||
<el-input v-model="addNodeCmd.label"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button @click="addNodeDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitAddNode">确 定</el-button>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="新增关系"
|
||||
:visible.sync="addEdgeDialogVisible"
|
||||
@close="handleAddEdgeDialogClose"
|
||||
width="40%">
|
||||
<el-form ref="addEdgeForm" :model="addEdgeCmd" label-width="100px" :rules="addEdgeRules">
|
||||
<el-form-item label="关系名称" prop="edgeName">
|
||||
<el-input v-model="addEdgeCmd.edgeName"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="关系描述">
|
||||
<el-input v-model="addEdgeCmd.label"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="是否显示箭头">
|
||||
<el-radio v-model="addEdgeCmd.arrow" :label="true">是</el-radio>
|
||||
<el-radio v-model="addEdgeCmd.arrow" :label="false">否</el-radio>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button @click="addEdgeDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitAddEdge">确 定</el-button>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request';
|
||||
import {config} from '@/components/graph/config';
|
||||
import G6 from '@antv/g6';
|
||||
import noumenonModify from '@/components/dialog/NoumenonModify';
|
||||
import noumenonEdgeModify from '@/components/dialog/NoumenonEdgeModify';
|
||||
import treeGraphInfo from '@/components/dialog/TreeGraphInfo';
|
||||
|
||||
|
||||
G6.registerBehavior('click-add-edge', {
|
||||
// Set the events and the corresponding responsing function for this behavior
|
||||
getEvents() {
|
||||
return {
|
||||
'node:click': 'onClick', // The event is canvas:click, the responsing function is onClick
|
||||
mousemove: 'onMousemove', // The event is mousemove, the responsing function is onMousemove
|
||||
'edge:click': 'onEdgeClick',
|
||||
};
|
||||
},
|
||||
// The responsing function for node:click defined in getEvents
|
||||
onClick(ev) {
|
||||
const self = this;
|
||||
const node = ev.item;
|
||||
const graph = self.graph;
|
||||
// The position where the mouse clicks
|
||||
const point = {x: ev.x, y: ev.y};
|
||||
const model = node.getModel();
|
||||
if (self.addingEdge && self.edge) {
|
||||
graph.updateItem(self.edge, {
|
||||
target: model.id,
|
||||
});
|
||||
// 打开编辑新建关系的信息弹窗
|
||||
vm.showAddEdge(self.edge._cfg.model);
|
||||
self.edge = null;
|
||||
self.addingEdge = false;
|
||||
} else {
|
||||
// Add anew edge, the end node is the current node user clicks
|
||||
self.edge = graph.addItem('edge', {
|
||||
source: model.id,
|
||||
target: model.id,
|
||||
});
|
||||
self.addingEdge = true;
|
||||
}
|
||||
},
|
||||
// The responsing function for mousemove defined in getEvents
|
||||
onMousemove(ev) {
|
||||
const self = this;
|
||||
// The current position the mouse clicks
|
||||
const point = {x: ev.x, y: ev.y};
|
||||
if (self.addingEdge && self.edge) {
|
||||
// Update the end node to the current node the mouse clicks
|
||||
self.graph.updateItem(self.edge, {
|
||||
target: point,
|
||||
});
|
||||
}
|
||||
},
|
||||
// The responsing function for edge:click defined in getEvents
|
||||
onEdgeClick(ev) {
|
||||
const self = this;
|
||||
const currentEdge = ev.item;
|
||||
if (self.addingEdge && self.edge === currentEdge) {
|
||||
self.graph.removeItem(self.edge);
|
||||
self.edge = null;
|
||||
self.addingEdge = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var vm;
|
||||
var graph;
|
||||
var graphData;
|
||||
var _ = require('lodash');
|
||||
var nodeProps = {
|
||||
id: 'vid',
|
||||
label: 'properties.label',
|
||||
name: 'properties.tagName',
|
||||
labelName: 'properties.label'
|
||||
};
|
||||
var edgeProps = {
|
||||
source: 'srcId',
|
||||
target: 'dstId',
|
||||
name: 'properties.edgeName',
|
||||
label: 'properties.label',
|
||||
arrow: 'properties.arrow',
|
||||
labelName: 'properties.label'
|
||||
}
|
||||
// const subjectColors = ['#5F95FF', '#61DDAA', '#65789B', '#F6BD16', '#7262FD', '#78D3F8', '#9661BC', '#F6903D', '#008685', '#F08BB4'];
|
||||
// const colorSets = G6.Util.getColorSetsBySubjectColors(subjectColors, '#fff', 'default', '#777');
|
||||
export default {
|
||||
components: {
|
||||
'noumenon-modify': noumenonModify,
|
||||
'noumenon-edge-modify': noumenonEdgeModify,
|
||||
'tree-graph-info': treeGraphInfo
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
qo: '',
|
||||
graphName: '',
|
||||
nodeCount: 0,
|
||||
edgeCount: 0,
|
||||
valueCount: 0,
|
||||
ontologyId: '',
|
||||
infoVisible: false, // 左侧信息栏显隐
|
||||
treeInfoVisible: false, // 右侧树形图谱
|
||||
clickNode: {id: '', name: ''},
|
||||
addNodeDialogVisible: false,
|
||||
addNodeCmd: {
|
||||
ontologyId: '',
|
||||
tagName: '',
|
||||
label: ''
|
||||
},
|
||||
arrowVisible: false, // 是否展示箭头方向
|
||||
createEdgeModelFlag: false, // 创建关系模式开关
|
||||
addEdgeDialogVisible: false,
|
||||
addEdgeCmd: {
|
||||
ontologyId: '',
|
||||
edgeName: '',
|
||||
label: '',
|
||||
source: '',
|
||||
target: '',
|
||||
arrow: false
|
||||
},
|
||||
selectObj: {
|
||||
type: '',
|
||||
value: {}
|
||||
},
|
||||
addNodeRules: {
|
||||
tagName: [
|
||||
{required: true, message: '请输入类名称', trigger: 'blur'},
|
||||
],
|
||||
},
|
||||
addEdgeRules: {
|
||||
edgeName: [
|
||||
{required: true, message: '请输入类名称', trigger: 'blur'},
|
||||
],
|
||||
},
|
||||
}
|
||||
},
|
||||
created() {
|
||||
vm = this;
|
||||
vm.ontologyId = this.$route.query.id;
|
||||
vm.qo = {
|
||||
pageNo: this.$route.query.pageNo,
|
||||
name: this.$route.query.name
|
||||
};
|
||||
vm.graphName = this.$route.query.graphName;
|
||||
},
|
||||
mounted() {
|
||||
vm.initGraph();
|
||||
},
|
||||
methods: {
|
||||
queryCount() {
|
||||
request({
|
||||
url: `/nebula_model/countallbyontologyid/${vm.ontologyId}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
vm.nodeCount = res.data.gns;
|
||||
vm.edgeCount = res.data.gxs;
|
||||
vm.valueCount = res.data.zsxs;
|
||||
});
|
||||
},
|
||||
queryTagData() { // 查询图空间内节点关系
|
||||
vm.queryCount(); // 更新统计信息
|
||||
request({
|
||||
url: `/nebula_model/findrelationbyontologyid/${vm.ontologyId}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
// ontologyId: vm.ontologyId
|
||||
}
|
||||
}).then(res => {
|
||||
res.data.nodes.forEach(node => {
|
||||
Reflect.ownKeys(nodeProps).forEach(key => {
|
||||
node[key] = _.get(node, nodeProps[key], '');
|
||||
});
|
||||
});
|
||||
res.data.relations.forEach(edge => {
|
||||
Reflect.ownKeys(edgeProps).forEach(key => {
|
||||
edge[key] = _.get(edge, edgeProps[key], '');
|
||||
});
|
||||
// 是否指向自己
|
||||
edge.type = edge.srcId === edge.dstId ? 'loop' : '';
|
||||
// 是否显示箭头
|
||||
edge.style = {endArrow: edge.arrow};
|
||||
});
|
||||
graphData = {
|
||||
nodes: res.data.nodes,
|
||||
edges: res.data.relations
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
});
|
||||
},
|
||||
initGraph: function () {
|
||||
this.destroyGraph();
|
||||
let mountNodeDom = document.getElementById("mountNode");
|
||||
let _width = mountNodeDom.clientWidth, _height = mountNodeDom.clientHeight;
|
||||
// 创建 G6 图实例
|
||||
if (graph == undefined || graph == null) {
|
||||
graph = new G6.Graph(config({width: _width, height: _height, id: 'mountNode'}));
|
||||
|
||||
graph.on('aftercreateedge', (e) => {
|
||||
vm.showAddEdge(e.edge._cfg.model);
|
||||
});
|
||||
|
||||
graph.on('canvas:click', (e) => {
|
||||
vm.selectObj = {
|
||||
type: '',
|
||||
value: {}
|
||||
};
|
||||
vm.clearStates();
|
||||
vm.infoVisible = false;
|
||||
});
|
||||
|
||||
/*拖动节点-start*/
|
||||
// const forceLayout = graph.get('layoutController').layoutMethod;
|
||||
// graph.on('node:dragstart', function (e) {
|
||||
// graph.layout();
|
||||
// refreshDragedNodePosition(e);
|
||||
// });
|
||||
// graph.on('node:drag', function (e) {
|
||||
// if (forceLayout) {
|
||||
// forceLayout.execute();
|
||||
// }
|
||||
// refreshDragedNodePosition(e);
|
||||
// });
|
||||
// graph.on('node:dragend', function (e) {
|
||||
// e.item.get('model').fx = null;
|
||||
// e.item.get('model').fy = null;
|
||||
// });
|
||||
/*拖动节点-end*/
|
||||
|
||||
graph.on('edge:click', function (e) { // 点击边事件
|
||||
let edge = e.item._cfg.model;
|
||||
if (!edge.name) {
|
||||
return;
|
||||
}
|
||||
let fields = [];
|
||||
if (edge.properties && edge.properties.nebulafieldsnapshot) {
|
||||
fields = edge.properties.nebulafieldsnapshot;
|
||||
}
|
||||
vm.showEdgeModifyCom({
|
||||
edgeName: edge.name,
|
||||
label: edge.labelName,
|
||||
arrow: edge.arrow,
|
||||
source: edge.srcId,
|
||||
target: edge.dstId
|
||||
}, fields);
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.setItemState(e.item, 'yourStateName', true);
|
||||
|
||||
});
|
||||
|
||||
graph.on('node:click', (e) => {
|
||||
if (!event.shiftKey && !vm.createEdgeModelFlag) { // 按住shift属于新增关系,不需要触发点击节点事件
|
||||
let node = e.item._cfg.model;
|
||||
vm.showNodeModifyCom({
|
||||
id: node.id,
|
||||
tagName: node.name,
|
||||
label: node.labelName
|
||||
}, _.get(node, 'properties.nebulafieldsnapshot', []));
|
||||
vm.clearStates();
|
||||
graph.setItemState(e.item, 'yourStateName', true);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
vm.queryTagData();
|
||||
},
|
||||
destroyGraph() {
|
||||
try {
|
||||
if (graph != undefined && graph != null) {
|
||||
graph.clear();
|
||||
graph = null;
|
||||
document.getElementById("mountNode").innerHTML = "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
clearStates() { // 清空选中状态
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
},
|
||||
changeCreateEdgeModel() { // 切换模式,默认和添加关系模式之间切换
|
||||
vm.createEdgeModelFlag = !vm.createEdgeModelFlag;
|
||||
if (vm.createEdgeModelFlag) {
|
||||
graph.setMode("addEdge");
|
||||
} else {
|
||||
graph.setMode("default");
|
||||
}
|
||||
},
|
||||
showAddNodeDialog() {
|
||||
vm.addNodeCmd = {
|
||||
ontologyId: vm.ontologyId,
|
||||
tagName: '',
|
||||
label: ''
|
||||
};
|
||||
vm.addNodeDialogVisible = true;
|
||||
},
|
||||
submitAddNode() {
|
||||
request({
|
||||
url: `/nebula_model/insertmodelvertex`,
|
||||
method: 'post',
|
||||
data: {
|
||||
ontologyId: vm.ontologyId,
|
||||
tagName: vm.addNodeCmd.tagName,
|
||||
label: vm.addNodeCmd.label
|
||||
}
|
||||
}).then(res => {
|
||||
graph.addItem('node', {
|
||||
id: res.data.vid,
|
||||
name: vm.addNodeCmd.tagName,
|
||||
label: vm.addNodeCmd.tagName,
|
||||
labelName: vm.addNodeCmd.label
|
||||
});
|
||||
graphData.nodes = graph.save().nodes;
|
||||
graph.layout();
|
||||
vm.$message.success("新增类成功");
|
||||
vm.addNodeDialogVisible = false;
|
||||
|
||||
vm.showNodeModifyCom(res.data.ob, []);
|
||||
});
|
||||
},
|
||||
showAddEdge(edge) {
|
||||
vm.addEdgeCmd = {
|
||||
ontologyId: vm.ontologyId,
|
||||
edgeName: '',
|
||||
label: '',
|
||||
arrow: false,
|
||||
source: edge.source,
|
||||
target: edge.target
|
||||
};
|
||||
vm.addEdgeDialogVisible = true;
|
||||
},
|
||||
handleAddEdgeDialogClose() {
|
||||
vm.queryTagData();
|
||||
},
|
||||
submitAddEdge() {
|
||||
vm.$refs['addEdgeForm'].validate((valid) => {
|
||||
if (!valid) {
|
||||
return;
|
||||
}
|
||||
let edge = {
|
||||
srcId: vm.addEdgeCmd.source,
|
||||
ontologyId: vm.ontologyId,
|
||||
dstId: vm.addEdgeCmd.target,
|
||||
edgeName: vm.addEdgeCmd.edgeName,
|
||||
arrow: vm.addEdgeCmd.arrow,
|
||||
label: vm.addEdgeCmd.label
|
||||
};
|
||||
request({
|
||||
url: `/nebula_model/insertmodeledge`,
|
||||
method: 'post',
|
||||
data: edge
|
||||
}).then(res => {
|
||||
vm.queryTagData();
|
||||
vm.$message.success("新增关系成功");
|
||||
vm.addEdgeDialogVisible = false;
|
||||
|
||||
vm.showEdgeModifyCom(edge, []);
|
||||
});
|
||||
});
|
||||
},
|
||||
showTreeCom() { // 显示树形图谱窗口
|
||||
vm.treeInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['treeGraphInfo'].queryTreeData(vm.ontologyId);
|
||||
});
|
||||
},
|
||||
showNodeModifyCom(node, fields) { // 显示节点编辑组件
|
||||
vm.infoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.selectObj = {
|
||||
type: 'node',
|
||||
value: node
|
||||
};
|
||||
vm.$refs['noumenonModify'].updateValue(node.tagName, node.label, fields);
|
||||
});
|
||||
},
|
||||
showEdgeModifyCom(edge, fields) { // 显示关系编辑组件
|
||||
vm.infoVisible = true;
|
||||
vm.selectObj = {
|
||||
type: 'edge',
|
||||
value: edge
|
||||
};
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['noumenonEdgeModify'].updateValue(edge.edgeName, edge.label, edge.arrow, fields, vm.selectObj.value.source, vm.selectObj.value.target);
|
||||
});
|
||||
},
|
||||
backGraph() { // 返回
|
||||
if(vm.$refs['treeGraphInfo']) {
|
||||
vm.$refs['treeGraphInfo'].destroyTree();
|
||||
}
|
||||
vm.$router.push({path: "/graphModel", query: {pageNo: vm.qo.pageNo, name: vm.qo.name}})
|
||||
vm.$message.success("保存成功");
|
||||
},
|
||||
changeArrowVisible() { // 显隐箭头
|
||||
vm.arrowVisible = !vm.arrowVisible;
|
||||
vm.queryTagData();
|
||||
},
|
||||
removeNode() { // 删除节点
|
||||
if (vm.selectObj.type.length === 0) {
|
||||
vm.$message.warning("请先选中节点或关系");
|
||||
return;
|
||||
}
|
||||
this.$confirm('此操作将永久删除选中节点和关系, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
if (vm.selectObj.type === "node") {
|
||||
request({
|
||||
url: `/nebula_model/deletemodelvertex/${vm.selectObj.value.id}`,
|
||||
method: 'get',
|
||||
data: {
|
||||
// vid: vm.selectObj.value.id
|
||||
}
|
||||
}).then(res => {
|
||||
vm.$message.success("删除成功");
|
||||
vm.queryTagData();
|
||||
vm.infoVisible = false;
|
||||
});
|
||||
} else if (vm.selectObj.type === "edge") {
|
||||
request({
|
||||
url: `/nebula_model/deletemodeledge`,
|
||||
method: 'post',
|
||||
data: {
|
||||
srcId: vm.selectObj.value.source,
|
||||
dstId: vm.selectObj.value.target,
|
||||
}
|
||||
}).then(res => {
|
||||
vm.$message.success("删除成功");
|
||||
vm.queryTagData();
|
||||
vm.infoVisible = false;
|
||||
});
|
||||
}
|
||||
}).catch(() => {
|
||||
});
|
||||
},
|
||||
submitUpdateNode(modifyCmd) { // 更新
|
||||
request({
|
||||
url: `/nebula_model/insertmodelvertex`,
|
||||
method: 'post',
|
||||
data: {
|
||||
vid: vm.selectObj.value.id,
|
||||
tagName: modifyCmd.tagName,
|
||||
label: modifyCmd.label,
|
||||
fields: modifyCmd.fields
|
||||
}
|
||||
}).then(res => {
|
||||
vm.queryTagData();
|
||||
vm.$message.success("保存成功");
|
||||
});
|
||||
},
|
||||
submitUpdateEdge(modifyCmd) {
|
||||
request({
|
||||
url: `/nebula_model/insertmodeledge`,
|
||||
method: 'post',
|
||||
data: {
|
||||
edgeName: modifyCmd.edgeName,
|
||||
label: modifyCmd.label,
|
||||
fields: modifyCmd.fields,
|
||||
arrow: modifyCmd.arrow,
|
||||
srcId: vm.selectObj.value.source,
|
||||
dstId: vm.selectObj.value.target,
|
||||
}
|
||||
}).then(res => {
|
||||
vm.queryTagData();
|
||||
vm.$message.success("保存成功");
|
||||
});
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDragedNodePosition(e) {
|
||||
const model = e.item.get('model');
|
||||
model.fx = e.x;
|
||||
model.fy = e.y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.graph-name-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 65px;
|
||||
width: 700px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.form-title {
|
||||
font-size: 16px;
|
||||
margin: 15px 0;
|
||||
|
||||
}
|
||||
|
||||
.add-attribute-box {
|
||||
margin-top: 10px;
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.graph-info-box, .tree-graph-info {
|
||||
position: absolute;
|
||||
width: 500px;
|
||||
top: 0px;
|
||||
overflow: hidden;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
z-index: 999;
|
||||
border-right: 1px solid rgba(52, 100, 224, 0.15);
|
||||
box-shadow: 0px 2px 21px 0px rgba(52, 100, 224, 0.15);
|
||||
}
|
||||
|
||||
.graph-info-box {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.tree-graph-info {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.graph-info-child {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left, .el-icon-d-arrow-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-right {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.info-line-item {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-content > div {
|
||||
margin-bottom: 5px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
/*这个样式不写,右键弹框会一直显示在画布的左下角*/
|
||||
position: absolute;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #f2f4f7;
|
||||
left: -99999px;
|
||||
top: -999999px;
|
||||
}
|
||||
|
||||
.menu div {
|
||||
list-style: none;
|
||||
padding: 5px 10px;
|
||||
color: black;
|
||||
border-bottom: 1px dashed #ffffff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu div:hover {
|
||||
color: #659bc5;
|
||||
background: #f3f6fa;
|
||||
}
|
||||
|
||||
.menu div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
ul#menuBox li {
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
ul#menuBox li:hover {
|
||||
color: #1c63e0;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
margin-left: -150px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
div.icon-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 75px;
|
||||
}
|
||||
|
||||
div.icon {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 40px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
div.icon i {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
div.icon-add i {
|
||||
background: url("../../assets/image/icon/bg-add.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-remove i {
|
||||
background: url("../../assets/image/icon/changyonggoupiaorenshanchu.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-save i {
|
||||
background: url("../../assets/image/icon/fanhui.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow i {
|
||||
background: url("../../assets/image/icon/arrowTop-fill.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-tree i {
|
||||
background: url("../../assets/image/icon/shuxingicon.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow.active i {
|
||||
background: url("../../assets/image/icon/arrowTop-fill-active.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow.active {
|
||||
box-shadow: 0px 0px 6px #979797;
|
||||
}
|
||||
|
||||
.node-box {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
947
web/src/components/graph/GraphSpace.vue
Normal file
947
web/src/components/graph/GraphSpace.vue
Normal file
@@ -0,0 +1,947 @@
|
||||
<template>
|
||||
<div class="node-box" style="position:relative;" v-loading="graphLoading">
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="infoVisible" class="graph-info-box">
|
||||
<div class="graph-info-child">
|
||||
<div class="info-item" style="margin-bottom: 15px">
|
||||
<div class="info-item-title">
|
||||
<label>{{comment}}</label>
|
||||
<!--<div class="add-attribute-box" style="float: right" @click="showAddGraphSpaceDialog({Name:''})">新建-->
|
||||
<!--</div>-->
|
||||
<!--<div class="add-attribute-box" style="float: right;margin-right: 10px" @click="removeGraphSpace()">-->
|
||||
<!--删除-->
|
||||
<!--</div>-->
|
||||
</div>
|
||||
<div class="info-item-content">
|
||||
<!--<el-select v-model="space" filterable placeholder="请选择" @change="handleSpaceChange">-->
|
||||
<!--<el-option v-for="item in spaceList"-->
|
||||
<!--:key="item.name"-->
|
||||
<!--:label="item.comment"-->
|
||||
<!--:value="item.name">-->
|
||||
<!--</el-option>-->
|
||||
<!--</el-select>-->
|
||||
<div style="margin-top: 20px;font-size: 14px;color: #606266">本体名称:{{ontologyName}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-tabs v-model="activeTab" class="el-tabs-item">
|
||||
<el-tab-pane label="概念" name="tag">
|
||||
<el-table
|
||||
class="tab-table-item"
|
||||
@row-click="modifyTag"
|
||||
:data="tagList"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="Name"
|
||||
label="名称">
|
||||
<template slot-scope="scope">
|
||||
<div :style="scope.row.Name === activeSelectType ? 'color: #409EFF':''">
|
||||
<label>{{ scope.row.comment }}</label>
|
||||
<label>({{ scope.row.count }})</label>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <div class="add-attribute-box" @click="modifyTag({Name:''})">+新建TAG</div>-->
|
||||
</el-tab-pane>
|
||||
<el-tab-pane label="关系" name="edge" class="el-tabs-item">
|
||||
<el-table
|
||||
class="tab-table-item"
|
||||
@row-click="modifyEdge"
|
||||
:data="edgeList"
|
||||
style="width: 100%">
|
||||
<el-table-column
|
||||
prop="Name"
|
||||
label="名称">
|
||||
<template slot-scope="scope">
|
||||
<div :style="scope.row.Name === activeSelectType ? 'color: #409EFF':''">
|
||||
<label>{{ scope.row.comment }}</label>
|
||||
<label>({{ scope.row.count }})</label>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<!-- <div class="add-attribute-box" @click="modifyEdge({Name:''})">+新建EDGE</div>-->
|
||||
</el-tab-pane>
|
||||
|
||||
</el-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="propsInfoVisible" class="tree-graph-info">
|
||||
<div class="graph-info-child">
|
||||
<div v-show="tabModifyCmdType === 'tag'">
|
||||
<tag-modify ref="tagModify"></tag-modify>
|
||||
</div>
|
||||
<div v-show="tabModifyCmdType === 'edge'">
|
||||
<edge-type-modify ref="edgeTypeModify"></edge-type-modify>
|
||||
</div>
|
||||
<div v-show="tabModifyCmdType === 'nodeDetail'">
|
||||
<node-detail ref="nodeDetail"></node-detail>
|
||||
</div>
|
||||
<div v-show="tabModifyCmdType === 'edgeAdd'">
|
||||
<edge-add ref="edgeAdd"></edge-add>
|
||||
</div>
|
||||
<i class="el-icon-d-arrow-right" @click="propsInfoVisible = false;"></i>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<!-- 为图谱准备一个具备大小(宽高)的Dom -->
|
||||
<div class="tree-container">
|
||||
<div class="icon-box">
|
||||
<div v-if="showFlag" style="display: inline-block;margin-right: 50px">
|
||||
<el-input placeholder="请输入关键词" v-model="keyword" style="width: 550px">
|
||||
<el-select v-model="searchTag" slot="prepend" placeholder="请选择概念" style="width: 150px" filterable>
|
||||
<el-option :label="item.comment" :value="item.Name" v-for="(item,index) in tagList" v-if="index != 0"></el-option>
|
||||
</el-select>
|
||||
<el-button slot="append" icon="el-icon-search" @click="searchBtn()"></el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
<div class="icon icon-search" @click="showFlag = !showFlag">
|
||||
<i title="搜索"></i>
|
||||
</div>
|
||||
<div class="icon icon-add" @click="showAddNodeDialog">
|
||||
<i title="新增类"></i>
|
||||
</div>
|
||||
<div title="新增关系" class="icon icon-arrow"
|
||||
@click="showAddEdgeDialog">
|
||||
<i title="新增关系"></i>
|
||||
</div>
|
||||
<div class="icon icon-save" @click="backGraph">
|
||||
<i title="返回"></i>
|
||||
</div>
|
||||
<!--<div title="按住shift点选两节点可新增关系" :class="['icon icon-arrow', createEdgeModelFlag ? 'active' : '']"-->
|
||||
<!--@click="changeCreateEdgeModel">-->
|
||||
<!--<i title="按住shift点选两节点可新增关系"></i>-->
|
||||
<!--</div>-->
|
||||
<!-- <div class="icon icon-remove" @click="removeNode">-->
|
||||
<!-- <i title="删除"></i>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<div id="mountNode" style="width: 100%; height: 1000px"></div>
|
||||
</div>
|
||||
<graph-space-add ref="graphSpaceAdd"></graph-space-add>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request';
|
||||
import {config} from '@/components/graph/config';
|
||||
import G6 from '@antv/g6';
|
||||
import tagModify from '@/components/dialog/TagModify';
|
||||
import edgeTypeModify from '@/components/dialog/EdgeTypeModify';
|
||||
import nodeDetail from '@/components/dialog/NodeDetail';
|
||||
import edgeAdd from '@/components/dialog/EdgeAdd';
|
||||
import edgeDetail from '@/components/dialog/EdgeDetail';
|
||||
import {getFirstStringProperty} from '@/utils/common';
|
||||
import graphSpaceAdd from '@/components/dialog/GraphSpaceAdd';
|
||||
|
||||
G6.registerBehavior('click-add-edge', {
|
||||
// Set the events and the corresponding responsing function for this behavior
|
||||
getEvents() {
|
||||
return {
|
||||
'node:click': 'onClick', // The event is canvas:click, the responsing function is onClick
|
||||
mousemove: 'onMousemove', // The event is mousemove, the responsing function is onMousemove
|
||||
'edge:click': 'onEdgeClick',
|
||||
};
|
||||
},
|
||||
// The responsing function for node:click defined in getEvents
|
||||
onClick(ev) {
|
||||
const self = this;
|
||||
const node = ev.item;
|
||||
const graph = self.graph;
|
||||
// The position where the mouse clicks
|
||||
const point = {x: ev.x, y: ev.y};
|
||||
const model = node.getModel();
|
||||
if (self.addingEdge && self.edge) {
|
||||
graph.updateItem(self.edge, {
|
||||
target: model.id,
|
||||
});
|
||||
// 打开编辑新建关系的信息弹窗
|
||||
vm.showAddEdgeDialog(self.edge._cfg.model);
|
||||
self.edge = null;
|
||||
self.addingEdge = false;
|
||||
} else {
|
||||
// Add anew edge, the end node is the current node user clicks
|
||||
self.edge = graph.addItem('edge', {
|
||||
source: model.id,
|
||||
target: model.id,
|
||||
});
|
||||
self.addingEdge = true;
|
||||
}
|
||||
},
|
||||
// The responsing function for mousemove defined in getEvents
|
||||
onMousemove(ev) {
|
||||
const self = this;
|
||||
// The current position the mouse clicks
|
||||
const point = {x: ev.x, y: ev.y};
|
||||
if (self.addingEdge && self.edge) {
|
||||
// Update the end node to the current node the mouse clicks
|
||||
self.graph.updateItem(self.edge, {
|
||||
target: point,
|
||||
});
|
||||
}
|
||||
},
|
||||
// The responsing function for edge:click defined in getEvents
|
||||
onEdgeClick(ev) {
|
||||
const self = this;
|
||||
const currentEdge = ev.item;
|
||||
if (self.addingEdge && self.edge === currentEdge) {
|
||||
self.graph.removeItem(self.edge);
|
||||
self.edge = null;
|
||||
self.addingEdge = false;
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
var vm;
|
||||
var graph;
|
||||
var graphData;
|
||||
var _ = require('lodash');
|
||||
var nodeProps = {
|
||||
id: 'vid',
|
||||
};
|
||||
var edgeProps = {
|
||||
source: 'srcId',
|
||||
target: 'dstId',
|
||||
}
|
||||
export default {
|
||||
components: {
|
||||
'tag-modify': tagModify,
|
||||
'edge-type-modify': edgeTypeModify,
|
||||
'node-detail': nodeDetail,
|
||||
'edge-detail': edgeDetail,
|
||||
'graph-space-add': graphSpaceAdd,
|
||||
'edge-add':edgeAdd
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
space: '', // 当前展示的图空间(实例)
|
||||
activeSelectType: 'All',
|
||||
spaceList: [], // 所有的图空间列表
|
||||
tagList: [], // 图空间的TAG列表
|
||||
edgeList: [], // 图空间的EDGE列表
|
||||
activeTab: 'tag',
|
||||
graphLoading: false,
|
||||
tabModifyCmdType: 'tag',
|
||||
createEdgeModelFlag: false,
|
||||
infoVisible: true, // 左侧信息栏显隐
|
||||
propsInfoVisible: false, // 右侧树形图谱
|
||||
selectObj: {
|
||||
type: '',
|
||||
value: {}
|
||||
},
|
||||
tagMap: {},
|
||||
edgeMap: {},
|
||||
ontologyName:"",
|
||||
showFlag:false,
|
||||
keyword:"",
|
||||
searchTag:"",
|
||||
ontologyId:"",
|
||||
comment:"",
|
||||
qo:{
|
||||
pageNo:"",
|
||||
name:""
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
vm = this;
|
||||
},
|
||||
mounted() {
|
||||
vm.qo = {
|
||||
pageNo: this.$route.query.pageNo,
|
||||
name: this.$route.query.name
|
||||
};
|
||||
vm.ontologyId = this.$route.query.ontologyId;
|
||||
vm.space = this.$route.query.space;
|
||||
vm.comment = this.$route.query.comment;
|
||||
vm.handleSpaceChange();
|
||||
// vm.querySpaceList();
|
||||
// vm.initGraph();
|
||||
},
|
||||
methods: {
|
||||
querySpaceList() { // 查询图空间列表,供快速选择切换
|
||||
request({
|
||||
url: '/nebula_operate/showspace',
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
vm.spaceList = res.data;
|
||||
if (res.data.length > 0) {
|
||||
vm.space = res.data[0].name;
|
||||
vm.handleSpaceChange();
|
||||
}else{
|
||||
vm.space = "";
|
||||
vm.tagMap = {};
|
||||
vm.edgeMap = {};
|
||||
vm.tagList = [];
|
||||
vm.edgeList = [];
|
||||
vm.ontologyName = "";
|
||||
vm.destroyGraph();
|
||||
}
|
||||
});
|
||||
},
|
||||
queryOntologyDetail(){
|
||||
var id = vm.ontologyId;
|
||||
// vm.spaceList.forEach(item => {
|
||||
// if(item.name == vm.space){
|
||||
// id = item.OntologyId;
|
||||
// }
|
||||
// })
|
||||
|
||||
if(id){
|
||||
request({
|
||||
url: `/ontology/getone/`+id,
|
||||
method: 'post',
|
||||
data: {id:id}
|
||||
}).then(res => {
|
||||
vm.ontologyName = res.data.name;
|
||||
})
|
||||
}
|
||||
},
|
||||
handleSpaceChange() {
|
||||
vm.initGraph();
|
||||
vm.queryStatistics();
|
||||
vm.queryOntologyDetail();
|
||||
},
|
||||
queryStatistics() {
|
||||
request({
|
||||
url: `/nebula_operate/censusgraphbyspace/${vm.space}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
let row = res.data;
|
||||
let tagMap = {};
|
||||
let edgeMap = {};
|
||||
row.forEach(item => {
|
||||
if (item.type === "Space") {
|
||||
if (item.name === "vertices") {
|
||||
tagMap['All'] = item.count;
|
||||
} else if (item.name === "edges") {
|
||||
edgeMap['All'] = item.count;
|
||||
}
|
||||
} else {
|
||||
if (item.type === "Tag") {
|
||||
tagMap[item.name] = item.count;
|
||||
} else if (item.type === "Edge") {
|
||||
edgeMap[item.name] = item.count;
|
||||
}
|
||||
}
|
||||
});
|
||||
vm.tagMap = tagMap;
|
||||
vm.edgeMap = edgeMap;
|
||||
vm.queryTagList();
|
||||
vm.queryEdgeList();
|
||||
});
|
||||
},
|
||||
queryTagList() { // 查询tag列表
|
||||
vm.propsInfoVisible = false;
|
||||
request({
|
||||
url: `/nebula_operate/showtag/${vm.space}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
res.data.unshift({
|
||||
Name: `All`,
|
||||
comment: '实体数'
|
||||
});
|
||||
res.data.forEach(row => {
|
||||
row.count = vm.tagMap[row.Name] ? vm.tagMap[row.Name] : 0;
|
||||
});
|
||||
vm.tagList = res.data;
|
||||
});
|
||||
},
|
||||
queryEdgeList() {
|
||||
vm.propsInfoVisible = false;
|
||||
request({
|
||||
url: `/nebula_operate/showedge/${vm.space}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
res.data.unshift({
|
||||
Name: `All`,
|
||||
comment: '三元组数'
|
||||
});
|
||||
res.data.forEach(row => {
|
||||
row.count = vm.edgeMap[row.Name] ? vm.edgeMap[row.Name] : 0;
|
||||
});
|
||||
vm.edgeList = res.data;
|
||||
});
|
||||
},
|
||||
modifyTag(row) {
|
||||
vm.tabModifyCmdType = 'tag';
|
||||
if (row.Name !== "All") {
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['tagModify'].updateValue(vm.space, row.Name);
|
||||
});
|
||||
} else {
|
||||
vm.propsInfoVisible = false;
|
||||
}
|
||||
// 更新图谱
|
||||
if (row.Name.length > 0) {
|
||||
vm.activeSelectType = row.Name;
|
||||
vm.querySpaceGraphData(row.Name, 'tag');
|
||||
}
|
||||
},
|
||||
modifyEdge(row) {
|
||||
vm.tabModifyCmdType = 'edge';
|
||||
if (row.Name !== "All") {
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['edgeTypeModify'].updateValue(vm.space, row.Name);
|
||||
});
|
||||
} else {
|
||||
vm.propsInfoVisible = false;
|
||||
}
|
||||
|
||||
// 更新图谱
|
||||
if (row.Name.length > 0) {
|
||||
vm.activeSelectType = row.Name;
|
||||
vm.querySpaceGraphData(row.Name, 'edge');
|
||||
}
|
||||
},
|
||||
queryDetail(type, obj) { // type : node/edge
|
||||
vm.tabModifyCmdType = type + 'Detail';
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs[type + 'Detail'].queryDetail(vm.space, obj);
|
||||
});
|
||||
},
|
||||
showAddGraphSpaceDialog() {
|
||||
vm.$refs['graphSpaceAdd'].openDialog();
|
||||
},
|
||||
removeGraphSpace() {
|
||||
this.$confirm('此操作将永久删除, 是否继续?', '提示', {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}).then(() => {
|
||||
request({
|
||||
url: `/nebula_operate/dropspace/${vm.space}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
vm.$message.success("删除成功");
|
||||
vm.querySpaceList();
|
||||
});
|
||||
});
|
||||
},
|
||||
querySpaceGraphData(typeName, type) { // 查询图空间内节点关系
|
||||
if (typeName == undefined) {
|
||||
vm.activeSelectType = "All";
|
||||
typeName = "All";
|
||||
}
|
||||
// vm.propsInfoVisible = false;
|
||||
vm.graphLoading = true;
|
||||
|
||||
// 根据查询类型区分接口,分三种,固定edgeType,固定tag的和查询全部类型的
|
||||
if (typeName === 'All') {
|
||||
request({
|
||||
url: `/nebula_operate/findpathinspace/${vm.space}`,
|
||||
method: 'post',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
vm.resolveGraphData(res);
|
||||
});
|
||||
} else {
|
||||
if (type === "tag") {
|
||||
request({
|
||||
url: `/nebula_operate/findnodes/${vm.space}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
tag: typeName
|
||||
}
|
||||
}).then(res => {
|
||||
vm.resolveGraphData({data: {nodes: res.data, relations: []}});
|
||||
});
|
||||
} else if (type === "edge") {
|
||||
request({
|
||||
url: `/nebula_operate/findrelations/${vm.space}`,
|
||||
method: 'post',
|
||||
data: {
|
||||
edge: typeName
|
||||
}
|
||||
}).then(res => {
|
||||
vm.resolveGraphData(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
resolveGraphData(res) { //处理图谱数据,格式化
|
||||
if (res.data && res.data.nodes) {
|
||||
res.data.nodes.forEach(node => {
|
||||
Reflect.ownKeys(nodeProps).forEach(key => {
|
||||
node[key] = _.get(node, nodeProps[key], '');
|
||||
});
|
||||
|
||||
if (node.properties) {
|
||||
node.label = node.name = getFirstStringProperty(node.properties);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (res.data && res.data.relations) {
|
||||
res.data.relations.forEach(edge => {
|
||||
Reflect.ownKeys(edgeProps).forEach(key => {
|
||||
edge[key] = _.get(edge, edgeProps[key], '');
|
||||
});
|
||||
if (edge.properties) {
|
||||
edge.label = edge.name = getFirstStringProperty(edge.properties);
|
||||
}
|
||||
|
||||
// 是否指向自己
|
||||
edge.type = edge.srcId === edge.dstId ? 'loop' : '';
|
||||
});
|
||||
}
|
||||
|
||||
graphData = {
|
||||
nodes: res.data && res.data.nodes ? res.data.nodes : [],
|
||||
edges: res.data && res.data.relations ? res.data.relations : []
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
vm.graphLoading = false;
|
||||
},
|
||||
initGraph() {
|
||||
this.destroyGraph();
|
||||
let mountNodeDom = document.getElementById("mountNode");
|
||||
let _width = mountNodeDom.clientWidth, _height = mountNodeDom.clientHeight;
|
||||
// 创建 G6 图实例
|
||||
if (graph == undefined || graph == null) {
|
||||
graph = new G6.Graph(config({width: _width, height: _height, id: 'mountNode'}));
|
||||
|
||||
graph.on('aftercreateedge', (e) => {
|
||||
vm.showAddEdgeDialog(e.edge._cfg.model);
|
||||
});
|
||||
|
||||
graph.on('canvas:click', (e) => {
|
||||
vm.selectObj = {
|
||||
type: '',
|
||||
value: {}
|
||||
};
|
||||
vm.clearStates();
|
||||
});
|
||||
|
||||
graph.on('edge:click', function (e) { // 点击边事件
|
||||
let edge = e.item._cfg.model;
|
||||
if (!edge.name) {
|
||||
return;
|
||||
}
|
||||
vm.queryDetail('edge', {
|
||||
edgeType: edge.edgeName,
|
||||
srcVid: edge.srcId,
|
||||
dstVid: edge.dstId
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.setItemState(e.item, 'yourStateName', true);
|
||||
|
||||
});
|
||||
|
||||
graph.on('node:click', (e) => {
|
||||
if (!event.shiftKey && !vm.createEdgeModelFlag) { // 按住shift属于新增关系,不需要触发点击节点事件
|
||||
let node = e.item._cfg.model;
|
||||
vm.queryDetail('node', {
|
||||
tag: node.labels,
|
||||
vid: node.vid,
|
||||
});
|
||||
vm.clearStates();
|
||||
graph.setItemState(e.item, 'yourStateName', true);
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
vm.querySpaceGraphData('All');
|
||||
},
|
||||
destroyGraph() {
|
||||
try {
|
||||
if (graph != undefined && graph != null) {
|
||||
graph.clear();
|
||||
graph = null;
|
||||
document.getElementById("mountNode").innerHTML = "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
clearStates() { // 清空选中状态
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
},
|
||||
changeCreateEdgeModel() { // 切换模式,默认和添加关系模式之间切换
|
||||
vm.createEdgeModelFlag = !vm.createEdgeModelFlag;
|
||||
if (vm.createEdgeModelFlag) {
|
||||
graph.setMode("addEdge");
|
||||
} else {
|
||||
graph.setMode("default");
|
||||
}
|
||||
},
|
||||
showAddEdgeDialog(edge) {
|
||||
vm.tabModifyCmdType = 'edgeDetail';
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['edgeDetail'].createItem(vm.space, edge.source, edge.target);
|
||||
});
|
||||
},
|
||||
showAddNodeDialog() {
|
||||
vm.tabModifyCmdType = 'nodeDetail';
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['nodeDetail'].createItem(vm.space);
|
||||
});
|
||||
},
|
||||
showAddEdgeDialog(){
|
||||
vm.tabModifyCmdType = 'edgeAdd';
|
||||
vm.propsInfoVisible = true;
|
||||
vm.$nextTick(() => {
|
||||
vm.$refs['edgeAdd'].createItem(vm.space);
|
||||
});
|
||||
},
|
||||
//搜索
|
||||
searchBtn(){
|
||||
if(!vm.searchTag){
|
||||
vm.$message.warning("请选择概念");
|
||||
return false;
|
||||
}
|
||||
if(!vm.keyword){
|
||||
vm.$message.warning("请输入关键字");
|
||||
return false;
|
||||
}
|
||||
vm.graphLoading = true;
|
||||
request({
|
||||
url: `/nebula_operate/findnodebykeyword/${vm.space}/${vm.searchTag}`,
|
||||
method: 'post',
|
||||
data: {keyword:vm.keyword}
|
||||
}).then(res => {
|
||||
if (res.data) {
|
||||
res.data.forEach(node => {
|
||||
Reflect.ownKeys(nodeProps).forEach(key => {
|
||||
node[key] = _.get(node, nodeProps[key], '');
|
||||
});
|
||||
|
||||
if (node.properties) {
|
||||
node.label = node.name = getFirstStringProperty(node.properties);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
graphData = {
|
||||
nodes: res.data ? res.data : [],
|
||||
edges: []
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
vm.graphLoading = false;
|
||||
});
|
||||
},
|
||||
findOnePathBySrcidAndDctid(srcId, dstId){
|
||||
request({
|
||||
url: `/nebula_operate/findonepathbysrcidanddctid/${vm.space}/${srcId}/${dstId}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
vm.resolveGraphData(res);
|
||||
});
|
||||
},
|
||||
findNodeById(tag,vid){
|
||||
request({
|
||||
url: `/nebula_operate/findnodebyid/${vm.space}/${tag}/${vid}`,
|
||||
method: 'get',
|
||||
data: {}
|
||||
}).then(res => {
|
||||
if (res.data) {
|
||||
Reflect.ownKeys(nodeProps).forEach(key => {
|
||||
res.data[key] = _.get(res.data, nodeProps[key], '');
|
||||
});
|
||||
|
||||
if (res.data.properties) {
|
||||
res.data.label = res.data.name = getFirstStringProperty(res.data.properties);
|
||||
}
|
||||
}
|
||||
|
||||
graphData = {
|
||||
nodes: res.data ? [res.data] : [],
|
||||
edges: []
|
||||
};
|
||||
// 读取数据
|
||||
graph.data(graphData);
|
||||
// 渲染图
|
||||
graph.render();
|
||||
vm.graphLoading = false;
|
||||
});
|
||||
},
|
||||
backGraph() { // 返回
|
||||
vm.$router.push({path: "/graphSpaceManage", query: {pageNo: vm.qo.pageNo, name: vm.qo.name}})
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDragedNodePosition(e) {
|
||||
const model = e.item.get('model');
|
||||
model.fx = e.x;
|
||||
model.fy = e.y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
||||
.el-tabs-item {
|
||||
height: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.el-tabs-item .el-tabs__content {
|
||||
height: calc(100% - 50px);
|
||||
}
|
||||
|
||||
.el-tabs-item .el-tabs__content .el-tab-pane {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.tab-table-item {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.node-box {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.info-item-content .el-select {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
margin-bottom: 45px;
|
||||
}
|
||||
|
||||
.info-item-title {
|
||||
border-bottom: 1px solid #ccc;
|
||||
padding-bottom: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.info-item-title label {
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
color: #2f2f2f;
|
||||
}
|
||||
|
||||
.graph-name-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 65px;
|
||||
width: 250px;
|
||||
font-size: 18px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
|
||||
.form-title {
|
||||
font-size: 16px;
|
||||
margin: 15px 0;
|
||||
|
||||
}
|
||||
|
||||
.add-attribute-box {
|
||||
margin-top: 10px;
|
||||
color: #409EFF;
|
||||
cursor: pointer;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.graph-info-box, .tree-graph-info {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
overflow: hidden;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
padding: 15px;
|
||||
z-index: 999;
|
||||
border-right: 1px solid rgba(52, 100, 224, 0.15);
|
||||
box-shadow: 0px 2px 21px 0px rgba(52, 100, 224, 0.15);
|
||||
}
|
||||
|
||||
.graph-info-box {
|
||||
left: 0;
|
||||
width: 230px;
|
||||
}
|
||||
|
||||
.tree-graph-info {
|
||||
right: 0;
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.graph-info-child {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.graph-info-child .el-table__header-wrapper {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left, .el-icon-d-arrow-right, .el-icon-d-arrow-right2 {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left {
|
||||
right: 0px;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-right {
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
.info-line-item {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-content > div {
|
||||
margin-bottom: 5px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
/*这个样式不写,右键弹框会一直显示在画布的左下角*/
|
||||
position: absolute;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #f2f4f7;
|
||||
left: -99999px;
|
||||
top: -999999px;
|
||||
}
|
||||
|
||||
.menu div {
|
||||
list-style: none;
|
||||
padding: 5px 10px;
|
||||
color: black;
|
||||
border-bottom: 1px dashed #ffffff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu div:hover {
|
||||
color: #659bc5;
|
||||
background: #f3f6fa;
|
||||
}
|
||||
|
||||
.menu div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
ul#menuBox li {
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
ul#menuBox li:hover {
|
||||
color: #1c63e0;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
position: relative;
|
||||
padding-left: 300px;
|
||||
}
|
||||
|
||||
.tips-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
left: 50%;
|
||||
width: 300px;
|
||||
margin-left: -150px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
div.icon-box {
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
right: 75px;
|
||||
}
|
||||
|
||||
div.icon {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 40px;
|
||||
margin-left: 15px;
|
||||
}
|
||||
|
||||
div.icon i {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
|
||||
div.icon-add i {
|
||||
background: url("../../assets/image/icon/bg-add.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-remove i {
|
||||
background: url("../../assets/image/icon/changyonggoupiaorenshanchu.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-save i {
|
||||
background: url("../../assets/image/icon/fanhui.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow i {
|
||||
background: url("../../assets/image/icon/arrowTop-fill.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
div.icon-tree i {
|
||||
background: url("../../assets/image/icon/shuxingicon.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-search i {
|
||||
background: url("../../assets/image/icon/search.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow.active i {
|
||||
background: url("../../assets/image/icon/arrowTop-fill-active.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-arrow.active {
|
||||
box-shadow: 0px 0px 6px #979797;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
327
web/src/components/graph/Graph_x6.vue
Normal file
327
web/src/components/graph/Graph_x6.vue
Normal file
@@ -0,0 +1,327 @@
|
||||
<template>
|
||||
<div class="node-box">
|
||||
<transition name="el-zoom-in-center">
|
||||
<div v-show="infoVisible" class="graph-info-box">
|
||||
<div class="form-title"></div>
|
||||
<el-form ref="relationForm" label-width="80px">
|
||||
<el-form-item label="*关系名称">
|
||||
<el-input v-model="relationCmd.name"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
</transition>
|
||||
<i :class="infoVisible ? 'el-icon-d-arrow-left' : 'el-icon-d-arrow-right'"
|
||||
@click="infoVisible = !infoVisible"></i>
|
||||
<!-- 为图谱准备一个具备大小(宽高)的Dom -->
|
||||
<div class="tree-container">
|
||||
<div class="icon icon-add" @click="showAddNodeDialog">
|
||||
<i title="新增节点"></i>
|
||||
</div>
|
||||
<div class="icon icon-remove">
|
||||
<i title="删除"></i>
|
||||
</div>
|
||||
<div class="icon icon-save">
|
||||
<i title="保存"></i>
|
||||
</div>
|
||||
<div id="mountNode" style="width: 100%; height: 1000px"></div>
|
||||
</div>
|
||||
<el-dialog
|
||||
title="新增节点"
|
||||
:visible.sync="addNodeDialogVisible"
|
||||
width="40%">
|
||||
<el-form ref="addNodeForm" label-width="80px">
|
||||
<el-form-item label="节点名称">
|
||||
<el-input v-model="addNodeCmd.name"></el-input>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<span slot="footer" class="dialog-footer">
|
||||
<el-button @click="addNodeDialogVisible = false">取 消</el-button>
|
||||
<el-button type="primary" @click="submitAddNode">确 定</el-button>
|
||||
</span>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import request from '@/utils/request';
|
||||
import { Graph } from '@antv/x6';
|
||||
|
||||
var vm;
|
||||
var graph;
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
nodeNum: 0,
|
||||
linkNum: 0,
|
||||
infoVisible: false,
|
||||
clickNode: {id: '', name: ''},
|
||||
addNodeDialogVisible: false,
|
||||
addNodeCmd: {
|
||||
name: ''
|
||||
},
|
||||
relationCmd: {
|
||||
name: '',
|
||||
table: '',
|
||||
sourceId: '',
|
||||
targetId: '',
|
||||
edgeId: ''
|
||||
},
|
||||
nodeAttributes: [] // 节点属性
|
||||
}
|
||||
},
|
||||
created() {
|
||||
vm = this;
|
||||
},
|
||||
mounted() {
|
||||
const data = {
|
||||
// 节点
|
||||
nodes: [
|
||||
{
|
||||
id: 'node1', // String,可选,节点的唯一标识
|
||||
x: 40, // Number,必选,节点位置的 x 值
|
||||
y: 40, // Number,必选,节点位置的 y 值
|
||||
width: 80, // Number,可选,节点大小的 width 值
|
||||
height: 40, // Number,可选,节点大小的 height 值
|
||||
label: 'hello', // String,节点标签
|
||||
},
|
||||
{
|
||||
id: 'node2', // String,节点的唯一标识
|
||||
x: 160, // Number,必选,节点位置的 x 值
|
||||
y: 180, // Number,必选,节点位置的 y 值
|
||||
width: 80, // Number,可选,节点大小的 width 值
|
||||
height: 40, // Number,可选,节点大小的 height 值
|
||||
label: 'world', // String,节点标签
|
||||
},
|
||||
],
|
||||
// 边
|
||||
edges: [
|
||||
{
|
||||
source: 'node1', // String,必须,起始节点 id
|
||||
target: 'node2', // String,必须,目标节点 id
|
||||
},
|
||||
],
|
||||
};
|
||||
vm.initGraph(data);
|
||||
},
|
||||
methods: {
|
||||
initGraph: function (_data) {
|
||||
this.destroyGraph();
|
||||
let _width = document.getElementById("mountNode").clientWidth;
|
||||
let _height = document.getElementById("mountNode").clientHeight;
|
||||
graph = new Graph({
|
||||
container: document.getElementById('mountNode'),
|
||||
width: _width,
|
||||
height: _height,
|
||||
scroller: {
|
||||
enabled: true,
|
||||
pannable: true,
|
||||
pageVisible: false,
|
||||
pageBreak: false,
|
||||
},
|
||||
// mousewheel: {
|
||||
// enabled: true,
|
||||
// modifiers: ['ctrl', 'meta'],
|
||||
// },
|
||||
modes: {
|
||||
// Defualt mode
|
||||
default: ['drag-node',
|
||||
{
|
||||
type: 'drag-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'zoom-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'create-edge',
|
||||
key: 'shift', // undefined by default, options: 'shift', 'control', 'ctrl', 'meta', 'alt'
|
||||
},
|
||||
]
|
||||
},
|
||||
});
|
||||
graph.fromJSON(_data);
|
||||
|
||||
},
|
||||
destroyGraph() {
|
||||
try {
|
||||
if(graph != undefined && graph != null) {
|
||||
graph.clear();
|
||||
graph = null;
|
||||
document.getElementById("mountNode").innerHTML = "";
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
},
|
||||
clearStates() { // 清空选中状态
|
||||
graph.getNodes().forEach((node) => {
|
||||
graph.clearItemStates(node);
|
||||
});
|
||||
graph.getEdges().forEach((edge) => {
|
||||
graph.clearItemStates(edge);
|
||||
});
|
||||
},
|
||||
showAddNodeDialog() {
|
||||
vm.addNodeCmd = {
|
||||
name: ''
|
||||
};
|
||||
vm.addNodeDialogVisible = true;
|
||||
},
|
||||
submitAddNode() {
|
||||
graph.addNode({
|
||||
id: Math.ceil(Math.random()*10000).toString(),
|
||||
label: vm.addNodeCmd.name
|
||||
});
|
||||
vm.$message.success("新增节点成功");
|
||||
vm.addNodeDialogVisible = false;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
function refreshDragedNodePosition(e) {
|
||||
const model = e.item.get('model');
|
||||
model.fx = e.x;
|
||||
model.fy = e.y;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.graph-info-box {
|
||||
position: absolute;
|
||||
left: 290px;
|
||||
width: 500px;
|
||||
top: 60px;
|
||||
bottom: 0;
|
||||
background-color: #fff;
|
||||
padding: 15px 15px;
|
||||
z-index: 999;
|
||||
border-right: 1px solid rgba(52, 100, 224, 0.15);
|
||||
box-shadow: 0px 2px 21px 0px rgba(52, 100, 224, 0.15);
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left, .el-icon-d-arrow-right {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
margin-top: -10px;
|
||||
font-size: 20px;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-left {
|
||||
left: 780px;
|
||||
}
|
||||
|
||||
.el-icon-d-arrow-right {
|
||||
left: 300px;
|
||||
}
|
||||
|
||||
.info-line-item {
|
||||
margin-bottom: 35px;
|
||||
}
|
||||
|
||||
.info-title {
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.info-content > div {
|
||||
margin-bottom: 5px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
ol, ul {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.menu {
|
||||
/*这个样式不写,右键弹框会一直显示在画布的左下角*/
|
||||
position: absolute;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #f2f4f7;
|
||||
left: -99999px;
|
||||
top: -999999px;
|
||||
}
|
||||
|
||||
.menu div {
|
||||
list-style: none;
|
||||
padding: 5px 10px;
|
||||
color: black;
|
||||
border-bottom: 1px dashed #ffffff;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.menu div:hover {
|
||||
color: #659bc5;
|
||||
background: #f3f6fa;
|
||||
}
|
||||
|
||||
.menu div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
ul#menuBox li {
|
||||
cursor: pointer;
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
ul#menuBox li:hover {
|
||||
color: #1c63e0;
|
||||
}
|
||||
|
||||
.tree-container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
div.icon {
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
padding: 15px;
|
||||
background-color: #fff;
|
||||
border-radius: 40px;
|
||||
|
||||
position: absolute;
|
||||
top: 25px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
div.icon i {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
margin: auto;
|
||||
display: inline-block;
|
||||
|
||||
}
|
||||
|
||||
div.icon-add {
|
||||
right: 75px;
|
||||
}
|
||||
|
||||
div.icon-add i {
|
||||
background: url("../../assets/image/icon/bg-add.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-remove {
|
||||
right: 145px;
|
||||
}
|
||||
|
||||
div.icon-remove i {
|
||||
background: url("../../assets/image/icon/changyonggoupiaorenshanchu.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
div.icon-save {
|
||||
right: 215px;
|
||||
}
|
||||
|
||||
div.icon-save i {
|
||||
background: url("../../assets/image/icon/baocun.png") no-repeat center/contain;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
81
web/src/components/graph/config.js
Normal file
81
web/src/components/graph/config.js
Normal file
@@ -0,0 +1,81 @@
|
||||
export function config(param) {
|
||||
return {
|
||||
container: param.id, // 指定图画布的容器id
|
||||
// 画布宽高
|
||||
width: param.width,
|
||||
height: param.height,
|
||||
layout: {
|
||||
type: 'force',
|
||||
nodeStrength: -0,
|
||||
nodeSize: 80,
|
||||
preventOverlap: true,
|
||||
edgeStrength: 0.1
|
||||
},
|
||||
// layout: {
|
||||
// type: 'fruchterman',
|
||||
// gravity: 10, // 重力大小,影响紧凑性, 默认10
|
||||
// speed: 5, // 每次迭代节点移动的速度。速度太快可能会导致强烈震荡,默认1
|
||||
// workerEnabled: true, // 是否启用 web-worker 以防布局计算时间过长阻塞页面交互, 默认false
|
||||
// },
|
||||
modes: {
|
||||
// Defualt mode
|
||||
default: ['drag-node',
|
||||
{
|
||||
type: 'drag-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'zoom-canvas',
|
||||
enableOptimize: true, // enable the optimize to hide the shapes beside nodes' keyShape
|
||||
},
|
||||
{
|
||||
type: 'create-edge',
|
||||
key: 'shift', // undefined by default, options: 'shift', 'control', 'ctrl', 'meta', 'alt'
|
||||
},
|
||||
],
|
||||
addEdge: ['click-add-edge'],
|
||||
},
|
||||
// plugins: [contextMenu], //minimap
|
||||
defaultNode: {
|
||||
size: 25,
|
||||
color: '#4b4b4b',
|
||||
label: 'name',
|
||||
style: {
|
||||
fill: '#68BDF6'
|
||||
},
|
||||
labelCfg: {
|
||||
position: 'bottom',
|
||||
style: {
|
||||
fontSize: 14,
|
||||
fill: '#4b4b4b'
|
||||
}
|
||||
}
|
||||
},
|
||||
defaultEdge: {
|
||||
style: {
|
||||
stroke: '#B3B3B3',
|
||||
lineAppendWidth: 10, // Enlarge the range the edge to be hitted
|
||||
},
|
||||
labelCfg: {
|
||||
autoRotate: false,
|
||||
style: {
|
||||
fontSize: 13,
|
||||
fill: '#989898'
|
||||
}
|
||||
},
|
||||
},
|
||||
nodeStateStyles: {
|
||||
yourStateName: {
|
||||
stroke: '#f00',
|
||||
lineWidth: 3,
|
||||
},
|
||||
},
|
||||
edgeStateStyles: {
|
||||
yourStateName: {
|
||||
stroke: '#f00',
|
||||
lineWidth: 3,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user