• 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来排序。
  • 相关阅读:
    char nvarchar varchar
    第32月第8天 打包 Framework 时使用 CocoaPods 引入第三方库的方法
    第31月第25天 xcode debug 限制uitextfiled输入
    第31月第22天 draw
    第31月第19天 NV12
    第31月第17天 resolveInstanceMethod
    第31月第15天 -fembed-bitcode
    第31月第10天 tableview头部空白 Other Linker Flags rtmp
    第31月 第9天 责任链AppDelegate
    第30月第18天 autolayout代码
  • 原文地址:https://www.cnblogs.com/codebook/p/16113910.html
Copyright © 2020-2023  润新知