ertop图完成

This commit is contained in:
clay 2021-07-19 23:50:08 +08:00
parent f6bfe30d7e
commit 4752ce1d4c
14 changed files with 919 additions and 1313 deletions

View File

@ -1,93 +0,0 @@
/**
* @author: clay
* @data: 2019/07/05
* @description: edit mode: 通过先后点击两个节点来添加连线容易和节点点击动作交叉已弃用
*/
export default {
name: 'click-add-edge',
options: {
getEvents() {
return {
'node:click': 'onNodeClick',
'canvas:mousemove': 'onMousemove',
'edge:click': 'onEdgeClick' // 点击空白处,取消边
}
},
onNodeClick(event) {
let graph = this.graph
let node = event.item
let point = { x: event.x, y: event.y }
let model = node.getModel()
let edgeShape = self.currentEdgeShape.guid || 'line'
if (this.addingEdge && this.edge) {
// 点击第二个节点
graph.updateItem(this.edge, {
target: model.id
})
this.edge = null
this.addingEdge = false
// 记录【连线】前后的数据状态
if (this.historyData) {
let graph = this.graph
// 如果当前点过【撤销】了,连线后没有【重做】功能
// 重置undoCount连线后的数据给(当前所在historyIndex + 1),且清空这个时间点之后的记录
if (self.undoCount > 0) {
self.historyIndex = self.historyIndex - self.undoCount // 此时的historyIndex应当更新为【撤销】后所在的索引位置
for (let i = 1; i <= self.undoCount; i++) {
let key = `graph_history_${self.historyIndex + i}`
self.removeHistoryData(key)
}
self.undoCount = 0
} else {
// 正常顺序执行的情况,记录【连线】前的数据状态
let key = `graph_history_${self.historyIndex}`
self.addHistoryData(key, this.historyData)
}
// 记录【连线】后的数据状态
self.historyIndex += 1
let key = `graph_history_${self.historyIndex}`
let currentData = JSON.stringify(graph.save())
self.addHistoryData(key, currentData)
}
} else {
// 点击第一个节点
this.historyData = JSON.stringify(graph.save())
if (edgeShape === 'stepline') {
this.edge = graph.addItem('edge', {
source: model.id,
target: point,
type: edgeShape,
controlPoints: [{ x: 100, y: 70 }]
})
} else {
this.edge = graph.addItem('edge', {
source: model.id,
target: point,
type: edgeShape
})
}
this.addingEdge = true
}
},
onMousemove(event) {
const point = { x: event.x, y: event.y }
if (this.addingEdge && this.edge) {
this.graph.updateItem(this.edge, {
target: point
})
}
},
onEdgeClick(ev) {
let graph = this.graph
const currentEdge = ev.item
// 拖拽过程中,点击会点击到新增的边上
if (this.addingEdge && this.edge === currentEdge) {
graph.removeItem(this.edge)
this.edge = null
this.addingEdge = false
}
}
}
}

View File

@ -1,173 +0,0 @@
/**
* @author: winyuan
* @data: 2019/07/16
* @repository: https://github.com/winyuan
* @description: edit mode: 鼠标点击交互
*/
// 用来获取调用此js的vue组件实例this
let vm = null;
const sendThis = (_this) => {
vm = _this;
};
export default {
sendThis, // 暴露函数
name: "click-event-edit",
options: {
getEvents() {
return {
"node:click": "onNodeClick",
"node:contextmenu": "onNodeRightClick",
"edge:click": "onEdgeClick",
"edge:contextmenu": "onEdgeRightClick",
"canvas:click": "onCanvasClick",
};
},
onNodeClick(event) {
// todo..."selected"是g6自带的状态在"drag-add-edge"中的"node:mouseup"事件也会触发,故此处不需要设置"selected"状态
// let clickNode = event.item;
// clickNode.setState('selected', !clickNode.hasState('selected'));
vm.currentFocus = "node";
vm.rightMenuShow = false;
this.updateVmData(event);
},
onNodeRightClick(event) {
let graph = vm.graph;
let clickNode = event.item;
let clickNodeModel = clickNode.getModel();
let selectedNodes = graph.findAllByState("node", "selected");
let selectedNodeIds = selectedNodes.map(item => {
return item.getModel().id;
});
vm.selectedNode = clickNode;
// 如果当前点击节点是之前选中的某个节点,就进行下面的处理
if (selectedNodes.length > 1 && selectedNodeIds.indexOf(clickNodeModel.id) > -1) {
vm.rightMenuShow = true;
let rightMenu = vm.$refs.rightMenu;
rightMenu.style.left = event.clientX + "px";
rightMenu.style.top = event.clientY + "px";
} else {
// 隐藏右键菜单
vm.rightMenuShow = false;
// 先取消所有节点的选中状态
selectedNodes.forEach(node => {
node.setState("selected", false);
});
// 再添加该节点的选中状态
clickNode.setState("selected", true);
vm.currentFocus = "node";
this.updateVmData(event);
}
graph.paint();
},
onEdgeClick(event) {
let clickEdge = event.item;
clickEdge.setState("selected", !clickEdge.hasState("selected"));
vm.currentFocus = "edge";
this.updateVmData(event);
},
onEdgeRightClick(event) {
let graph = vm.graph;
let clickEdge = event.item;
let clickEdgeModel = clickEdge.getModel();
let selectedEdges = graph.findAllByState("edge", "selected");
// 如果当前点击节点不是之前选中的单个节点,才进行下面的处理
if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) {
// 先取消所有节点的选中状态
graph.findAllByState("edge", "selected").forEach(edge => {
edge.setState("selected", false);
});
// 再添加该节点的选中状态
clickEdge.setState("selected", true);
vm.currentFocus = "edge";
this.updateVmData(event);
}
let point = { x: event.x, y: event.y };
},
onCanvasClick() {
vm.currentFocus = "canvas";
vm.rightMenuShow = false;
},
updateVmData(event) {
let self = this
if (event.item._cfg.type === "node") {
// const item = event.item;
// let group = item.getContainer();
// let children = group.get("children");
// for (let i = 0, len = children.length; i < len; i++) {
// const shape = children[i];
// if (shape.get("name") === "collapses") {
// console.log(shape.get("name"),"dkfjg");
// self.graph.updateItem(item, {
// collapsed: true,
// size: [300, 50]
// });
// setTimeout(() => graph.layout(), 100);
// } else if (shape.get("name") === "expands") {
//
// console.log(shape.get("name"),"expands");
// self.graph.updateItem(item, {
// collapsed: false,
// size: [300, 500]
// });
// setTimeout(() => graph.layout(), 100);
// }
// }
// 更新vm的data: selectedNode 和 selectedNodeParams
let clickNode = event.item;
// console.log(clickNode)
if (clickNode.hasState("selected")) {
let clickNodeModel = clickNode.getModel();
vm.selectedNode = clickNode;
let nodeAppConfig = { ...vm.nodeAppConfig };
Object.keys(nodeAppConfig).forEach(function(key) {
nodeAppConfig[key] = "";
});
vm.selectedNodeParams = {
label: clickNodeModel.label || "",
appConfig: { ...nodeAppConfig, ...clickNodeModel.appConfig }
};
}
} else if (event.item._cfg.type === "edge") {
console.log("jshdjhd")
// 更新vm的data: selectedEdge 和 selectedEdgeParams
let clickEdge = event.item;
if (clickEdge.hasState("selected")) {
let clickEdgeModel = clickEdge.getModel();
vm.selectedEdge = clickEdge;
let edgeAppConfig = { ...vm.edgeAppConfig };
Object.keys(edgeAppConfig).forEach(function(key) {
edgeAppConfig[key] = "";
});
vm.selectedEdgeParams = {
label: clickEdgeModel.label || "",
appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig }
};
}
}
}
}
};
const isInBBox = (point, bbox) => {
const {
x,
y
} = point;
const {
minX,
minY,
maxX,
maxY
} = bbox;
return x < maxX && x > minX && y > minY && y < maxY;
};

View File

@ -1,43 +0,0 @@
/**
* @author: clay
* @data: 2021/5/18 14:21
* @email: clay@hchyun.com
* @description: node
*/
let vm = null;
const sendThis = (_this) => {
vm = _this;
};
export default {
sendThis,
name: 'dice-er-edge',
options: {
getDefaultCfg() {
return {
multiple: true,
};
},
getEvents() {
return {
"edge:click": "onEdgeClick",
}
},
onEdgeClick(event) {
let clickEdge = event.item;
console.log(789)
console.log(clickEdge);
clickEdge.setState("selected", !clickEdge.hasState("selected"));
console.log(clickEdge);
let model = clickEdge.getModel();
console.log(model);
model.label = "4564654"
model.stroke='#e2e2e2'
clickEdge.update(model)
// console.log(vm)
// vm.currentFocus = "edge";
this.updateVmData(event);
},
}
}

View File

@ -19,7 +19,14 @@ const isInBBox = (point, bbox) => {
return x < maxX && x > minX && y > minY && y < maxY;
};
let vm = null;
const sendThis = (_this) => {
vm = _this;
};
export default {
sendThis,
name: 'dice-er-node',
options: {
getDefaultCfg() {
@ -31,8 +38,11 @@ export default {
return {
itemHeight: 50,
wheel: "scroll",
"node:click": "click",
// "node:mousemove": "moves",
"node:click": "onNodeClick",
"edge:click": "onEdgeClick",
"node:contextmenu": "onNodeRightClick",
"edge:contextmenu": "onEdgeRightClick",
"canvas:click": "onCanvasClick",
};
},
scroll(e) {
@ -73,7 +83,113 @@ export default {
},
click(e) {
onNodeClick(event) {
this.shrinkage(event)
vm.currentFocus = "node";
vm.rightMenuShow = false;
this.updateVmData(event);
},
/**
* 点击连线
* @param event
*/
onEdgeClick(event) {
let clickEdge = event.item;
clickEdge.setState("selected", !clickEdge.hasState("selected"));
vm.currentFocus = "edge";
this.updateVmData(event);
},
onNodeRightClick(event) {
let graph = vm.graph;
let clickNode = event.item;
let clickNodeModel = clickNode.getModel();
let selectedNodes = graph.findAllByState("node", "selected");
let selectedNodeIds = selectedNodes.map(item => {
return item.getModel().id;
});
vm.selectedNode = clickNode;
// 如果当前点击节点是之前选中的某个节点,就进行下面的处理
if (selectedNodes.length > 1 && selectedNodeIds.indexOf(clickNodeModel.id) > -1) {
vm.rightMenuShow = true;
let rightMenu = vm.$refs.rightMenu;
rightMenu.style.left = event.clientX + "px";
rightMenu.style.top = event.clientY + "px";
} else {
// 隐藏右键菜单
vm.rightMenuShow = false;
// 先取消所有节点的选中状态
selectedNodes.forEach(node => {
node.setState("selected", false);
});
// 再添加该节点的选中状态
clickNode.setState("selected", true);
vm.currentFocus = "node";
this.updateVmData(event);
}
graph.paint();
},
/**
*
* @param event
*/
onEdgeRightClick(event) {
let graph = vm.graph;
let clickEdge = event.item;
let clickEdgeModel = clickEdge.getModel();
let selectedEdges = graph.findAllByState("edge", "selected");
// 如果当前点击节点不是之前选中的单个节点,才进行下面的处理
if (!(selectedEdges.length === 1 && clickEdgeModel.id === selectedEdges[0].getModel().id)) {
// 先取消所有节点的选中状态
graph.findAllByState("edge", "selected").forEach(edge => {
edge.setState("selected", false);
});
// 再添加该节点的选中状态
clickEdge.setState("selected", true);
vm.currentFocus = "edge";
this.updateVmData(event);
}
let point = { x: event.x, y: event.y };
},
onCanvasClick() {
vm.currentFocus = "canvas";
vm.rightMenuShow = false;
},
updateVmData(event) {
if (event.item._cfg.type === "node") {
let clickNode = event.item;
if (clickNode.hasState("selected")) {
let clickNodeModel = clickNode.getModel();
vm.selectedNode = clickNode;
let nodeAppConfig = { ...vm.nodeAppConfig };
Object.keys(nodeAppConfig).forEach(function(key) {
nodeAppConfig[key] = "";
});
vm.selectedNodeParams = {
label: clickNodeModel.label || "",
appConfig: { ...nodeAppConfig, ...clickNodeModel.appConfig }
};
}
} else if (event.item._cfg.type === "edge") {
// 更新vm的data: selectedEdge 和 selectedEdgeParams
let clickEdge = event.item;
if (clickEdge.hasState("selected")) {
let clickEdgeModel = clickEdge.getModel();
vm.selectedEdge = clickEdge;
let edgeAppConfig = { ...vm.edgeAppConfig };
Object.keys(edgeAppConfig).forEach(function(key) {
edgeAppConfig[key] = "";
});
vm.selectedEdgeParams = {
label: clickEdgeModel.label || "",
sourceAttrs:clickEdgeModel.sourceAttrs,
targetAttrs:clickEdgeModel.targetAttrs,
appConfig: { ...edgeAppConfig, ...clickEdgeModel.appConfig }
};
}
}
},
shrinkage(e){
const {
graph
} = this;
@ -82,7 +198,7 @@ export default {
if (!item) {
return;
}
if (shape.get("name") === "collapse") {
if (shape.get("name") === "collapse") {
graph.updateItem(item, {
collapsed: true,
size: [300, 50],
@ -95,13 +211,12 @@ export default {
});
setTimeout(() => graph.layout(), 100);
}else {
// eslint-disable-next-line no-unused-vars
const model = item.getModel();
// console.log(JSON.stringify(model));
// console.log(model.style.default.fill = '#4eb922');
// console.log(model.style.selected.shadowColor = '#4eb922');
}
// eslint-disable-next-line no-unused-vars
const model = item.getModel();
// console.log(JSON.stringify(model));
// console.log(model.style.default.fill = '#4eb922');
// console.log(model.style.selected.shadowColor = '#4eb922');
}
},
moves(e) {
const name = e.shape.get("name");

View File

@ -79,6 +79,7 @@ export default {
let sourceAnchor = self.evtInfo.node.getAnchorPoints();
let sourceNodeModel = self.evtInfo.node.getModel();
console.log("sourceNodeModel",sourceNodeModel)
// 锚点数据
let anchorPoints = self.evtInfo.node.getAnchorPoints();
// 处理线条目标点
@ -94,6 +95,7 @@ export default {
id: utils.generateUUID(),
// 起始节点
source: sourceNodeModel.id,
sourceAttrs:sourceNodeModel.attrs,
sourceAnchor: sourceAnchor ? sourceAnchor.anchorIndex : "",
// 终止节点/位置
target: {
@ -139,7 +141,8 @@ export default {
}
self.graph.updateItem(self.drawEdge.currentLine, {
target: targetNodeModel.id,
targetAnchor: targetAnchor ? targetAnchor.anchorIndex : ""
targetAnchor: targetAnchor ? targetAnchor.anchorIndex : "",
targetAttrs:targetNodeModel.attrs,
});
// ************** 记录historyData的逻辑 start **************

View File

@ -6,20 +6,16 @@
import dragAddEdge from './drag-add-edge'
import hoverEventEdit from './hover-event-edit'
import clickEventEdit from './click-event-edit'
import dragEventEdit from './drag-event-edit'
import keyupEventEdit from './keyup-event-edit'
import diceErNode from './dice-er-node'
import diceErEdge from './dice-er-edge'
const obj = {
dragAddEdge,
hoverEventEdit,
// clickEventEdit,
dragEventEdit,
keyupEventEdit,
diceErNode,
diceErEdge
}
export default {

View File

@ -1,58 +0,0 @@
/**
* @author: clay
* @data: 2021/5/13 23:14
* @email: clay@hchyun.com
* @description: node
*/
export default {
name: "wheel-node",
options: {
getEvents() {
return {
wheel: "scorll"
};
},
scorll(e) {
console.log(456)
e.preventDefault();
const {
graph
} = this;
const nodes = graph.getNodes().filter((n) => {
const bbox = n.getBBox();
return isInBBox(graph.getPointByClient(e.clientX, e.clientY), bbox);
});
if (nodes) {
nodes.forEach((node) => {
const model = node.getModel();
if (model.attrs.length < 9) {
return;
}
const idx = model.startIndex || 0;
let startX = model.startX || 0.5;
let startIndex = idx + e.deltaY * 0.02;
startX -= e.deltaX;
if (startIndex < 0) {
startIndex = 0;
}
if (startX > 0) {
startX = 0;
}
if (startIndex > model.attrs.length - 1) {
startIndex = model.attrs.length - 1;
}
graph.update(node, {
startIndex,
startX,
});
});
}
},
}
};

View File

@ -8,6 +8,6 @@ export default {
type: 'top-cubic',
style: {
startArrow: false,
endArrow: false
endArrow: true
}
}

View File

@ -1,7 +1,6 @@
/**
* @author: winyuan
* @author: Clay
* @data: 2019/07/18
* @repository: https://github.com/winyuan
* @description: 线公共方法
*/
@ -21,6 +20,7 @@ export default {
},
name: 'edge-shape'
})
console.log(keyShape.attrs.endArrow = true)
return keyShape
},
setState(name, value, item) {

View File

@ -1,275 +0,0 @@
/**
* 带圆角折线连线的策略
* 文档https://www.yuque.com/antv/blog/eyi70n
* 参考https://github.com/guozhaolong/wfd/blob/master/src/item/edge.js
* 引用https://github.com/OXOYO/X-Flowchart-Vue/blob/master/src/global/lib/g6/edge/polylineFinding.js
*/
// 折线寻径
export const polylineFinding = function(sNode, tNode, sPort, tPort, offset = 10) {
const sourceBBox = sNode && sNode.getBBox ? sNode.getBBox() : getPointBBox(sPort)
const targetBBox = tNode && tNode.getBBox ? tNode.getBBox() : getPointBBox(tPort)
// 获取节点带 offset 的区域(扩展区域)
const sBBox = getExpandedBBox(sourceBBox, offset)
const tBBox = getExpandedBBox(targetBBox, offset)
// 获取扩展区域上的起始和终止连接点
const sPoint = getExpandedPort(sBBox, sPort)
const tPoint = getExpandedPort(tBBox, tPort)
// 获取合法折点集
let points = getConnectablePoints(sBBox, tBBox, sPoint, tPoint)
// 过滤合法点集,预处理、剪枝等
filterConnectablePoints(points, sBBox)
// 过滤合法点集,预处理、剪枝等
filterConnectablePoints(points, tBBox)
// 用 A-Star 算法寻径
let polylinePoints = AStar(points, sPoint, tPoint, sBBox, tBBox)
return polylinePoints
}
const getPointBBox = function(t) {
return {
centerX: t.x,
centerY: t.y,
minX: t.x,
minY: t.y,
maxX: t.x,
maxY: t.y,
height: 0,
width: 0
}
}
// 获取扩展区域
const getExpandedBBox = function(bbox, offset) {
if (bbox.width === 0 && bbox.height === 0) {
return bbox
}
return {
centerX: bbox.centerX,
centerY: bbox.centerY,
minX: bbox.minX - offset,
minY: bbox.minY - offset,
maxX: bbox.maxX + offset,
maxY: bbox.maxY + offset,
height: bbox.height + 2 * offset,
width: bbox.width + 2 * offset
}
}
// 获取扩展区域上的连接点
const getExpandedPort = function(bbox, point) {
// 判断连接点在上下左右哪个区域相应地给x或y加上或者减去offset
if (Math.abs(point.x - bbox.centerX) / bbox.width > Math.abs(point.y - bbox.centerY) / bbox.height) {
return {
x: point.x > bbox.centerX ? bbox.maxX : bbox.minX,
y: point.y
}
}
return {
x: point.x,
y: point.y > bbox.centerY ? bbox.maxY : bbox.minY
}
}
// 获取合法折点集合
const getConnectablePoints = function(sBBox, tBBox, sPoint, tPoint) {
let lineBBox = getBBoxFromVertexes(sPoint, tPoint)
let outerBBox = combineBBoxes(sBBox, tBBox)
let sLineBBox = combineBBoxes(sBBox, lineBBox)
let tLineBBox = combineBBoxes(tBBox, lineBBox)
let points = [
...vertexOfBBox(sLineBBox),
...vertexOfBBox(tLineBBox),
...vertexOfBBox(outerBBox)
]
const centerPoint = { x: outerBBox.centerX, y: outerBBox.centerY }
let bboxes = [outerBBox, sLineBBox, tLineBBox, lineBBox]
bboxes.forEach(bbox => {
// 包含 bbox 延长线和线段的相交线
points = [
...points,
...crossPointsByLineAndBBox(bbox, centerPoint)
]
})
points.push({ x: sPoint.x, y: tPoint.y })
points.push({ x: tPoint.x, y: sPoint.y })
return points
}
const getBBoxFromVertexes = function(sPoint, tPoint) {
const minX = Math.min(sPoint.x, tPoint.x)
const maxX = Math.max(sPoint.x, tPoint.x)
const minY = Math.min(sPoint.y, tPoint.y)
const maxY = Math.max(sPoint.y, tPoint.y)
return {
centerX: (minX + maxX) / 2,
centerY: (minY + maxY) / 2,
maxX: maxX,
maxY: maxY,
minX: minX,
minY: minY,
height: maxY - minY,
width: maxX - minX
}
}
const combineBBoxes = function(sBBox, tBBox) {
const minX = Math.min(sBBox.minX, tBBox.minX)
const minY = Math.min(sBBox.minY, tBBox.minY)
const maxX = Math.max(sBBox.maxX, tBBox.maxX)
const maxY = Math.max(sBBox.maxY, tBBox.maxY)
return {
centerX: (minX + maxX) / 2,
centerY: (minY + maxY) / 2,
minX: minX,
minY: minY,
maxX: maxX,
maxY: maxY,
height: maxY - minY,
width: maxX - minX
}
}
const vertexOfBBox = function(bbox) {
return [
{ x: bbox.minX, y: bbox.minY },
{ x: bbox.maxX, y: bbox.minY },
{ x: bbox.maxX, y: bbox.maxY },
{ x: bbox.minX, y: bbox.maxY }
]
}
const crossPointsByLineAndBBox = function(bbox, centerPoint) {
let crossPoints = []
if (!(centerPoint.x < bbox.minX || centerPoint.x > bbox.maxX)) {
crossPoints = [
...crossPoints,
{ x: centerPoint.x, y: bbox.minY },
{ x: centerPoint.x, y: bbox.maxY }
]
}
if (!(centerPoint.y < bbox.minY || centerPoint.y > bbox.maxY)) {
crossPoints = [
...crossPoints,
{ x: bbox.minX, y: centerPoint.y },
{ x: bbox.maxX, y: centerPoint.y }
]
}
return crossPoints
}
// 过滤连接点
const filterConnectablePoints = function(points, bbox) {
return points.filter(point => {
return point.x <= bbox.minX || point.x >= bbox.maxX || point.y <= bbox.minY || point.y >= bbox.maxY
})
}
const crossBBox = function(bboxes, p1, p2) {
for (let i = 0; i < bboxes.length; i++) {
const bbox = bboxes[i]
if (p1.x === p2.x && bbox.minX < p1.x && bbox.maxX > p1.x) {
if ((p1.y < bbox.maxY && p2.y >= bbox.maxY) || (p2.y < bbox.maxY && p1.y >= bbox.maxY)) {
return true
}
} else if (p1.y === p2.y && bbox.minY < p1.y && bbox.maxY > p1.y) {
if ((p1.x < bbox.maxX && p2.x >= bbox.maxX) || (p2.x < bbox.maxX && p1.x >= bbox.maxX)) {
return true
}
}
}
return false
}
const getCost = function(p1, p2) {
return Math.abs(p1.x - p2.x) + Math.abs(p1.y - p2.y)
}
// aStar 寻径
const AStar = function(points, sPoint, tPoint, sBBox, tBBox) {
const openList = [sPoint]
const closeList = []
points.forEach(item => {
item.id = item.x + '-' + item.y
})
let tmpArr = []
points.forEach(item => {
if (!tmpArr.includes(target => target.id === item.id)) {
tmpArr.push(item)
}
})
points = [
...tmpArr,
tPoint
]
let endPoint
while (openList.length > 0) {
let minCostPoint
openList.forEach((p, i) => {
if (!p.parent) {
p.f = 0
}
if (!minCostPoint) {
minCostPoint = p
}
if (p.f < minCostPoint.f) {
minCostPoint = p
}
})
if (minCostPoint.x === tPoint.x && minCostPoint.y === tPoint.y) {
endPoint = minCostPoint
break
}
openList.splice(openList.findIndex(o => o.x === minCostPoint.x && o.y === minCostPoint.y), 1)
closeList.push(minCostPoint)
const neighbor = points.filter(p => {
return (p.x === minCostPoint.x || p.y === minCostPoint.y) &&
!(p.x === minCostPoint.x && p.y === minCostPoint.y) &&
!crossBBox([sBBox, tBBox], minCostPoint, p)
}
)
neighbor.forEach(p => {
const inOpen = openList.find(o => o.x === p.x && o.y === p.y)
const currentG = getCost(p, minCostPoint)
if (!closeList.find(o => o.x === p.x && o.y === p.y)) {
if (inOpen) {
if (p.g > currentG) {
p.parent = minCostPoint
p.g = currentG
p.f = p.g + p.h
}
} else {
p.parent = minCostPoint
p.g = currentG
let h = getCost(p, tPoint)
if (crossBBox([tBBox], p, tPoint)) {
// 如果穿过bbox则增加该点的预估代价为bbox周长的一半
h += (tBBox.width / 2 + tBBox.height / 2)
}
p.h = h
p.f = p.g + p.h
openList.push(p)
}
}
})
}
if (endPoint) {
const result = []
result.push({
x: endPoint.x,
y: endPoint.y
})
while (endPoint.parent) {
endPoint = endPoint.parent
result.push({
x: endPoint.x,
y: endPoint.y
})
}
return result.reverse()
}
return []
}

View File

@ -1,13 +1,10 @@
/**
* @author: winyuan
* @author: Clay
* @data: 2019/07/18
* @repository: https://github.com/winyuan
* @description: 曲线
*/
import utils from "../utils";
import base from './base'
import { polylineFinding } from './polyline-finding'
export default {
name: 'top-cubic',
extendName: 'cubic',

View File

@ -74,15 +74,15 @@ export default {
r: 3,
symbol: 'circle',
lineWidth: 1,
fill: 'white',
fill: '#FFFFFF',
fillOpacity: 1,
stroke: '#096DD9',
strokeOpacity: 1,
cursor: 'crosshair'
},
hover: {
fillOpacity: 1,
strokeOpacity: 1
fillOpacity: 0.3,
strokeOpacity: 0.5
},
unhover: {
fillOpacity: 0,

File diff suppressed because one or more lines are too long

View File

@ -4,6 +4,7 @@
ref="topology"
:graph-data="graphData"
:node-app-config="nodeAppConfig"
:edge-app-config="edgeAppConfig"
@doAutoRefresh="doAutoRefresh"
@doManualRefresh="doManualRefresh"
@doChangeMode="doChangeMode"
@ -15,146 +16,246 @@
<script>
/* 局部注册 */
import Topology from "./packages/topology/src/topology";
import { deepClone } from "./utils/index";
import Topology from './packages/topology/src/topology'
import { deepClone } from './utils/index'
export default {
name: "DemoTopology",
name: 'DemoTopology',
components: {
"topology": Topology
'topology': Topology
},
data() {
return {
graphData: {
nodes: [
// {
// "id": "info",
// "label": "Employee",
// "attrs": [{
// "key": "id",
// "type": "number(6)"
// },
// {
// "key": "key",
// "type": "varchar(255)"
// },
// {
// "key": "gender",
// "type": "enum(M, F)"
// },
// {
// "key": "birthday",
// "type": "date"
// },
// {
// "key": "hometown",
// "type": "varchar(255)"
// },
// {
// "key": "country",
// "type": "varchar(255)"
// },
// {
// "key": "nation",
// "type": "varchar(255)"
// },
// {
// "key": "jobId",
// "type": "number(3)",
// "relation": [{
// "key": "id",
// "nodeId": "job"
// }]
// },
// {
// "key": "phone",
// "type": "varchar(255)"
// },
// {
// "key": "deptId",
// "type": "number(6)",
// "relation": [{
// "key": "id",
// "nodeId": "dept"
// }]
// },
// {
// "key": "startTime",
// "type": "date"
// },
// {
// "key": "leaveTime",
// "type": "date"
// }
// ]
// },
// {
// "id": "job",
// "label": "Job",
// "attrs": [{
// "key": "id",
// "type": "number(3)"
// },
// {
// "key": "title",
// "type": "varchar(255)"
// },
// {
// "key": "level",
// "type": "number(3)"
// }
// ]
// },
// {
// "id": "dept",
// "label": "Department",
// "attrs": [{
// "key": "id",
// "type": "number(6)"
// },
// {
// "key": "title",
// "type": "varchar(255)"
// },
// {
// "key": "desc",
// "type": "text"
// },
// {
// "key": "parent",
// "type": "number(6)",
// "relation": [{
// "key": "id",
// "nodeId": "dept"
// }]
// },
// {
// "key": "manager",
// "type": "number(6)"
// }
// ]
// }
],
edges: [
{
"source": "info",
"target": "job",
"sourceKey": "jobId",
"targetKey": "id"
'nodes': [{
'id': 'a72f9f56-d3b6-4f7b-a42f-bf9cc45e7c3c',
'x': 144.47865794573642,
'y': -12.739970930232559,
'labels': '服务器',
'table': 'test_table',
'type': 'dice-er-box',
'attrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'size': [55, 55],
'width': 250,
'height': 316,
'anchorPoints': [[0.5, 0], [1, 0.5], [0.5, 1], [0, 0.5]],
'appState': { 'alert': false },
'labelCfg': { 'position': 'bottom' },
'style': {
'default': {
'stroke': '#CED4D9',
'fill': 'transparent',
'shadowBlur': 10,
'shadowColor': 'rgba(13, 26, 38, 0.08)',
'lineWidth': 1,
'radius': 4,
'strokeOpacity': 0.7
}, 'selected': { 'shadowColor': '#ff240b', 'shadowBlur': 2 }, 'unselected': { 'shadowColor': '' }
},
{
"source": "info",
"target": "dept",
"sourceKey": "deptId",
"targetKey": "id"
'selectedIndex': null,
'label': '',
'appConfig': { 'ip': '', 'port': '', 'sysName': '' },
'depth': 0
}, {
'id': '933f5158-258e-44c9-8e1f-70aa701729a8',
'x': 494.37013081395366,
'y': 132.21506782945744,
'labels': '数据库',
'table': 'test_table',
'type': 'dice-er-box',
'attrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'size': [55, 55],
'width': 250,
'height': 316,
'anchorPoints': [[0.5, 0], [1, 0.5], [0.5, 1], [0, 0.5]],
'appState': { 'alert': false },
'labelCfg': { 'position': 'bottom' },
'style': {
'default': {
'stroke': '#CED4D9',
'fill': 'transparent',
'shadowBlur': 10,
'shadowColor': 'rgba(13, 26, 38, 0.08)',
'lineWidth': 1,
'radius': 4,
'strokeOpacity': 0.7
}, 'selected': { 'shadowColor': '#ff240b', 'shadowBlur': 2 }, 'unselected': { 'shadowColor': '' }
},
{
"source": "dept",
"target": "dept",
"sourceKey": "parent",
"targetKey": "id"
}
]
'selectedIndex': null,
'label': '',
'appConfig': { 'ip': '', 'port': '', 'sysName': '' },
'depth': 0
}], 'edges': [{
'id': 'b424996b-62cf-487c-bd7b-83210e8a6866',
'source': '933f5158-258e-44c9-8e1f-70aa701729a8',
'sourceAttrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'sourceAnchor': 3,
'target': '933f5158-258e-44c9-8e1f-70aa701729a8',
'type': 'top-cubic',
'style': {
'active': { 'stroke': 'rgb(95, 149, 255)', 'lineWidth': 1 },
'selected': {
'stroke': 'rgb(95, 149, 255)',
'lineWidth': 2,
'shadowColor': 'rgb(95, 149, 255)',
'shadowBlur': 10,
'text-shape': { 'fontWeight': 500 }
},
'highlight': { 'stroke': 'rgb(95, 149, 255)', 'lineWidth': 2, 'text-shape': { 'fontWeight': 500 } },
'inactive': { 'stroke': 'rgb(234, 234, 234)', 'lineWidth': 1 },
'disable': { 'stroke': 'rgb(245, 245, 245)', 'lineWidth': 1 },
'edgeStyle': {
'default': { 'stroke': '#e2e2e2', 'lineWidth': 3, 'lineAppendWidth': 10 },
'selected': { 'shadowColor': '#626262', 'shadowBlur': 3 }
},
'stroke': '#A3B1BF',
'lineWidth': 2,
'strokeOpacity': 0.92,
'lineAppendWidth': 10
},
'labelCfg': { 'position': 'center', 'autoRotate': false },
'startPoint': { 'x': 493.87013081395366, 'y': 290.21506782945744, 'anchorIndex': 3 },
'endPoint': { 'x': 493.87013081395366, 'y': 290.21506782945744, 'anchorIndex': 3 },
'targetAnchor': 3,
'targetAttrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'curveOffset': [-20, 20],
'curvePosition': [0.5, 0.5],
'depth': 0
}, {
'id': '22cefcd1-5d30-4a89-a061-81d7c111a99d',
'source': '933f5158-258e-44c9-8e1f-70aa701729a8',
'sourceAttrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'sourceAnchor': 3,
'target': 'a72f9f56-d3b6-4f7b-a42f-bf9cc45e7c3c',
'type': 'top-cubic',
'style': {
'active': { 'stroke': 'rgb(95, 149, 255)', 'lineWidth': 1 },
'selected': {
'stroke': 'rgb(95, 149, 255)',
'lineWidth': 2,
'shadowColor': 'rgb(95, 149, 255)',
'shadowBlur': 10,
'text-shape': { 'fontWeight': 500 }
},
'highlight': { 'stroke': 'rgb(95, 149, 255)', 'lineWidth': 2, 'text-shape': { 'fontWeight': 500 } },
'inactive': { 'stroke': 'rgb(234, 234, 234)', 'lineWidth': 1 },
'disable': { 'stroke': 'rgb(245, 245, 245)', 'lineWidth': 1 },
'edgeStyle': {
'default': { 'stroke': '#e2e2e2', 'lineWidth': 3, 'lineAppendWidth': 10 },
'selected': { 'shadowColor': '#626262', 'shadowBlur': 3 }
},
'stroke': '#A3B1BF',
'lineWidth': 2,
'strokeOpacity': 0.92,
'lineAppendWidth': 10
},
'labelCfg': { 'position': 'center', 'autoRotate': false },
'startPoint': { 'x': 493.87013081395366, 'y': 290.21506782945744, 'anchorIndex': 3 },
'endPoint': { 'x': 394.9786579457364, 'y': 145.26002906976743, 'anchorIndex': 1 },
'curveOffset': [-20, 20],
'curvePosition': [0.5, 0.5],
'targetAnchor': 1,
'targetAttrs': [{ 'key': 'id', 'type': 'number(6)', 'comment': '主键id' }, {
'key': 'key',
'type': 'varchar(255)',
'comment': '关键字'
}, { 'key': 'gender', 'type': 'enum(M, F)', 'comment': 'gender' }, {
'key': 'birthday',
'type': 'date',
'comment': '生日'
}, { 'key': 'hometown', 'type': 'varchar(255)', 'comment': '家乡' }, {
'key': 'country',
'type': 'varchar(255)',
'comment': '国家'
}, { 'key': 'nation', 'type': 'varchar(255)', 'comment': 'nation' }, {
'key': 'jobId',
'type': 'number(3)',
'comment': '工作id'
}, { 'key': 'phone', 'type': 'varchar(255)', 'comment': '电话' }],
'depth': 0,
'label': '',
'appConfig': { 'associated': 'left', 'tableComment': 'id', 'relComment': 'key' }
}], 'combos': []
},
nodeTypeList: [
// { guid: "blue", label: "", imgSrc: require("../../assets/images/blue.svg") },
@ -163,50 +264,55 @@ export default {
],
//
nodeAppConfig: {
ip: "节点IP",
port: "节点端口",
sysName: "设备名称"
// ip: 'IP',
// port: '',
// sysName: ''
},
edgeAppConfig: {
associated: '查询方式',
tableComment: '主表字段',
relComment: '关联字段'
},
autoRefreshTimer: null
};
}
},
mounted() {
let graphData = deepClone(this.graphData);
this.$refs.topology.initTopo(graphData);
this.randomChange();
let graphData = deepClone(this.graphData)
this.$refs.topology.initTopo(graphData)
this.randomChange()
},
methods: {
doAutoRefresh(interval) {
if (interval === -1) {
clearInterval(this.autoRefreshTimer);
clearInterval(this.autoRefreshTimer)
} else {
clearInterval(this.autoRefreshTimer);
clearInterval(this.autoRefreshTimer)
this.autoRefreshTimer = setInterval(() => {
this.randomChange();
}, interval);
this.$once("hook:beforeDestroy", () => {
clearInterval(this.autoRefreshTimer);
});
this.randomChange()
}, interval)
this.$once('hook:beforeDestroy', () => {
clearInterval(this.autoRefreshTimer)
})
}
},
doChangeMode(graphMode) {
clearInterval(this.autoRefreshTimer);
let graphData = deepClone(this.graphData);
this.$refs.topology.changeGraphMode(graphData, graphMode);
clearInterval(this.autoRefreshTimer)
let graphData = deepClone(this.graphData)
this.$refs.topology.changeGraphMode(graphData, graphMode)
},
doManualRefresh() {
this.randomChange();
this.randomChange()
},
doSaveData(graphData) {
console.log(JSON.stringify(graphData));
console.log(JSON.stringify(graphData))
},
randomChange() {
let graphData = deepClone(this.$refs.topology.getGraphData());
let { nodes } = graphData;
this.$refs.topology.changeGraphData(graphData);
let graphData = deepClone(this.$refs.topology.getGraphData())
let { nodes } = graphData
this.$refs.topology.changeGraphData(graphData)
}
}
};
}
</script>
<style lang="scss" scoped>