vue基于antv-x6实现思维脑图和文件树组合图

实现内容主要根据节点children的类型字段来判定是采用文件树模式展示还是思维脑图展示,实现页面如下图展示

addAllNode(){
      const cells = []
      const addNode = (data,x,y) => {
        var newNode = this.graph.createNode({
          id: data.id,
          shape: (data.type === 'topic'||data.type === 'topic-branch')?'topic':'topic-child',
          x: x,
          y: y,
          width: data.width,
          height: data.height,
          attrs: {
            label:data.type!='topic-branch'&&data.type!='topic'?{
              refX: 1,
              textAnchor: 'start',
              textVerticalAnchor: 'middle',
              textWrap: {
                text: data.type === 'functional' ? data.label:
                  data.type === 'property' ? data.label:
                  data.type === 'mode' ? 'FM:'+data.label:
                  data.type === 'cause' ? 'FC:'+data.label:
                  data.type === 'cause_mode' ? 'FC:'+data.label:
                  data.type === 'result' ? 'FE:'+data.label:
                  data.type === 'cause_result' ? 'FE:'+data.label:
                  data.type === 'result_mode' ? 'FE:'+data.label:
                  data.type === 'measure' ? data.measure_type+":"+data.label:
                  data.type === 'cause_measure' ? 'DC:'+data.label:data.label
              },
              fontWeight: (data.type === 'topic'|| data.type === 'cause_mode'||data.type === 'result_mode'||data.type === 'cause_measure'||data.type === 'cause_result')? 'bold':'normal',
              fill: data.type==='functional'?'#00b400':
                data.type==='property'?'#63B8FF':
                data.type==='mode'?'#f50004':
                (data.type==='cause'||data.type==='cause_mode')?'#ff8430':
                (data.type==='result'||data.type==='result_mode'||data.type==='cause_result')?'#eb00eb':
                (data.type==='measure'&&(data.measure_type==='DC'||data.measure_type==='ODC'))?'#4559FE':
                (data.type==='measure'&&(data.measure_type==='PC'||data.measure_type==='OPC'))?'rgb(153, 102, 255)':
                data.type==='cause_measure'?'#4559FE':
                data.type==='d_interface'?'green':
                data.type==='interface'?'#009999':'black',
            }:{
              textWrap: {
                text: data.label
              },
              fontWeight: data.type === 'topic' ? 'bold':'normal',
              fill: data.type === 'topic' ? '#ffffff':'black',
            },
            body: {
              fill: data.type==='topic'?'#5091c6':data.type==='topic-branch'?'#EFF4FF':
                data.is_customer_need==1?'rgb(228, 255, 253)':'none',
            },
          },
          type: data.type,
          name: data.label,
          level: data.level,
          tip_info: data.tip_info,
          is_have_upper_mode: data.is_have_upper_mode,
          is_customer_need:data.is_customer_need,
          ports: { ...this.ports },
        })
        if(data.leaf){
          newNode.toggleButtonVisibility(data.leaf === false)
        }
        this.addNodePort(newNode)
        var son_fun_x = x+40;//非产品节点x坐标 = 上级x+40
        var son_fun_y = y+newNode.size().height+this.fun_hap;//非产品节点y坐标
        var son_pro_x = x+(newNode.size().width>150?newNode.size().width:150)+40;//产品节点x坐标
        if (data.children) {
          if(data.collapse==0){//展开
            let son_pro_num = 0;//子产品个数
            //先添加非产品节点
            data.children.forEach((item) => {
              if(item.type!='topic-branch'){
                var newSonNodeInfo = addNode(item,son_fun_x,son_fun_y);
                var newSonNode = newSonNodeInfo.node;
                son_fun_y = newSonNodeInfo.last_son_y;//依次往下累加高度y
                //
                var sun_fun_width = son_fun_x+newSonNode.size().width+40;
                if(sun_fun_width>son_pro_x){
                  son_pro_x = sun_fun_width
                }
                if(newSonNodeInfo.sun_fun_width>son_pro_x){//依次取出非产品节点最宽
                  son_pro_x = newSonNodeInfo.sun_fun_width
                }
                //添加连接线
                cells.push(this.addEdgeMethod(newNode,newSonNode,2))
              }else{
                son_pro_num += 1;
              }
            })
            //计算下级产品的高度
            let son_pro_notlast_total_height = 0;
            let add_son_pro_num = 0;
            //最后一个产品高度不纳入高度计算
            data.children.forEach((item) => {
              if(item.type=='topic-branch'){
                add_son_pro_num += 1;
                api.lowProductTotalHeight(item,this.pro_hap,this.fun_hap)
                if(add_son_pro_num<son_pro_num){
                  son_pro_notlast_total_height += item.total_height
                }else{//如果最后一个下面有多个同级,将高度纳入总高度计算
                  if(data.have_mul_son_product&&data.have_mul_son_product==1&&item.collapse==0&&(item.have_mul_son_product&&item.have_mul_son_product==1)){
                    son_pro_notlast_total_height += item.total_height
                  }
                }
              }
            })
            //添加产品类节点
            var first_product_height = 0;//第一个子产品y坐标
            if(son_pro_num==1){//子产品个数只有1个
              if(data.type=='topic'){//父级是根节点,y坐标需要处理,因为根节点高度和其他节点高度不一样
                first_product_height = y + newNode.size().height/2 - data.children[0].height/2
              }else{//父级不是根节点,y坐标=父级y坐标
                first_product_height = y
              }
            }else if(son_pro_num>1){//子产品个数有多个,将子级所有高度对半开
              first_product_height = y + newNode.size().height/2 - data.children[0].height/2 - son_pro_notlast_total_height/2;
            }
            var front_pro_total_height = 0;//上面产品总高度
            //
            var pro_index = 0;//子产品顺序
            data.children.forEach((item) => {
              if(item.type=='topic-branch'){
                var newSonNodeInfo;
                if(pro_index==0){//第一个产品,从顶部第一个子产品y坐标开始
                  newSonNodeInfo = addNode(item,son_pro_x,first_product_height);
                  if(item.have_mul_son_product&&item.have_mul_son_product==1){//下面同级有多个产品
                    front_pro_total_height = first_product_height + item.low_half_height
                  }else{//下面同级没有多个产品
                    front_pro_total_height = first_product_height + item.total_height
                  }
                }else{//非第一个产品
                  if(item.have_mul_son_product&&item.have_mul_son_product==1){//下面同级有多个产品
                    var son_y = front_pro_total_height + item.up_half_height
                    newSonNodeInfo = addNode(item,son_pro_x,son_y);
                  }else{//下面同级没有多个产品
                    newSonNodeInfo = addNode(item,son_pro_x,front_pro_total_height);
                  }
                  front_pro_total_height += item.total_height
                }
                var newSonNode = newSonNodeInfo.node;
                //添加连接线
                cells.push(this.addEdgeMethod(newNode,newSonNode,1))
                pro_index += 1;
              }
            })
          }else{
            newNode.toggleCollapse()
          }
        }
        cells.push(newNode);
        var info = {
          node: newNode,
          last_son_y: son_fun_y,
          sun_fun_width: son_pro_x
        }
        return info;
      }
      //根节点
      addNode(this.data,this.width/3,this.height/3)
      this.graph.resetCells(cells)
    },
    //连接节点type=1 产品-产品;
    addEdgeMethod(source,target,type){
      const sourcePorts = source.getPorts()
      const targetPorts = target.getPorts()
      return this.graph.createEdge({
        source:{
          cell:source,
          port:sourcePorts[type==1?0:2].id,
        },
        target:{
          cell:target,
          port:targetPorts[1].id,
        },
        router: {
          name: 'manhattan'
        },
        attrs: {
          line: {
            targetMarker: '',
            stroke: '#A2B1C3',
            strokeWidth: 2,
          },
        },
        zIndex:0
      })
    },
    //添加连接点
    addNodePort(node){
      node.addPort({ group: 'right' })
      node.addPort({ group: 'left' })
      node.addPort(
        {
          id:node.getProp("id")+"3",
          group: 'bottom',
          args: {
            x: 10,
            y: node.getBBox().height
          },
        }
      )
    },
    init() {
      var that = this
      //定义BOM
      class TreeNode extends Node {
        collapsed = false
        postprocess() {
          this.toggleCollapse(false)
        }
        isCollapsed() {
          return this.collapsed
        }
        toggleButtonVisibility(visible) {
          this.attr('buttonGroup', {
            display: visible ? 'block' : 'none',
          })
        }
        toggleCollapse(collapsed) {
          const target = collapsed == null ? !this.collapsed : collapsed
          if (target) {
            this.attr('buttonSign', {
              d: 'M 1 5 9 5 M 5 1 5 9',
              strokeWidth: 1.6,
            })
          } else {
            this.attr('buttonSign', {
              d: 'M 2 5 8 5',
              strokeWidth: 1.8,
            })
          }
          this.collapsed = target
        }
      }
      TreeNode.config({
        markup: [
          {
            tagName: 'rect',
            selector: 'body',
          },
          {
            tagName: 'g',
            selector: 'buttonGroup',
            children: [
              {
                tagName: 'rect',
                selector: 'button',
                attrs: {
                  'pointer-events': 'visiblePainted',
                },
              },
              {
                tagName: 'path',
                selector: 'buttonSign',
                attrs: {
                  fill: 'none',
                  'pointer-events': 'none',
                },
              },
            ],
          },
          {
            tagName: 'text',
            selector: 'label',
          },
        ],
        attrs: {
          body: {
            rx: 6,
            ry: 6,
            refWidth: '100%',
            refHeight: '100%',
            strokeWidth: 1,
            fill: '#EFF4FF',
            stroke: '#5091c6',
          },
          label: {
            textWrap: {
              ellipsis: true,
              width: -10,
            },
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
            refX: '50%',
            refY: '50%',
            fontSize: 14,
          },
          buttonGroup: {
            refX: '0%',
            refY: '50%',
          },
          button: {
            fill: '#5F95FF',
            stroke: 'none',
            x: -14,
            y: -7.5,
            height: 15,
            width: 15,
            rx: 10,
            ry: 10,
            cursor: 'pointer',
            event: 'node:collapse',
          },
          buttonSign: {
            refX: -11.5,
            refY: -5,
            stroke: '#FFFFFF',
            strokeWidth: 1.6,
          },
        },
      })
      Node.registry.register('topic', TreeNode, true)
      //定义模式/措施/原因/结果节点
      class TreeLineNode extends Node {
        collapsed = false
        postprocess() {
          this.toggleCollapse(false)
        }
        isCollapsed() {
          return this.collapsed
        }
        toggleButtonVisibility(visible) {
          this.attr('buttonGroup', {
            display: visible ? 'block' : 'none',
          })
        }
        toggleCollapse(collapsed) {
          const target = collapsed == null ? !this.collapsed : collapsed
          if (target) {
            this.attr('buttonSign', {
              d: 'M 1 5 9 5 M 5 1 5 9',
              strokeWidth: 1.6,
            })
          } else {
            this.attr('buttonSign', {
              d: 'M 2 5 8 5',
              strokeWidth: 1.8,
            })
          }
          this.collapsed = target
        }
      }
      TreeLineNode.config({
        markup: [
          {
            tagName: 'rect',
            selector: 'body',
          },
          {
            tagName: 'g',
            selector: 'buttonGroup',
            children: [
              {
                tagName: 'rect',
                selector: 'button',
                attrs: {
                  'pointer-events': 'visiblePainted',
                },
              },
              {
                tagName: 'path',
                selector: 'buttonSign',
                attrs: {
                  fill: 'none',
                  'pointer-events': 'none',
                },
              },
            ],
          },
          {
            tagName: 'text',
            selector: 'label',
          },
        ],
        attrs: {
          body: {
            refWidth: '100%',
            refHeight: '100%',
            fill: '#ffffff',
            strokeWidth: 0,
            stroke: '#5F95FF'
          },
          label: {
            textWrap: {
              ellipsis: true,
              width: -10,
            },
            textAnchor: 'middle',
            textVerticalAnchor: 'middle',
            refX: '50%',
            refY: '50%',
            fontSize: 12,
          },
          buttonGroup: {
            refX: '0%',
            refY: '50%',
          },
          button: {
            fill: '#5F95FF',
            stroke: 'none',
            x: -14,
            y: -7.5,
            height: 15,
            width: 15,
            rx: 10,
            ry: 10,
            cursor: 'pointer',
            event: 'node:collapse',
          },
          buttonSign: {
            refX: -11.5,
            refY: -5,
            stroke: '#FFFFFF',
            strokeWidth: 1.6,
          },
        },
      })
      Node.registry.register('topic-child', TreeLineNode, true)
      // 边
      Graph.registerEdge('mindmap-edge',{
        inherit: 'edge',
        attrs: {
          line: {
            targetMarker: '',
            stroke: '#A2B1C3',
            strokeWidth: 1
          }
        },
        zIndex: 0
      },true)
      //定义画布
      const containerRef = this.$refs.containerRef
      this.graph = new Graph({
        container: containerRef,
        width:this.width,
        height:this.height,
        grid: {
          visible: false
        },
        background: {
          color: '#F2F7FA',
        },
        panning:false,//拖拽--禁用拖拽因为与滚动冲突
        mousewheel: {//鼠标滚动
          enabled: true,
          modifiers: ['ctrl', 'meta'],
        },
      })
      //滚动
      this.graph.use(
        new Scroller({
          enabled: true,
          pageVisible:true
        }),
      )
      //渲染
      const render = () => {
        that.addAllNode()
      }
      this.graph.on('node:collapse', ({ node }) => {
        var node_data = api.findNodeData(that.data,node.getProp('id'))
        if(node_data.collapse==0){
          node_data.collapse = 1//不展开
          node_data.have_mul_son_product = 0
        }else{
          node_data.collapse = 0
        }
        that.addAllNode()
      })
      //节点右键事件
      if(this.dfmea_info.statu==0){
        this.graph.on('node:contextmenu', ({ e, node}) => {
          this.onContextmenu(e,node)
        })
      }
      //节点点击事件
      this.graph.on('node:click', ({ e,node }) => {
        this.onNodeClick(e,node)
      })
      // 渲染
      render()
    },
//产品下产品高度
export function lowProductTotalHeight(node,pro_hap,fun_hap){
  let totalHeight = 0;
  if(node.children&&node.children.length>0&&node.collapse==0){
    //子产品个数
    let son_pro_num = 0;
    node.children.forEach(c => {
      if(c.type=='topic-branch'){
        son_pro_num += 1;
      }
    });
    //产品总高度高度
    let funHeight = node.height + pro_hap;
    let notLastProTotalHeight = 0;//如果最后一个子级产品下面没多同级产品 或者 当前节点下面没多同级产品 或者最后一个节点不展开,则不计算最后一个节点高度
    let lastProHeight  = 0;//最后一个产品高度
    let pro_index = 0;
    let have_mul_low_product = 0;//下面有多个同级产品
    node.children.forEach(c => {
      if(c.type!='topic-branch'){
        funHeight += lowFunTotalHeight(c,fun_hap)
      }else{
        lowProductTotalHeight(c,pro_hap,fun_hap)
        if(c.have_mul_son_product&&c.have_mul_son_product==1){//下面有多个同级产品
          have_mul_low_product = 1
        }
        pro_index += 1;
        if(son_pro_num>pro_index){
          notLastProTotalHeight += c.total_height
        }else{
          if(node.have_mul_son_product&&node.have_mul_son_product==1&&c.collapse==0&&(c.have_mul_son_product&&c.have_mul_son_product==1)){
            notLastProTotalHeight += c.total_height
          }else{
            lastProHeight = c.total_height
          }
        }
      }
    });
    if(have_mul_low_product==1||son_pro_num>1){//下面有多个同级产品 高度=子级产品上半高度+max(子级产品下半高度,功能总高度)
      node.up_half_height = notLastProTotalHeight/2
      node.low_half_height = Math.max(funHeight,notLastProTotalHeight/2+lastProHeight)
      totalHeight = Math.max(funHeight,notLastProTotalHeight/2+lastProHeight)+(notLastProTotalHeight/2)
    }else if(son_pro_num==1){//下面同级产品只有一个子级产品 高度 = max(自身子级功能类高度,子级产品高度)
      totalHeight = Math.max(funHeight,notLastProTotalHeight+lastProHeight)
    }else{//下面没有子级产品 高度 = max(自身子级功能类高度,子级产品高度)
      totalHeight = funHeight
    }
    if(son_pro_num>1||have_mul_low_product==1){//有多个同级下级产品
      node.have_mul_son_product = 1;
    }
  }
  node.total_height = Math.max(node.height+pro_hap,totalHeight)
  return node;
}
//产品下功能/特性等高度
function lowFunTotalHeight(node,fun_hap) {
  let totalHeight = 0;
  if(node.children&&node.children.length>0&&node.collapse==0){
    totalHeight += node.height+fun_hap
    node.children.forEach((c,index) => {
      totalHeight += lowFunTotalHeight(c,fun_hap);
    });
  }
  return Math.max(node.height+fun_hap, totalHeight);
}
//根据id去查找节点数据
export function findNodeData(data,node_id){
  var node_data = null;
  if(data.id==node_id){
    node_data =  data
  }else{
    for(var i=0;i<data.children.length;i++){
      node_data = findNodeData(data.children[i],node_id)
      if(node_data!=null){
        break
      }
    }
  }
  return node_data;
}
//根据id去查找节点上级数据
export function findNodeParentData(data,node_id){
  var node_data = null;
  for(var i=0;i<data.children.length;i++){
    if(data.children[i].id==node_id){
      node_data = data;
      break
    }else{
      node_data = findNodeParentData(data.children[i],node_id)
      if(node_data!=null){
        break
      }
    }
  }
  return node_data;
}
//对比数据差异并重新修改原数据
export function CompareDifferences(old_data,new_data){
  old_data.label = new_data.label
  old_data.width = new_data.width
  old_data.leaf = new_data.leaf
  //是否有删除数据
  for(var i=0;i<old_data.children.length;i++){
    let is_del = true ;
    for(var j=0;j<new_data.children.length;j++){
      if(old_data.children[i].id==new_data.children[j].id){
        is_del = false;
        CompareDifferences(old_data.children[i],new_data.children[j]);
        break;
      }
    }
    if(is_del){
      old_data.children.splice(i,1)
      i--
    }
  }
  //是否有新增数据
  var new_children = []
  for(var i=0;i<new_data.children.length;i++){
    let is_add = true ;
    for(var j=0;j<old_data.children.length;j++){
      if(new_data.children[i].id==old_data.children[j].id){
        is_add = false;
        break;
      }
    }
    if(is_add){
      new_children.push(new_data.children[i])
    }
  }
  //新增
  for(var i=0;i<new_children.length;i++){
    old_data.children.push(new_children[i])
  }
}

  • 8
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

simple8973

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值