代码之家  ›  专栏  ›  技术社区  ›  Navin Leon

基于边界动态设置初始d3缩放-V4

  •  6
  • Navin Leon  · 技术社区  · 8 年前

    我在页面上显示了大量的节点,大多数时候,由于节点的放置,圆圈会离开屏幕的可见区域。

    有没有办法根据节点的整个边界框动态设置初始缩放级别,以便所有节点都适合屏幕的可见区域?

    更新日期:

    我为这个加了一把小提琴 https://jsfiddle.net/navinleon/6ygaxoyq/3/

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");
        
        
    
      var zoom = d3.zoom()
        .scaleExtent([-8 / 2, 4])
        .on("zoom", zoomed);
        
        svg.call(zoom);
    
      var g = svg.append("g");
    
      var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) {
          return d.id;
        }))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));
    
      var graph = {
        "nodes": [{
          "id": "Myriel",
          "group": 1
        }, {
          "id": "Napoleon",
          "group": 1
        }, {
          "id": "Mlle.Baptistine",
          "group": 1
        }, {
          "id": "Mme.Magloire",
          "group": 1
        }, {
          "id": "CountessdeLo",
          "group": 1
        }, {
          "id": "Geborand",
          "group": 1
        }, {
          "id": "Champtercier",
          "group": 1
        }, {
          "id": "Cravatte",
          "group": 1
        }, {
          "id": "Count",
          "group": 1
        }, {
          "id": "OldMan",
          "group": 1
        }, {
          "id": "Labarre",
          "group": 2
        }, {
          "id": "Valjean",
          "group": 2
        }, {
          "id": "Marguerite",
          "group": 3
        }, {
          "id": "Mme.deR",
          "group": 2
        }, {
          "id": "Isabeau",
          "group": 2
        }, {
          "id": "Gervais",
          "group": 2
        }, {
          "id": "Tholomyes",
          "group": 3
        }, {
          "id": "Listolier",
          "group": 3
        }, {
          "id": "Fameuil",
          "group": 3
        }, {
          "id": "Blacheville",
          "group": 3
        }, {
          "id": "Favourite",
          "group": 3
        }, {
          "id": "Dahlia",
          "group": 3
        }, {
          "id": "Zephine",
          "group": 3
        }, {
          "id": "Fantine",
          "group": 3
        }, {
          "id": "Mme.Thenardier",
          "group": 4
        }, {
          "id": "Thenardier",
          "group": 4
        }, {
          "id": "Cosette",
          "group": 5
        }, {
          "id": "Javert",
          "group": 4
        }, {
          "id": "Fauchelevent",
          "group": 0
        }, {
          "id": "Bamatabois",
          "group": 2
        }, {
          "id": "Perpetue",
          "group": 3
        }, {
          "id": "Simplice",
          "group": 2
        }, {
          "id": "Scaufflaire",
          "group": 2
        }, {
          "id": "Woman1",
          "group": 2
        }, {
          "id": "Judge",
          "group": 2
        }, {
          "id": "Champmathieu",
          "group": 2
        }, {
          "id": "Brevet",
          "group": 2
        }, {
          "id": "Chenildieu",
          "group": 2
        }, {
          "id": "Cochepaille",
          "group": 2
        }, {
          "id": "Pontmercy",
          "group": 4
        }, {
          "id": "Boulatruelle",
          "group": 6
        }, {
          "id": "Eponine",
          "group": 4
        }, {
          "id": "Anzelma",
          "group": 4
        }, {
          "id": "Woman2",
          "group": 5
        }, {
          "id": "MotherInnocent",
          "group": 0
        }, {
          "id": "Gribier",
          "group": 0
        }, {
          "id": "Jondrette",
          "group": 7
        }, {
          "id": "Mme.Burgon",
          "group": 7
        }, {
          "id": "Gavroche",
          "group": 8
        }, {
          "id": "Gillenormand",
          "group": 5
        }, {
          "id": "Magnon",
          "group": 5
        }, {
          "id": "Mlle.Gillenormand",
          "group": 5
        }, {
          "id": "Mme.Pontmercy",
          "group": 5
        }, {
          "id": "Mlle.Vaubois",
          "group": 5
        }, {
          "id": "Lt.Gillenormand",
          "group": 5
        }, {
          "id": "Marius",
          "group": 8
        }, {
          "id": "BaronessT",
          "group": 5
        }, {
          "id": "Mabeuf",
          "group": 8
        }, {
          "id": "Enjolras",
          "group": 8
        }, {
          "id": "Combeferre",
          "group": 8
        }, {
          "id": "Prouvaire",
          "group": 8
        }, {
          "id": "Feuilly",
          "group": 8
        }, {
          "id": "Courfeyrac",
          "group": 8
        }, {
          "id": "Bahorel",
          "group": 8
        }, {
          "id": "Bossuet",
          "group": 8
        }, {
          "id": "Joly",
          "group": 8
        }, {
          "id": "Grantaire",
          "group": 8
        }, {
          "id": "MotherPlutarch",
          "group": 9
        }, {
          "id": "Gueulemer",
          "group": 4
        }, {
          "id": "Babet",
          "group": 4
        }, {
          "id": "Claquesous",
          "group": 4
        }, {
          "id": "Montparnasse",
          "group": 4
        }, {
          "id": "Toussaint",
          "group": 5
        }, {
          "id": "Child1",
          "group": 10
        }, {
          "id": "Child2",
          "group": 10
        }, {
          "id": "Brujon",
          "group": 4
        }, {
          "id": "Mme.Hucheloup",
          "group": 8
        }],
        "links": [{
          "source": "Napoleon",
          "target": "Myriel",
          "value": 1
        }, {
          "source": "Mlle.Baptistine",
          "target": "Myriel",
          "value": 8
        }, {
          "source": "Mme.Magloire",
          "target": "Myriel",
          "value": 10
        }]
      }
    
      var link = g.append("g")
        .attr("class", "links")
        .selectAll("line")
        .data(graph.links)
        .enter().append("line");
    
      var node = g.append("g")
        .attr("class", "nodes")
        .selectAll("circle")
        .data(graph.nodes)
        .enter().append("circle")
        .attr("r", 2.5)
        .on('click', clicked);
    
      node.append("title")
        .text(function(d) {
          return d.id;
        });
    
      simulation
        .nodes(graph.nodes)
        .on("tick", ticked);
    
      simulation.force("link")
        .links(graph.links);
    
      function ticked() {
        link
          .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;
          });
    
        node
          .attr("cx", function(d) {
            return d.x;
          })
          .attr("cy", function(d) {
            return d.y;
          })
          .attr('r',20)
      }
    
      var active = d3.select(null);
    
      function clicked(d) {
    
        if (active.node() === this){
          active.classed("active", false);
          return reset();
        }
        
        active = d3.select(this).classed("active", true);
    
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(width / 2, height / 2)
            .scale(8)
            .translate(-(+active.attr('cx')), -(+active.attr('cy')))
          );
      }
    
      function reset() {
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(0, 0)
            .scale(1)
          );
      }
    
      function zoomed() {
        g.attr("transform", d3.event.transform);
      }
    <script src="https://d3js.org/d3.v5.min.js"></script>
    
    <svg width="960" height="600"></svg>

    预期:

    enter image description here

    2 回复  |  直到 8 年前
        1
  •  6
  •   Andrew Reid    8 年前

    在完成冷却之前,您无法预测部队布局将占据的最终界限。然而,有两种可能的解决方案可以达到预期的效果。

    1. 约束布局, 或者,探索在节点接近svg边界时减少力和速度。

    2. 当力在冷却时扩展到svg的边界之外时,更改缩放。

    第一,通过在视口中对节点进行边界处理,可以达到相同的效果。然而,节点的大小不会缩小,这可能会导致相当多的混乱。有许多关于堆栈溢出的问题和答案可以处理这种方法(例如 one )。

    我不相信我以前见过第二个例子。使用d3缩放功能应该不会太难做到这一点。虽然我们无法在不运行布局的情况下预测布局的大小,但我们可以根据任何给定时间点的力的大小动态缩放。为此,我们大体上可以采用与缩放单个节点相同的方法:应用新的缩放标识。

    但是,与缩放到节点时不同,我们需要确定比例。要确定比例,我们需要找到force布局的边界,并将其与svg的边界进行比较。我会用不同的方法 answer ,但这两种方法都应该可以很好地工作(我不确定哪种性能更好)。

    首先我们得到x和y坐标的范围:

     var xExtent = d3.extent(node.data(), function(d) { return d.x; });
    var yExtent = d3.extent(node.data(), function(d) { return d.y; });
    

    我们也可以调整半径,我只是用节点中心来回答这个问题

    接下来,我们得到x和y的刻度:

    var xScale = width/(xExtent[1]-xExtent[0]);
    var yScale = height/(yExtent[1]-yExtent[0]);
    

    然后我们找出哪个更具限制性,并使用该尺度:

    var minScale = Math.min(xScale,yScale);
    

    现在,我们像缩放到点时一样设置缩放标识,但我们要以力布局的中间为中心的点(我们可以使用刚才计算的范围来确定中间),比例是刚才确定的比例。但是,我们仅在满足某些条件的情况下应用更改—在我下面的示例中,如果节点超出svg的边界,就会应用更改:

    if(minScale < 1) {
       var transform = d3.zoomIdentity.translate(width/2,height/2)
        .scale(minScale)
        .translate(-(xExtent[0]+xExtent[1])/2,-(yExtent[0]+yExtent[1])/2)
      svg.call(zoom.transform, transform);
    }
    

    下面是这种方法的演示,嵌入在tick函数中:

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");
    
      var zoom = d3.zoom()
        .scaleExtent([-8 / 2, 4])
        .on("zoom", zoomed);
        
        svg.call(zoom);
    
      var g = svg.append("g");
    
      var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) {
          return d.id;
        }))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));
    
      var graph = {
        "nodes": [{
          "id": "Myriel",
          "group": 1
        }, {
          "id": "Napoleon",
          "group": 1
        }, {
          "id": "Mlle.Baptistine",
          "group": 1
        }, {
          "id": "Mme.Magloire",
          "group": 1
        }, {
          "id": "CountessdeLo",
          "group": 1
        }, {
          "id": "Geborand",
          "group": 1
        }, {
          "id": "Champtercier",
          "group": 1
        }, {
          "id": "Cravatte",
          "group": 1
        }, {
          "id": "Count",
          "group": 1
        }, {
          "id": "OldMan",
          "group": 1
        }, {
          "id": "Labarre",
          "group": 2
        }, {
          "id": "Valjean",
          "group": 2
        }, {
          "id": "Marguerite",
          "group": 3
        }, {
          "id": "Mme.deR",
          "group": 2
        }, {
          "id": "Isabeau",
          "group": 2
        }, {
          "id": "Gervais",
          "group": 2
        }, {
          "id": "Tholomyes",
          "group": 3
        }, {
          "id": "Listolier",
          "group": 3
        }, {
          "id": "Fameuil",
          "group": 3
        }, {
          "id": "Blacheville",
          "group": 3
        }, {
          "id": "Favourite",
          "group": 3
        }, {
          "id": "Dahlia",
          "group": 3
        }, {
          "id": "Zephine",
          "group": 3
        }, {
          "id": "Fantine",
          "group": 3
        }, {
          "id": "Mme.Thenardier",
          "group": 4
        }, {
          "id": "Thenardier",
          "group": 4
        }, {
          "id": "Cosette",
          "group": 5
        }, {
          "id": "Javert",
          "group": 4
        }, {
          "id": "Fauchelevent",
          "group": 0
        }, {
          "id": "Bamatabois",
          "group": 2
        }, {
          "id": "Perpetue",
          "group": 3
        }, {
          "id": "Simplice",
          "group": 2
        }, {
          "id": "Scaufflaire",
          "group": 2
        }, {
          "id": "Woman1",
          "group": 2
        }, {
          "id": "Judge",
          "group": 2
        }, {
          "id": "Champmathieu",
          "group": 2
        }, {
          "id": "Brevet",
          "group": 2
        }, {
          "id": "Chenildieu",
          "group": 2
        }, {
          "id": "Cochepaille",
          "group": 2
        }, {
          "id": "Pontmercy",
          "group": 4
        }, {
          "id": "Boulatruelle",
          "group": 6
        }, {
          "id": "Eponine",
          "group": 4
        }, {
          "id": "Anzelma",
          "group": 4
        }, {
          "id": "Woman2",
          "group": 5
        }, {
          "id": "MotherInnocent",
          "group": 0
        }, {
          "id": "Gribier",
          "group": 0
        }, {
          "id": "Jondrette",
          "group": 7
        }, {
          "id": "Mme.Burgon",
          "group": 7
        }, {
          "id": "Gavroche",
          "group": 8
        }, {
          "id": "Gillenormand",
          "group": 5
        }, {
          "id": "Magnon",
          "group": 5
        }, {
          "id": "Mlle.Gillenormand",
          "group": 5
        }, {
          "id": "Mme.Pontmercy",
          "group": 5
        }, {
          "id": "Mlle.Vaubois",
          "group": 5
        }, {
          "id": "Lt.Gillenormand",
          "group": 5
        }, {
          "id": "Marius",
          "group": 8
        }, {
          "id": "BaronessT",
          "group": 5
        }, {
          "id": "Mabeuf",
          "group": 8
        }, {
          "id": "Enjolras",
          "group": 8
        }, {
          "id": "Combeferre",
          "group": 8
        }, {
          "id": "Prouvaire",
          "group": 8
        }, {
          "id": "Feuilly",
          "group": 8
        }, {
          "id": "Courfeyrac",
          "group": 8
        }, {
          "id": "Bahorel",
          "group": 8
        }, {
          "id": "Bossuet",
          "group": 8
        }, {
          "id": "Joly",
          "group": 8
        }, {
          "id": "Grantaire",
          "group": 8
        }, {
          "id": "MotherPlutarch",
          "group": 9
        }, {
          "id": "Gueulemer",
          "group": 4
        }, {
          "id": "Babet",
          "group": 4
        }, {
          "id": "Claquesous",
          "group": 4
        }, {
          "id": "Montparnasse",
          "group": 4
        }, {
          "id": "Toussaint",
          "group": 5
        }, {
          "id": "Child1",
          "group": 10
        }, {
          "id": "Child2",
          "group": 10
        }, {
          "id": "Brujon",
          "group": 4
        }, {
          "id": "Mme.Hucheloup",
          "group": 8
        }],
        "links": [{
          "source": "Napoleon",
          "target": "Myriel",
          "value": 1
        }, {
          "source": "Mlle.Baptistine",
          "target": "Myriel",
          "value": 8
        }, {
          "source": "Mme.Magloire",
          "target": "Myriel",
          "value": 10
        }]
      }
    
      var link = g.append("g")
        .attr("class", "links")
        .selectAll("line")
        .data(graph.links)
        .enter().append("line");
    
      var node = g.append("g")
        .attr("class", "nodes")
        .selectAll("circle")
        .data(graph.nodes)
        .enter().append("circle")
        .attr("r", 2.5)
        .on('click', clicked);
    
      node.append("title")
        .text(function(d) {
          return d.id;
        });
    
      simulation
        .nodes(graph.nodes)
        .on("tick", ticked);
    
      simulation.force("link")
        .links(graph.links);
    
      function ticked() {
         // set up zoom transform:
         var xExtent = d3.extent(node.data(), function(d) { return d.x; });
         var yExtent = d3.extent(node.data(), function(d) { return d.y; });
                
         // get scales:    
         var xScale = width/(xExtent[1] - xExtent[0]);
         var yScale = height/(yExtent[1] - yExtent[0]);
         
         // get most restrictive scale
         var minScale = Math.min(xScale,yScale);
            
         if (minScale < 1) {
          var transform = d3.zoomIdentity.translate(width/2,height/2)
             .scale(minScale)
             .translate(-(xExtent[0]+xExtent[1])/2,-(yExtent[0]+yExtent[1])/2)
           svg.call(zoom.transform, transform);   
         }
    
        link
          .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;
          });
    
        node
          .attr("cx", function(d) {
            return d.x;
          })
          .attr("cy", function(d) {
            return d.y;
          })
          .attr('r',20)
      }
    
      var active = d3.select(null);
    
      function clicked(d) {
    
        if (active.node() === this){
          active.classed("active", false);
          return reset();
        }
        
        active = d3.select(this).classed("active", true);
    
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(width / 2, height / 2)
            .scale(8)
            .translate(-(+active.attr('cx')), -(+active.attr('cy')))
          );
      }
    
      function reset() {
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(0, 0)
            .scale(1)
          );
      }
    
      function zoomed() {
        g.attr("transform", d3.event.transform);
      }
    <script src="https://d3js.org/d3.v5.min.js"></script>
    
    <svg width="960" height="600"></svg>

    上面的问题是,在模拟运行期间,鼠标事件基本上被忽略了-勾号事件运行得足够快,可以有效地覆盖由于鼠标导航而产生的任何更改。

    有几个潜在的解决方案:

    • 当可视化冷却到足以使用鼠标导航时,停止自动缩放

    • 启动用户缩放时停止自动缩放

    • 在力冷却之前不要启用用户缩放

    我将在这里快速实现第一个,因为它可能是最简单的。我还将通过一个常数因子来减小比例,以提供一点余量,这样当自动缩放停止时,节点应该保持在视图中。在鼠标导航不会导致可见更改的情况下(从等待开始,更改指针),我也更改了光标:

    var svg = d3.select("svg"),
        width = +svg.attr("width"),
        height = +svg.attr("height");
    
      var zoom = d3.zoom()
        .scaleExtent([-8 / 2, 4])
        .on("zoom", zoomed);
        
        svg.call(zoom);
    
      var g = svg.append("g");
    
      var simulation = d3.forceSimulation()
        .force("link", d3.forceLink().id(function(d) {
          return d.id;
        }))
        .force("charge", d3.forceManyBody())
        .force("center", d3.forceCenter(width / 2, height / 2));
    
      var graph = {
        "nodes": [{
          "id": "Myriel",
          "group": 1
        }, {
          "id": "Napoleon",
          "group": 1
        }, {
          "id": "Mlle.Baptistine",
          "group": 1
        }, {
          "id": "Mme.Magloire",
          "group": 1
        }, {
          "id": "CountessdeLo",
          "group": 1
        }, {
          "id": "Geborand",
          "group": 1
        }, {
          "id": "Champtercier",
          "group": 1
        }, {
          "id": "Cravatte",
          "group": 1
        }, {
          "id": "Count",
          "group": 1
        }, {
          "id": "OldMan",
          "group": 1
        }, {
          "id": "Labarre",
          "group": 2
        }, {
          "id": "Valjean",
          "group": 2
        }, {
          "id": "Marguerite",
          "group": 3
        }, {
          "id": "Mme.deR",
          "group": 2
        }, {
          "id": "Isabeau",
          "group": 2
        }, {
          "id": "Gervais",
          "group": 2
        }, {
          "id": "Tholomyes",
          "group": 3
        }, {
          "id": "Listolier",
          "group": 3
        }, {
          "id": "Fameuil",
          "group": 3
        }, {
          "id": "Blacheville",
          "group": 3
        }, {
          "id": "Favourite",
          "group": 3
        }, {
          "id": "Dahlia",
          "group": 3
        }, {
          "id": "Zephine",
          "group": 3
        }, {
          "id": "Fantine",
          "group": 3
        }, {
          "id": "Mme.Thenardier",
          "group": 4
        }, {
          "id": "Thenardier",
          "group": 4
        }, {
          "id": "Cosette",
          "group": 5
        }, {
          "id": "Javert",
          "group": 4
        }, {
          "id": "Fauchelevent",
          "group": 0
        }, {
          "id": "Bamatabois",
          "group": 2
        }, {
          "id": "Perpetue",
          "group": 3
        }, {
          "id": "Simplice",
          "group": 2
        }, {
          "id": "Scaufflaire",
          "group": 2
        }, {
          "id": "Woman1",
          "group": 2
        }, {
          "id": "Judge",
          "group": 2
        }, {
          "id": "Champmathieu",
          "group": 2
        }, {
          "id": "Brevet",
          "group": 2
        }, {
          "id": "Chenildieu",
          "group": 2
        }, {
          "id": "Cochepaille",
          "group": 2
        }, {
          "id": "Pontmercy",
          "group": 4
        }, {
          "id": "Boulatruelle",
          "group": 6
        }, {
          "id": "Eponine",
          "group": 4
        }, {
          "id": "Anzelma",
          "group": 4
        }, {
          "id": "Woman2",
          "group": 5
        }, {
          "id": "MotherInnocent",
          "group": 0
        }, {
          "id": "Gribier",
          "group": 0
        }, {
          "id": "Jondrette",
          "group": 7
        }, {
          "id": "Mme.Burgon",
          "group": 7
        }, {
          "id": "Gavroche",
          "group": 8
        }, {
          "id": "Gillenormand",
          "group": 5
        }, {
          "id": "Magnon",
          "group": 5
        }, {
          "id": "Mlle.Gillenormand",
          "group": 5
        }, {
          "id": "Mme.Pontmercy",
          "group": 5
        }, {
          "id": "Mlle.Vaubois",
          "group": 5
        }, {
          "id": "Lt.Gillenormand",
          "group": 5
        }, {
          "id": "Marius",
          "group": 8
        }, {
          "id": "BaronessT",
          "group": 5
        }, {
          "id": "Mabeuf",
          "group": 8
        }, {
          "id": "Enjolras",
          "group": 8
        }, {
          "id": "Combeferre",
          "group": 8
        }, {
          "id": "Prouvaire",
          "group": 8
        }, {
          "id": "Feuilly",
          "group": 8
        }, {
          "id": "Courfeyrac",
          "group": 8
        }, {
          "id": "Bahorel",
          "group": 8
        }, {
          "id": "Bossuet",
          "group": 8
        }, {
          "id": "Joly",
          "group": 8
        }, {
          "id": "Grantaire",
          "group": 8
        }, {
          "id": "MotherPlutarch",
          "group": 9
        }, {
          "id": "Gueulemer",
          "group": 4
        }, {
          "id": "Babet",
          "group": 4
        }, {
          "id": "Claquesous",
          "group": 4
        }, {
          "id": "Montparnasse",
          "group": 4
        }, {
          "id": "Toussaint",
          "group": 5
        }, {
          "id": "Child1",
          "group": 10
        }, {
          "id": "Child2",
          "group": 10
        }, {
          "id": "Brujon",
          "group": 4
        }, {
          "id": "Mme.Hucheloup",
          "group": 8
        }],
        "links": [{
          "source": "Napoleon",
          "target": "Myriel",
          "value": 1
        }, {
          "source": "Mlle.Baptistine",
          "target": "Myriel",
          "value": 8
        }, {
          "source": "Mme.Magloire",
          "target": "Myriel",
          "value": 10
        }]
      }
    
      var link = g.append("g")
        .attr("class", "links")
        .selectAll("line")
        .data(graph.links)
        .enter().append("line");
    
      var node = g.append("g")
        .attr("class", "nodes")
        .selectAll("circle")
        .data(graph.nodes)
        .enter().append("circle")
        .attr("r", 2.5)
        .on('click', clicked);
    
      node.append("title")
        .text(function(d) {
          return d.id;
        });
    
      simulation
        .nodes(graph.nodes)
        .on("tick", ticked);
    
      simulation.force("link")
        .links(graph.links);
        
      var check = true;
      svg.attr("cursor","wait")
    
      function ticked() {
       
        if(this.alpha() > 0.04) {
          
        
           // set up zoom transform:
           var xExtent = d3.extent(node.data(), function(d) { return d.x; });
           var yExtent = d3.extent(node.data(), function(d) { return d.y; });
    
           // get scales:    
           var xScale = width/(xExtent[1] - xExtent[0]) * 0.75;
           var yScale = height/(yExtent[1] - yExtent[0]) * 0.75;
    
           // get most restrictive scale
           var minScale = Math.min(xScale,yScale);
    
           if (minScale < 1) {
            var transform = d3.zoomIdentity.translate(width/2,height/2)
               .scale(minScale)
               .translate(-(xExtent[0]+xExtent[1])/2,-(yExtent[0]+yExtent[1])/2)
             svg.call(zoom.transform, transform);   
           }
         }
         else {
          svg.attr("cursor","pointer")
          if(check) console.log("check");
          var check = false;
         }
    
        link
          .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;
          });
    
        node
          .attr("cx", function(d) {
            return d.x;
          })
          .attr("cy", function(d) {
            return d.y;
          })
          .attr('r',20)
      }
    
      var active = d3.select(null);
    
      function clicked(d) {
    
        if (active.node() === this){
          active.classed("active", false);
          return reset();
        }
        
        active = d3.select(this).classed("active", true);
    
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(width / 2, height / 2)
            .scale(8)
            .translate(-(+active.attr('cx')), -(+active.attr('cy')))
          );
      }
    
      function reset() {
        svg.transition()
          .duration(750)
          .call(zoom.transform,
            d3.zoomIdentity
            .translate(0, 0)
            .scale(1)
          );
      }
    
      function zoomed() {
        g.attr("transform", d3.event.transform);
      }
    <脚本src=”https://d3js.org/d3.v5.min.js“></script>
    
    <svg width=“960”height=“600”></svg>

    只有在知道最终边界的一般概念后,才能渲染力,避免自动缩放过度书写导航的任何时间。

        2
  •  1
  •   Matt O'Tousa    8 年前

    d3添加完所有数据后,检查包含元素的大小,然后相应地缩放。看见 this question about checking the size of containers in SVG

    其中一个答案实际上有一个特定于d3的实现:

     var height = d3.select('#myGroup').select('svg').node().getBBox().height;
     var width = d3.select('#myGroup').select('svg').node().getBBox().width;
    

    我把它放在这里只是因为我的d3有点生锈,但我相信你可以用现有的 g 变量:

    g.node().getBBox().height
    g.node().getBBox().width
    

    我不熟悉d3的缩放功能,但通常您会这样做:

    let scaleRatioX = containerWidth / elementWidth
    let scaleRatioY = containerHeight / elementHeight
    
    elementWidth = elementWidth * scaleRatioX
    elementHeight = elementHeight * scaleRatioY
    
    // or, if you want to scale evenly
    let scaleRatio = Math.min(scaleRatioX, scaleRatioY)
    
    elementWidth = elementWidth * scaleRatio
    elementHeight = elementHeight * scaleRatio
    
    // then set the element size to the new values
    

    d3的变焦可能会增加更多的复杂性。