antv-G6拓扑图的自定义关系图小结

一、需求背景

结合项目需求有如下需求:
      • 分为上下两行,类似关系图;
      • 自定义node样式;
      • 击一个node,再点击另一类别node,生成连线。点击一个node后,再次点击当前node或者点击其他位置,取消选中。直线连接。点击连线,可以删除连线。连线样式有弧度。划上node节点出现弹框
  

二、主要代码实现

import G6 from '@antv/g6';   //引入G6
 
const setG6Topu = ()=>{
         //先清除原有
        if(graphs.current){
            graphs.current.destroy()
        }
        graphs.current = new G6.Graph({
            container: "topologicalCon",
            width: tuopuWidth,
            height: 154,
            linkCenter: true,
            modes: {
                // 根据不同需求设置拓扑图展示/可操作状态
                default: ['click-add-edge','rectNode'],  
                view: [],
            },
            defaultNode: {
                type: "rectNode",
                size: [32,48],
                color: "#008dff",
                style: {
                    fill: "#9EC9FF",
                    // lineWidth: 3
                },
                labelCfg: {
                    
                }
            },
            nodeStateStyles: {
                selected: {
                  stroke: '#f00',
                  lineWidth: 5
                }
              },

            plugins:[tooltip],
            defaultEdge: {
                type: 'polyline',
                style: {
                    radius: 10,
                    offset: 12,
                    stroke: '#40A9FF',
                    lineWidth: 1,
                }
            }
        });
        //添加数据
        graphs.current.data(tpData);
        //切换状态
        if(type=="view" || disableState){
          graphs.current.setMode('view')
        }else{
          graphs.current.setMode('default')
        }
        //加载
        graphs.current.render();
    }
 //self为自定义暂存点数据,getEdgesObj()/getNodeObj()/getEdgesObj等为自定义获取数据方法以及自定义判断
 
 //G6自带核心方法黄色框中标出。
 
 //点击node逻辑:不可连线则清除,可连线则起点保存,终点生成线
 
 
 //鼠标滑过逻辑:有起点下生成跟随鼠标的直需线
 
 //点击线逻辑:有起点下点击则清空,无起点则为单纯点击线(本次需求为删除)
 
 G6.registerBehavior('click-add-edge', {
        // Set the events and the corresponding responsing function for this behavior
        getEvents() {
          return {
            // 'click': 'onClick'
            'node:click': 'onNodeClick', // The event is canvas:click, the responsing function is onClick
            'mousemove': 'onMousemove', // The event is mousemove, the responsing function is onMousemove
            'edge:click': 'onEdgeClick', // The event is edge:click, the responsing function is onEdgeClick
          };
        },
        onNodeClick(ev) {
            //点击node操作
            const node = ev.item;
            const point = { x: ev.x, y: ev.y };
            const model = node.getModel();
            let obg = {...tpData}
            let {nodes=[],edges = []} = obg
            //判断是否已被连线,有则该次清空连线事件
            const hasLined =  getEdgesObj(edges,model.id) 
            if(hasLined){
              if (self.current.addingEdge && self.current.edge) {
                graphs.current.removeItem(self.current.edge);
                self.current.addingEdge = false
                self.current.edge = null
                self.current.id = null 
                return 
              }
              return 
            }
            //判断是否已有暂存点,有则为生成线,无则为生成新连线,做起点暂存
            if (self.current.addingEdge && self.current.edge) {
                const sourceObj =  getNodeObj(nodes,self.current.id)
                const targetObj =  getNodeObj(nodes,model.id)
                //判断起点终点为同一个
                if(sourceObj.addtype == targetObj.addtype){
                    graphs.current.removeItem(self.current.edge);
                    self.current.addingEdge = false
                    self.current.edge = null
                    self.current.id = null 
                    return
                }
                const lineWired =  getEdgesObj(edges,self.current.id,model.id)
                if(lineWired){
                    graphs.current.removeItem(self.current.edge);
                    self.current.addingEdge = false
                    self.current.edge = null
                    self.current.id = null 
                    return 
                }

                
                edges.push({
                    source: self.current.id,
                    target: model.id,
                })
                obg.edges = edges
                seTtpData(obg)
                self.current.addingEdge = false
                self.current.edge = null
                self.current.id = null 
            } else {
                self.current.addingEdge = true
                //添加线
                self.current.edge = graphs.current.addItem('edge', {
                  source: model.id,
                  target: model.id,
                })
                self.current.id = model.id 
            }
        },
        onMousemove(ev) {
            //鼠标在拓扑区域滑动操作
            const point = { x: ev.x, y: ev.y };
            if (self.current.addingEdge && self.current.edge) {
                //有暂存点则修未连接线颜色/样式
                graphs.current.updateItem(self.current.edge, {
                    type: 'line',
                    target: point,
                    style: {
                        lineDash: [2, 2] ,   // 虚线边
                        stroke: '#5FB333',
                    }
                });
            }
        },
        onEdgeClick(ev) {
            //点击线触发事件
            const currentEdge = ev.item;
            if (self.current.addingEdge && self.current.edge === currentEdge) {
                //有暂存点则进行删除暂存操作
                graphs.current.removeItem(self.current.edge);
                self.current.addingEdge = false
                self.current.edge = null
                self.current.id = null 

            }else{
                //没暂存点则删除线
                const node = ev.item
                const model = node.getModel();
                let obg = {...tpData}
                let {edges = []} = obg
                const sourceObj =  getNodeObj(obg.nodes,model.source)
                const targetObj =  getNodeObj(obg.nodes,model.target)
                let list  = []
                edges.forEach(el=>{
                  if(el.source == model.source && el.target == model.target){
                    return 
                  }else{
                    list.push(el)
                  }
                })
                obg.edges = [...list]
                
 //自定义node节点,网上流传两种方式,此种支持G6定义事件,符合实际需求
 
 G6.registerNode('rectNode', {
        draw: (cfg, group) => {
            //根据条件判断可点击/不可点击样式,可不关注
            let styObg = {
                opacity:1,
                fill:'#FFFFFF',
                stroke:'#40A9FF',
                color:'#000000',
                textX:0,
                textY:cfg.addtype != 'top'?-8:15,
                imgX:-8,
                imgY:cfg.addtype != 'top'?2:-20,
            }
            if(disableState || cfg.disabledFlag){
              styObg={
                  opacity:0.3,
                  fill:'#FFFFFF',
                  stroke:'rgba(0,0,0,.25)',
                  color:'rgba(0,0,0,.25)',
                  textX:0,
                  textY:cfg.addtype != 'top'?-8:15,
                  imgX:-8,
                  imgY:cfg.addtype != 'top'?2:-20,
              }
            }
            

            //最外面的那层
          const shape = group.addShape('rect', {
            draggable: true,
            attrs: {
              x: -16,
              y: -24,
              width: 32,
              height: 48,
              fill: styObg.fill, //填充色
              stroke: styObg.stroke, //边框
              radius: 8,
            },
          });
            //里面的那层
          group.addShape('image', {
            attrs: {
                x: styObg.imgX,
                y:styObg.imgY,
                width: 16,
                height: 16,
                img: cfg.img,
                opacity: styObg.opacity,
            },
            name: 'ip-cp-icon',
         });
            //文字
          group.addShape('text', {
            attrs: {
             textAlign: 'center',
              x: styObg.textX,
              y: styObg.textY,
              width: 32,
              height: 12,
              lineHeight: 10,
              fontSize:12,
              text: getLable(cfg.label),
              fill: styObg.color,
            },
          });
          return shape;
        },
    
    });
 
//G6悬浮框

const tooltip = new G6.Tooltip({
    getContent(e) {
      const outDiv = document.createElement('div');
      outDiv.style.minWidth = '180px';
      outDiv.style.maxWidth = '680px';
      let lineObj = getLineObj(e.item.getModel().id)
      outDiv.innerHTML = `
        <h4>${e.item.getModel().CiName || e.item.getModel().id}</h4>
        <div>${相关连线'}:${lineObj.source?lineObj.source+' ——'+ lineObj.target :'-'} </div>`
      return outDiv
    },
    itemTypes: ['node']

  })

  

 

三、其他补充

  • node位置:在node的数据中的x,y属性定义,间隔与高度可动态计算;
  • 容易忽略的一点:代码整理删除和修改地方可能有点乱,实现逻辑与思路看下就行;
 
 

四、成品

五、

 
其实,到第四个就完了,占占篇幅而已。
posted @ 2022-09-05 15:19  疯癫释流年  阅读(2127)  评论(0编辑  收藏  举报