1. 程式人生 > >記錄使用echarts的graph型別繪製流程圖全過程(一)-x,y位置的計算

記錄使用echarts的graph型別繪製流程圖全過程(一)-x,y位置的計算

先說下本次案例業務需求,輸入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函式:
js// 設定下個 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;
              }
            }
          }
        }
      }
    }