d3.js力导向图节点如何都显示在边框内

最近用到d3.js中的force力导向图,想实现效果如下,所有城市节点都在可视范围内,如果超出有滚动条也可以。
clipboard.png

遇到的问题是,当节点一多,有的节点就会跑到外面去,这边是通过加大charge相互作用力,从原本的-300改为-3000,也是会跑到svg边框外。
clipboard.png

求教下节点怎么限制在svg边框内?

实例代码如下:

<html>  
  <head>  
        <meta charset="utf-8">  
        <title>力导向图</title>  
  </head> 

<style>


</style>
    <body>  
        <script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>  
        <script>           
                       
        var nodes = [ { name: "桂林"    }, { name: "广州" },
                      { name: "厦门"    }, { name: "杭州"   },
                      { name: "上海"   }, { name: "青岛"    },
                      { name: "天津"    } ];
                     
        var edges = [  { source : 0  , target: 1 } , { source : 0  , target: 2 } ,
                       { source : 0  , target: 3 } , { source : 1  , target: 4 } ,
                       { source : 1  , target: 5 } , { source : 1  , target: 6 }  ];    
        
        var width = 400;
        var height = 400;
        
        
        var svg = d3.select("body")
                    .append("svg")
                    .attr("width",width)
                    .attr("height",height)
                    .style("border","1px solid #000");
        
        var force = d3.layout.force()
                .nodes(nodes)        //指定节点数组
                .links(edges)        //指定连线数组
                .size([width,height])    //指定范围
                .linkDistance(150)    //指定连线长度
                .charge(-3000);    //相互之间的作用力

        force.start();    //开始作用

        console.log(nodes);
        console.log(edges);
        
        //添加连线        
        var svg_edges = svg.selectAll("line")
                            .data(edges)
                            .enter()
                            .append("line")
                            .style("stroke","#ccc")
                            .style("stroke-width",1);
        
        var color = d3.scale.category20();
                
        //添加节点            
        var svg_nodes = svg.selectAll("circle")
                            .data(nodes)
                            .enter()
                            .append("circle")
                            .attr("r",20)
                            .style("fill",function(d,i){
                                return color(i);
                            })
                            .call(force.drag);    //使得节点能够拖动

        //添加描述节点的文字
        var svg_texts = svg.selectAll("text")
                            .data(nodes)
                            .enter()
                            .append("text")
                            .style("fill", "black")
                            .attr("dx", 20)
                            .attr("dy", 8)
                            .text(function(d){
                                return d.name;
                            });
                    

        force.on("tick", function(){    //对于每一个时间间隔
        
             //更新连线坐标
             svg_edges.attr("x1",function(d){ return d.source.x; })
                     .attr("y1",function(d){ return d.source.y; })
                     .attr("x2",function(d){ return d.target.x; })
                     .attr("y2",function(d){ return d.target.y; });
             
             //更新节点坐标
             svg_nodes.attr("cx",function(d){ return d.x; })
                     .attr("cy",function(d){ return d.y; });

             //更新文字坐标
             svg_texts.attr("x", function(d){ return d.x; })
                 .attr("y", function(d){ return d.y; });
        });
        
          
        </script>  
        
    </body>  
</html>  
阅读 7.5k
1 个回答

  查了很多文档,表示很少人有这方面需求。。。
  最终想了个土办法,在每次tick的时候强制把节点位置拉回svg范围内,缺点是每次都要计算,如果节点多的话估计效率影响较大,但优点是动画效果看起来比较不突兀;也可以在force.on("end",function(){})里,仅最后一次校准,缺点就是动画很突兀,在最后要静止的时候,又回光返照了下,吓死人

主要修改下面两个地方↓

        force.on("tick", function(){    //对于每一个时间间隔
             //更新连线坐标
             svg_edges.attr("x1",function(d){ return validateXY(d.source.x,'x'); })
                     .attr("y1",function(d){ return validateXY(d.source.y,'y'); })
                     .attr("x2",function(d){ return validateXY(d.target.x,'x'); })
                     .attr("y2",function(d){ return validateXY(d.target.y,'y'); });
             
             //更新节点坐标
             svg_nodes.attr("cx",function(d){ return validateXY(d.x,'x'); })
                     .attr("cy",function(d){ return validateXY(d.y,'y'); });

             //更新文字坐标
             svg_texts.attr("x", function(d){ return validateXY(d.x,'x'); })
                 .attr("y", function(d){ return validateXY(d.y,'y'); });
        });
        
        //校验坐标是否在范围内,20为圆的半径
        function validateXY(val,type){
            var r = 20;
            if(val < r) return r;
            if(type=='x'){
               if(val > this.width - r) return this.width - r
            }else{
               if(val > this.height - r) return this.height - r
            }
            return val
        }

诸位大牛如果有更好的方法望不惜赐教。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题