How d3.js force map nodes are displayed in the border

the force force map in d3.js is recently used. To achieve the following effect, all city nodes are within visual range, and scrollbars are available if you exceed them.
clipboard.png

charge-300-3000svg
clipboard.png

ask for advice on how to limit the node to the svg frame?

the example code is as follows:

<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 -sharp000");
        
        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","-sharpccc")
                            .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>  
Nov.10,2021

looked up a lot of documents, indicating that few people have such a need.
finally came up with a native method, which forces the node position to be pulled back to the svg range every time of tick. The disadvantage is that it has to be calculated every time. If there are many nodes, the estimation efficiency has a greater impact, but the advantage is that the animation effect looks less obtrusive. You can also calibrate it only for the last time in force.on ("end", function () {}). The disadvantage is that the animation is very abrupt, and when it is finally going to rest, it comes back to light, which scares people to death

.

mainly modify the following two places

        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
        }

if you Daniel have a better way, you will not hesitate to give us advice.

Menu