echarts關系拓撲圖一個demo


需求描述

關系圖分層級展示,做一個類似樹結構的展示界面,每一層級節點按照權重計算坐標位置,父節點的位置放在在下層子節點中間。

需求分析

  • 關系圖不是真正的樹結構,所以目標節點只有在其下一層的才是計算的‘子節點’,如果兄弟節點有共同的下層‘子節點’,按照從左到右的順序優先排列(也就是說,若節點A和節點B是同一層的兄弟節點,他們有共同的下層節點C,那么就把C作為A的‘子節點’,在計算B節點坐標的時候就不在使用C節點作為參照了),這樣做的目的是為了避免兩個兄弟節點具有相同的下層‘子節點’導致渲染節點重合的問題。

  • 確定分層:關系層次是確定的,所以可以根據分類數組遍歷創建一個收集每一層節點的數組。

  • 節點排序:根據分層的節點,從上到下依次排出節點的順序,為了計算節點坐標做准備。

  • 計算所有葉子節點橫坐標:根據所有葉子節點的數量平分區間。遍歷分層數組,層從上到下,節點從左到右,如果當前節點是第一個葉子節點,設置權重比例為 w = 1,如果當前節點有子節點就去遍歷其子節點,如果其子節點是葉子節點就設置權重 w = w + 1, 按照遍歷順序每一個葉子節點權重 + 1。如果一共有n 個葉子節點那么就把空間平均分成

    n + 1份。這個(n+1)要記錄下來,等分空間的每一份寬度就是 boxWidth / (n + 1),用葉子節點的權重 * 每一份的寬度就是當前葉子節點的橫坐標了。

  • 計算除了葉子節點之外的節點橫坐標:在上一步基礎上,倒序遍歷分類數組,比如只有3層,從第三層開始遍歷,計算第二層節點橫坐標,只要將第二層某一節點的下層所有子節點(第三層的葉子節點)中兩頭的節點權重相加除以2作為當前節點在本層的權重,其他節點類似求權重即可。

  • 設置節點坐標:縱坐標可以設置成固定值,橫坐標按照節點權重乘以每一份寬度即可 w * boxWidth / (n + 1)

問題解決

  • 按分類排序節點
// 分類數組 before
categories: [
  {name: '分類1'},
  {name: '分類2'},
  {name: '分類3'}
],
    
// 分類數組 after,增加了children,其中就是node節點
cateArr: [
    name: '分類1',
    children: [...]
  },
  {
    name: '分類2',
    children: [...]
  },
  {
    name: '分類3',
    children: [...]
  }
],
      
// children 中的 node 節點
{
  "id": "1",
  "category": 0,
  "name": "節點名稱1",
  "value": 10
}
/**
 * @Description: 根據分層遍歷計算每個節點的子節點數
 * @param {*} links 節點關系數組(排序后)
 * @param {*} arr 分類數組
 * @param {*} lastIdx 數組最后一個index, 最后一層就是子節點,不用參與遍歷
 * @return {*}
 */
sortNodes(links, arr, lastIdx) {
  const cArr = cloneDeep(arr)
  let vm = this
  cArr.forEach((item, idx) => {
    if (idx !== lastIdx) {
      let prev = []
      let nodes = item.children
      nodes.forEach((node) => {
        let id = node.id
        let link = vm.getLinkIds(links, id, prev, item.pIds)
        node.ids = link.ids
        node.cIds = link.cIds
        prev = link.prev
      })
    }
  })
  this.setLeafWeight(cArr)
}
  • 葉子節點設置權重
/**
 * @Description: 設置葉子節點的權重
 * 每個葉子節點權重為1,其余節點權重為其下層所有關聯節點權重的中間值
 * @param {*} arr 分類數組
 * @return {*}
 */
setLeafWeight(arr) {
  let vm = this
  const cArr = cloneDeep(arr)
  let len = cArr.length - 1
  let len2 = len - 1 // 倒數第二層
  let w = 1 // 節點權重初始值
  vm.nodeMap.clear()

  // 從上到下計算所有葉子節點權重
  cArr.forEach((item, idx) => {
    if (idx !== len) {
      const nodes = item.children
      nodes.forEach((node) => {
        let ids = node.ids
        if (!ids.length) {
          vm.nodeMap.set(node.id, w)
          w++
        }
        if (idx === len2 && ids.length) {
          ids.forEach((id) => {
            vm.nodeMap.set(id, w)
            w++
          })
        }
      })
    }
  })

  this.leafGrad = vm.nodeMap.size + 1
  this.setRestWeight(cArr, len2)
},
  • 非葉子節點設置權重
/**
 * @Description: 設置除葉子節點之外的節點權重
 * @param {*} arr 分類數組
 * @param {*} len 分類數組倒數第二層index
 * @return {*}
 */
setRestWeight(arr, len) {
  let map = this.nodeMap
  for (let i = len; i > -1; i--) {
    let item = arr[i].children
    Array.isArray(item) &&
      item.forEach((node) => {
        if (!map.has(node.id)) {
          if (node.ids.length === 1) { // 只有一個字節的的直接取子節點值
            let mid = map.get(node.ids[0])
            map.set(node.id, mid)
          } else {
            let [start, end] = node.cIds
            let mid = (map.get(start) + map.get(end)) / 2
            map.set(node.id, mid)
          }
        }
      })
  }
},
  • 獲取節點坐標值
/**
 * @Description: 獲取節點坐標值
 * @param {*} cArr 大分類
 * @param {*} len 分類數組長度
 * @return {*}
 */
getNodePos(cArr, len) {
  const boxDom = document.getElementById('myChart')
  const boxWidth = boxDom.clientWidth - 100
  const ySize = Math.ceil(boxDom.clientHeight / (len + 1))
  const xGrad = boxWidth / this.leafGrad // 葉子節點分割區間寬度

  let allNodes = []
  cArr.forEach((c, idx) => {
    let children = c.children
    if (Array.isArray(children) && children.length) {
      let nodes = cloneDeep(children)
      const height = ySize * (idx + 1)
      nodes.forEach((node) => {
        node.x = this.nodeMap.get(node.id) * xGrad
        node.y = height
      })
      allNodes.push(...nodes)
    }
  })

  return allNodes
},

注意

  • 關系數組中數據排序會影響節點的坐標,所以通過遍歷分類數組,根據每一個節點的id在關系數組中按照當前節點的指向target來排序。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM