为了账号安全,请及时绑定邮箱和手机立即绑定

用 d3.js 做力导向图,如何使多条线并列显示不重叠

/ 猿问

用 d3.js 做力导向图,如何使多条线并列显示不重叠

王益达 2017-11-05 12:37:44

想要实现的效果(节点之间有 3 条连线(直线),分别并列显示):

http://img1.sycdn.imooc.com/5a2374ff000127e802880095.jpg

                        【图 1】


现在实现的效果(节点之间有 3 条连线,重叠在一起):

http://img3.sycdn.imooc.com/5a2376030001a73d04030099.jpg

                                     【图 2】


请问如何修改代码来实现【图 1】 连线(直线)不重合的效果?

查看完整描述

2 回答

已采纳
?
南岸绿竹

https://img1.sycdn.imooc.com/5ac9ddb300018bc904170295.jpg

设计思路:

  1. 对关系进行分组,指向两个相同节点为一组,记录下同组数据length,即两节点间关系线条数。

         //关系分组  
        var linkGroup = {};  
        //对连接线进行统计和分组,不区分连接线的方向,只要属于同两个实体,即认为是同一组  
        var linkmap = {};  
        for(var i=0; i<links.length; i++){  
            var key = links[i].source<links[i].target?links[i].source+':'+links[i].target:links[i].target+':'+links[i].source;  
            if(!linkmap.hasOwnProperty(key)){  
                linkmap[key] = 0;  
            }  
            linkmap[key]+=1;  
            if(!linkGroup.hasOwnProperty(key)){  
                linkGroup[key]=[];  
            }  
            linkGroup[key].push(links[i]);  
        } 

  2. 对连线进行对称编号({-1,0,1}、{-2,-1,0,1,-2}……),编号为0的线在中间,使连线对称分布

    for(var i=0; i<links.length; i++){  
            var key = links[i].source<links[i].target?links[i].source+':'+links[i].target:links[i].target+':'+links[i].source;  
            links[i].size = linkmap[key];  
            //同一组的关系进行编号  
            var group = linkGroup[key];  
            //给节点分配编号  
            setLinkNumber(group);  
        }

    function setLinkNumber(group){   
        if(group.length==0) return;
        if(group.length==1){
            group[0].linknum = 0;
            return;
        }
        var maxLinkNumber = group.length%2==0?group.length/2:(group.length-1)/2;
        
           var startLinkNum = -maxLinkNumber;
           for(var i = 0;i<group.length;i++){
               group[i].linknum = startLinkNum++;
           }
    }

  3. 连线初始化,涉及到一些坐标的运算,画了个图可能好理解一点,我们的目标就是计算坐标(x1,y1),(x2,y2),p点,q点坐标。我们知道O1,O2坐标以及他们的半径

    https://img2.sycdn.imooc.com/5ac9e19300013f6f05370304.jpg

    以下是计算过程

    edges_path.attr("d", function(d) {  
                    var tan = Math.abs((d.target.y - d.source.y)/(d.target.x - d.source.x)); //圆心连线tan值
                    var x1 = d.target.x - d.source.x > 0 ? Math.sqrt(d.sourceRadius*d.sourceRadius/(tan*tan + 1)) + d.source.x :
                         d.source.x - Math.sqrt(d.sourceRadius*d.sourceRadius/(tan*tan + 1)); //起点x坐标
                    var y1 = d.target.y - d.source.y > 0 ? Math.sqrt(d.sourceRadius*d.sourceRadius*tan*tan/(tan*tan + 1)) + d.source.y :
                        d.source.y - Math.sqrt(d.sourceRadius*d.sourceRadius*tan*tan/(tan*tan + 1)); //起点y坐标
                    var x2 = d.target.x - d.source.x > 0 ? d.target.x - Math.sqrt(d.targetRadius*d.targetRadius/(1+tan*tan)) :
                            d.target.x + Math.sqrt(d.targetRadius*d.targetRadius/(1+tan*tan));//终点x坐标
                    var y2 = d.target.y - d.source.y > 0 ? d.target.y - Math.sqrt(d.targetRadius*d.targetRadius*tan*tan/(1+tan*tan)) :
                        d.target.y + Math.sqrt(d.targetRadius*d.targetRadius*tan*tan/(1+tan*tan));//终点y坐标
                    if(d.target.x - d.source.x == 0 || tan == 0){ //斜率无穷大的情况或为0时
                        y1 = d.target.y - d.source.y > 0 ? d.source.y + d.sourceRadius:d.source.y - d.sourceRadius;
                        y2 = d.target.y - d.source.y > 0 ? d.target.y - d.targetRadius:d.target.y + d.targetRadius;
                    }
                    if(d.linknum==0){//设置编号为0的连接线为直线,其他连接线会均分在两边  
                        d.x_start = x1;
                        d.y_start = y1;
                        d.x_end = x2;
                        d.y_end = y2;
                        return 'M'+x1+' '+y1+' L '+ x2 +' '+y2;
                    }
                    var a = d.sourceRadius > d.targetRadius ? d.targetRadius*d.linknum/6 : d.sourceRadius*d.linknum/6;
                    var xm =d.target.x - d.source.x > 0 ? d.source.x + Math.sqrt((d.sourceRadius*d.sourceRadius-a*a)/(1+tan*tan)):
                        d.source.x - Math.sqrt((d.sourceRadius*d.sourceRadius-a*a)/(1+tan*tan));
                    var ym =d.target.y - d.source.y > 0 ? d.source.y + Math.sqrt((d.sourceRadius*d.sourceRadius-a*a)*tan*tan/(1+tan*tan)):
                        d.source.y - Math.sqrt((d.sourceRadius*d.sourceRadius-a*a)*tan*tan/(1+tan*tan));
                    var xn =d.target.x - d.source.x > 0 ? d.target.x - Math.sqrt((d.targetRadius*d.targetRadius-a*a)/(1+tan*tan)):
                        d.target.x + Math.sqrt((d.targetRadius*d.targetRadius-a*a)/(1+tan*tan));
                    var yn =d.target.y - d.source.y > 0 ? d.target.y - Math.sqrt((d.targetRadius*d.targetRadius-a*a)*tan*tan/(1+tan*tan)):
                        d.target.y + Math.sqrt((d.targetRadius*d.targetRadius-a*a)*tan*tan/(1+tan*tan));
                    if(d.target.x - d.source.x == 0 || tan == 0){//斜率无穷大或为0时
                        ym = d.target.y - d.source.y > 0 ? d.source.y + Math.sqrt(d.sourceRadius*d.sourceRadius-a*a):d.source.y - Math.sqrt(d.sourceRadius*d.sourceRadius-a*a);
                        yn = d.target.y - d.source.y > 0 ? d.target.y - Math.sqrt(d.targetRadius*d.targetRadius-a*a):d.target.y + Math.sqrt(d.targetRadius*d.targetRadius-a*a);
                    }
                    
                    var k = (x1-x2)/(y2-y1);//连线垂线的斜率
                    var dx = Math.sqrt(a*a/(1+k*k)); //相对垂点x轴距离
                    var dy = Math.sqrt(a*a*k*k/(1+k*k)); //相对垂点y轴距离
                    if((y2-y1) == 0){
                        dx = 0;
                        dy = Math.sqrt(a*a);
                    }
                    if(a > 0){
                        var xs = k > 0 ? xm - dx : xm + dx;
                        var ys = ym - dy;
                        var xt = k > 0 ? xn - dx : xn + dx;
                        var yt = yn - dy;
                    }else{
                        var xs = k > 0 ? xm + dx : xm - dx;
                        var ys = ym + dy;
                        var xt = k > 0 ? xn + dx : xn - dx;
                        var yt = yn + dy;
                    }
                    //记录连线起始和终止坐标,用于定位线上文字
                    d.x_start = x1;
                    d.y_start = y1;
                    d.x_end = x2;
                    d.y_end = y2;
                    return 'M'+xs+' '+ys+' L '+ xt +' '+yt;
                  });

  4. 最后想知道作者大大,连线上写字是如何实现的,挺急的!!!

    https://img3.sycdn.imooc.com/5a24a42400019ead04790104.jpg

查看完整回答
反对 2018-04-08
  • 王益达
    王益达
    能再把回答代码用 markdown 语法格式化一下就更好了。 连线文字居中问题,稍后整理出一份完整代码,在另一篇提问贴出来。
  • 王益达
    王益达
    另外,如果多条连并行的关系连线中,如果有指向相反方向的是不是会出现重合问题,显示成一条连线首尾都有箭头的情况。关系如下: A --[R1]--> B A <--[R2]-- B A --[R3]--> B
  • 南岸绿竹
    南岸绿竹
    一条关系对应一条线,箭头定义在线结束位置,不会出现重合、箭头方向变乱的情况,如图:
点击展开后面7
?
南岸绿竹

补充回复图片

https://img3.sycdn.imooc.com/5aceb5750001f7b605520453.jpg


查看完整回答
2 反对 2018-04-12

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信