先說下本次案例業務需求,輸入2個節點,獲取數據后繪制出2個節點間的路徑,之前使用的是網狀圖,但是網狀圖的效果不佳,需要轉換成流程圖的模式:
那么如何在不修改數據的情況下,實現類似效果尼?
看了下echarts的graph類型,可以實現類似的,下面是官方的實例
從實例中可以看出,難點在於節點的顯示位置x,y和曲線的設置。業務數據中:
1、節點的數量不定,關系的數量不定,
2、后台返回的數據只有單獨的節點信息和關系信息
實現思路:
1、分析數據,獲取前后節點關系,獲得行數位置(節點的xIndex信息)
在節點數組中找到開始節點並設置xIndex 為1,然后從它開始找第二層的節點
// 獲取節點的x軸順序
setNodeOrder () {
this.entityList.forEach(item => {
if (item.id === this.startNode) {
item.xIndex = 1;
}
// 設置一個對象,記錄節點信息,用於后面查詢信息,比數組查詢來的快
this.nodesObj[item.id] = item;
});
this.findNodeOrder(2, this.startNode);
}
// 廣度遍歷--按順序找到第index層的數據,並設置對應的參數,再層層遞進
findNodeOrder (xIndex, sId){
}
想一下,如果是第二層的節點,那么應該是關系中,源是startNode或者目的節點是startNode,如此類推,層層遞進,
這里需要使用廣度遍歷而不是深度遍歷,設定2個數組,currentQueue記錄當前層的節點,afterQueue記錄下一層的節點,當currentQueue層遍歷完后,將afterQueue變成currentQueue,繼續遍歷,直至結束,核心代碼如下:
......
let nextIds = [];
this.relationList.forEach(item => {
// 源
if (item.source === sId) {
if (!this.nodesObj[item.target].xIndex) {
this.nodesObj[item.target].xIndex = xIndex;
nextIds.push(item.target);
}
}
// 目的
if (item.target === sId) {
if (!this.nodesObj[item.source].xIndex) {
this.nodesObj[item.source].xIndex = xIndex;
nextIds.push(item.source);
}
}
});
let nextIdsLen = nextIds.length;
// 1、沒有當前的隊列,沒有后續的隊列,有nextIds
if (
!this.currentQueue.length &&
!this.afterQueue.length &&
nextIdsLen
) {
this.currentQueue = nextIds;
this.currentXindex = this.currentXindex + 1;
this.setNextOrder();
} else if (this.currentQueue.length && nextIdsLen) {
// 2、有當前的隊列在遍歷,排隊
this.afterQueue = this.afterQueue.concat(nextIds);
} else if (!this.currentQueue.length && this.afterQueue.length) {
// 3、沒有當前的隊列了,有排隊的隊列,則排隊的進去
if (nextIdsLen) {
this.afterQueue = this.afterQueue.concat(nextIds);
}
this.currentQueue = JSON.parse(JSON.stringify(this.afterQueue));
this.afterQueue = [];
this.currentXindex = this.currentXindex + 1;
}
setNextOrder函數:
setNextOrder () {
while (this.currentQueue.length) {
let id = this.currentQueue.shift();
this.findNodeOrder(this.currentXindex, id);
}
}
2、根據行數信息設置列數的位置(節點的yIndex層的信息)
先排序第一層和第二層的順序,因為第一層就一個節點,第二層和第一層的關系都是一樣的,所以順序無所謂,可以直接遇到就疊加
// 排完了x,再排列y
// 先排第一和二行的元素的y,按順序排
let maxXindex = 1;
let secondYIndexObj = {};
for (let i in this.nodesObj) {
let item = this.nodesObj[i];
if (!item.yIndex) {
maxXindex = item.xIndex > maxXindex ? item.xIndex : maxXindex;
if (item.xIndex === 1) {
item.yIndex = 1;
this.yIndexObj[item.id] = {
xIndex: 1,
yIndex: 1
};
}
if (item.xIndex === 2) {
if (!secondYIndexObj[item.xIndex]) {
item.yIndex = 1;
// 記錄當前的y軸上的節點個數
secondYIndexObj[item.xIndex] = 1;
this.yIndexObj[item.id] = {
xIndex: 2,
yIndex: 1
};
} else {
item.yIndex = secondYIndexObj[item.xIndex] + 1;
secondYIndexObj[item.xIndex] = item.yIndex;
this.yIndexObj[item.id] = {
xIndex: 2,
yIndex: item.yIndex
};
}
}
}
}
但是從第三層開始就要注意了,不能再按照第二層的遞增順序的方法來控制。因為第二層與第三層的關系和數量都不一定,如果隨機排列會導致線很亂,最好的方法是根據第二層的順序,獲取第三層的順序。即將第二層第N個節點有關的的節點設置在第N層。這樣如果第二層的數量與第三層的數量相等,那么就完全在一條直線上了。如果數量不等,也不至於那么混亂但是如果第三層有多個節點與第二層的同一個節點有關系,這個時候我選擇的是隨機選擇層級了
// 從第三層開始
if (maxXindex > 2) {
// 后面列的排序根據前一列為准(盡量保證在一條直線上)
for (let j = 3; j <= maxXindex; j++) {
for (let i in this.nodesObj) {
let item = this.nodesObj[i];
while (item.xIndex === j && !item.yIndex) {
// 先看跟它一層的節點有多少個
if (!this.nodeOrders[j]) {
let totals = this.findYtotal(j);
this.nodeOrders[j] = [];
for (let i = 1; i <= totals; i++) {
this.nodeOrders[j].push(i);
}
}
// 找第二層中的對應的層級
let findX = j - 1;
// 找到所有的層級
let findYs = this.findLinkNode(item.id, findX);
// 找到還有的層級
let sameArr = findYs.filter(x =>
this.nodeOrders[j].includes(x)
);
let findY;
// 找不到按順序抽取了
if (!sameArr.length) {
// 只能隨機選擇一個了
let ran = Math.floor(
Math.random() * this.nodeOrders[j].length
);
findY = this.nodeOrders[j][ran];
this.nodeOrders[j].splice(ran, 1);
} else {
findY = sameArr[0];
// 去除該順序
let order;
this.nodeOrders[j].find((num, k) => {
if (num === findY) {
order = k;
return true;
}
});
this.nodeOrders[j].splice(order, 1);
}
this.yIndexObj[item.id] = {
xIndex: j,
yIndex: findY
};
item.yIndex = findY;
}
}
}
}
3、設置具體的位置信息
獲取圖表的中心位置centerY,將起點和終點的y位置放在中間,其他的根據上面獲取的xIndex和yIndex的還有根據情況設置的間距(gapX,gapY)及節點的大小(size)計算出每個節點的x和y信息
// 設置節點的位置x,y
setNodesPositon () {
for (let i in this.nodesObj) {
let item = this.nodesObj[i];
if (item.id === this.startNode) {
item.y = this.centerY;
item.x = 1;
}
if (!item.x) {
item.x = this.gapX * (item.xIndex - 1) + this.size / 2;
}
if (!item.y && !item.total) {
item.total = this.findYtotal(item.xIndex);
if (item.total === 1) {
item.y = this.centerY;
} else {
let middleNum = item.total / 2;
let ceilNum = Math.ceil(middleNum);
let topGap;
let bottomGap;
let ty = (ceilNum - item.yIndex) * (this.gapY + this.size);
let by = (item.yIndex - ceilNum) * (this.gapY + this.size);
if (item.total % 2 === 1) {
topGap = this.centerY - ty;
bottomGap = this.centerY + by;
} else {
topGap = this.centerY - (ty + this.gapY + 0.5 * this.size);
bottomGap = this.centerY + (by - 0.5 * this.size);
}
if (item.yIndex <= middleNum) {
item.y = topGap;
} else {
item.y = bottomGap;
}
// 奇數
if (item.total % 2 === 1) {
if (item.yIndex === ceilNum) {
item.y = this.centerY;
}
}
}
}
}
}