如何确定力向图的整体大小

时间:2018-07-02 11:58:19

标签: javascript d3.js

我正在尝试确定D3力导向图的尺寸,以便在渲染画布后可以调整其大小。

我想我快要到了-我可以得到单个节点的大小,但是我不知道如何遍历节点以检查每个节点的边界框(或者更好地,获得图形的整体大小)在一次)。调整大小当前是在单击按钮时发生的。

下面是我的代码(作为一种临时措施,我只是将单个节点的大小乘以15,但应根据呈现的图形的尺寸对其进行动态调整):

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .link {
    fill: none;
    stroke-width: 1.5px;
  }
  
  circle {
    stroke: black;
    stroke-width: 1.5px;
  }
  
  text {
    font: 10px sans-serif;
    pointer-events: none;
  }
  
  #resizebutton {
    position: absolute;
    top: 10px;
    left: 20px;
    height: 100px;
    width: 100px;
  }
  
  #graph {
    position: absolute;
    top: 30px;
    left: 200px;
  }
</style>

<body>
  <div id="root">
    <div id="resizebutton">
      <button onclick="resizeGraph()">Resize</button>
    </div>
    <div style="background-color:lightgrey" id="graph" height="800px" width="800px">
    </div>
  </div>
</body>

<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
  var nodes = [{
      name: "node1"
    },
    {
      name: "node2"
    },
    {
      name: "node3"
    },
    {
      name: "node4"
    },
    {
      name: "node5"
    },
    {
      name: "node6"
    },
    {
      name: "node7"
    },
    {
      name: "node8"
    },
    {
      name: "node9"
    }
  ];

  var links = [{
      source: 0,
      target: 8
    },
    {
      source: 1,
      target: 8
    },
    {
      source: 2,
      target: 8
    },
    {
      source: 3,
      target: 8
    },
    {
      source: 4,
      target: 8
    },
    {
      source: 5,
      target: 8
    },
    {
      source: 6,
      target: 8
    },
    {
      source: 7,
      target: 8
    }
  ];

  var width = 800,
    height = 800;

  var force = d3.layout.force()
    .nodes(nodes)
    .links(links)
    .size([width, height])
    .linkDistance(150)
    .charge(-300)
    .on("tick", tick)
    .start();

  var svg = d3.select("#graph").append("svg")
    .attr("width", width)
    .attr("height", height);

  var colors = d3.scale.category10();

  var path = svg.append("g").selectAll("path")
    .data(force.links())
    .enter().append("line")
    .attr('class', 'link')
    .attr('stroke', function(d, i) {
      return colors(i);
    })

  var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
    .enter().append("circle")
    .attr("r", 8)
    .attr('class', 'circle')
    .attr('fill', function(d, i) {
      return colors(i);
    })
    .call(force.drag);

  var text = svg.append("g").selectAll("text")
    .data(force.nodes())
    .enter().append("text")
    .attr("x", 14)
    .attr("y", ".31em")
    .text(function(d) {
      return d.name;
    });

  function tick() {
    path.attr({
      x1: function(d) {
        return d.source.x;
      },
      y1: function(d) {
        return d.source.y;
      },
      x2: function(d) {
        return d.target.x;
      },
      y2: function(d) {
        return d.target.y;
      }
    });
    circle.attr("transform", transform);
    text.attr("transform", transform);
  }

  function transform(d) {
    return "translate(" + d.x + "," + d.y + ")";
  }

  function resizeGraph() {
    var bbox = circle.node().getBBox();
    wid = bbox.width * 15;
    hei = bbox.height * 15;
    document.getElementById("graph").style.width = wid + "px";
    document.getElementById("graph").style.height = hei + "px";
    svg.attr("width", wid + "px");
    svg.attr("height", hei + "px");
    force.size([wid, hei]).resume();
  };
</script>

非常感谢任何帮助。

2 个答案:

答案 0 :(得分:2)

无论您的目标是什么,您都不需要使用实际的DOM元素(例如,使用getBBox())来获取力图的大小,只需使用其数据即可。

在您的代码中,这将为您提供包含图表的矩形的四个角:

var minX = d3.min(circle.data(), function(d){return d.x});
var minY = d3.min(circle.data(), function(d){return d.y});
var maxX = d3.max(circle.data(), function(d){return d.x});
var maxY = d3.max(circle.data(), function(d){return d.y});

这是一个基于您的代码的演示,单击获取大小绘制容器矩形(要获得准确的矩形,请在单击之前等待模拟停止):

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .link {
    fill: none;
    stroke-width: 1.5px;
  }
  
  circle {
    stroke: black;
    stroke-width: 1.5px;
  }
  
  text {
    font: 10px sans-serif;
    pointer-events: none;
  }
  
  #resizebutton {
    position: absolute;
    top: 10px;
    left: 20px;
    height: 100px;
    width: 100px;
  }
  
  #graph {
    position: absolute;
    top: 30px;
    left: 100px;
  }
</style>

<body>
  <div id="root">
    <div id="resizebutton">
      <button onclick="resizeGraph()">Get size</button>
    </div>
    <div id="graph" height="400px" width="400px">
    </div>
  </div>
</body>

<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
  var nodes = [{
      name: "node1"
    },
    {
      name: "node2"
    },
    {
      name: "node3"
    },
    {
      name: "node4"
    },
    {
      name: "node5"
    },
    {
      name: "node6"
    },
    {
      name: "node7"
    },
    {
      name: "node8"
    },
    {
      name: "node9"
    }
  ];

  var links = [{
      source: 0,
      target: 8
    },
    {
      source: 1,
      target: 8
    },
    {
      source: 2,
      target: 8
    },
    {
      source: 3,
      target: 8
    },
    {
      source: 4,
      target: 8
    },
    {
      source: 5,
      target: 8
    },
    {
      source: 6,
      target: 8
    },
    {
      source: 7,
      target: 8
    }
  ];

  var width = 400,
    height = 400;

  var force = d3.layout.force()
    .nodes(nodes)
    .links(links)
    .size([width, height])
    .linkDistance(150)
    .charge(-300)
    .on("tick", tick)
    .start();

  var svg = d3.select("#graph").append("svg")
    .attr("width", width)
    .attr("height", height);

  var colors = d3.scale.category10();

  var path = svg.append("g").selectAll("path")
    .data(force.links())
    .enter().append("line")
    .attr('class', 'link')
    .attr('stroke', function(d, i) {
      return colors(i);
    })

  var circle = svg.append("g").selectAll("circle")
    .data(force.nodes())
    .enter().append("circle")
    .attr("r", 8)
    .attr('class', 'circle')
    .attr('fill', function(d, i) {
      return colors(i);
    })
    .call(force.drag);

  var text = svg.append("g").selectAll("text")
    .data(force.nodes())
    .enter().append("text")
    .attr("x", 14)
    .attr("y", ".31em")
    .text(function(d) {
      return d.name;
    });

  function tick() {
    path.attr({
      x1: function(d) {
        return d.source.x;
      },
      y1: function(d) {
        return d.source.y;
      },
      x2: function(d) {
        return d.target.x;
      },
      y2: function(d) {
        return d.target.y;
      }
    });
    circle.attr("transform", transform);
    text.attr("transform", transform);
  }

  function transform(d) {
    return "translate(" + d.x + "," + d.y + ")";
  }

  function resizeGraph() {
    var minX = d3.min(circle.data(), function(d) {
      return d.x
    });
    var minY = d3.min(circle.data(), function(d) {
      return d.y
    });
    var maxX = d3.max(circle.data(), function(d) {
      return d.x
    });
    var maxY = d3.max(circle.data(), function(d) {
      return d.y
    });
    svg.append("rect")
      .attr("x", minX - 8)
      .attr("y", minY - 8)
      .attr("width", maxX - minX + 16)
      .attr("height", maxY - minY + 16)
      .style("stroke", "red")
      .style("fill", "none");
  };
</script>

答案 1 :(得分:1)

与Gerardo的answer相反,我实际上喜欢您使用边界框的方法。您可以轻松地保留对包含圆圈的<g>的引用,并从中获取边界框。由于组的大小和位置由其内容固有地确定,因此其边界框也是其中所有圆的边界框。

实现非常简单:

var circles = svg.append("g");             // Keep the reference to the enclosing g
var circle = circles.selectAll("circle")   // Append to that group

从那里开始,您可以通过以下操作获得所有圈子的总体范围:

var bbox = circles.node().getBBox();

借用Gerardo的实现并插入上面的代码将导致以下工作演示。

<!DOCTYPE html>
<meta charset="utf-8">
<style>
  .link {
    fill: none;
    stroke-width: 1.5px;
  }
  
  circle {
    stroke: black;
    stroke-width: 1.5px;
  }
  
  text {
    font: 10px sans-serif;
    pointer-events: none;
  }
  
  #resizebutton {
    position: absolute;
    top: 10px;
    left: 20px;
    height: 100px;
    width: 100px;
  }
  
  #graph {
    position: absolute;
    top: 30px;
    left: 100px;
  }
</style>

<body>
  <div id="root">
    <div id="resizebutton">
      <button onclick="resizeGraph()">Get size</button>
    </div>
    <div id="graph" height="400px" width="400px">
    </div>
  </div>
</body>

<script src="https://d3js.org/d3.v3.min.js"></script>
<script>
  var nodes = [{
      name: "node1"
    },
    {
      name: "node2"
    },
    {
      name: "node3"
    },
    {
      name: "node4"
    },
    {
      name: "node5"
    },
    {
      name: "node6"
    },
    {
      name: "node7"
    },
    {
      name: "node8"
    },
    {
      name: "node9"
    }
  ];

  var links = [{
      source: 0,
      target: 8
    },
    {
      source: 1,
      target: 8
    },
    {
      source: 2,
      target: 8
    },
    {
      source: 3,
      target: 8
    },
    {
      source: 4,
      target: 8
    },
    {
      source: 5,
      target: 8
    },
    {
      source: 6,
      target: 8
    },
    {
      source: 7,
      target: 8
    }
  ];

  var width = 400,
    height = 400;

  var force = d3.layout.force()
    .nodes(nodes)
    .links(links)
    .size([width, height])
    .linkDistance(150)
    .charge(-300)
    .on("tick", tick)
    .start();

  var svg = d3.select("#graph").append("svg")
    .attr("width", width)
    .attr("height", height);

  var colors = d3.scale.category10();

  var path = svg.append("g").selectAll("path")
    .data(force.links())
    .enter().append("line")
    .attr('class', 'link')
    .attr('stroke', function(d, i) {
      return colors(i);
    })

  var circles = svg.append("g");
  var circle = circles.selectAll("circle")
    .data(force.nodes())
    .enter().append("circle")
    .attr("r", 8)
    .attr('class', 'circle')
    .attr('fill', function(d, i) {
      return colors(i);
    })
    .call(force.drag);

  var text = svg.append("g").selectAll("text")
    .data(force.nodes())
    .enter().append("text")
    .attr("x", 14)
    .attr("y", ".31em")
    .text(function(d) {
      return d.name;
    });

  function tick() {
    path.attr({
      x1: function(d) {
        return d.source.x;
      },
      y1: function(d) {
        return d.source.y;
      },
      x2: function(d) {
        return d.target.x;
      },
      y2: function(d) {
        return d.target.y;
      }
    });
    circle.attr("transform", transform);
    text.attr("transform", transform);
  }

  function transform(d) {
    return "translate(" + d.x + "," + d.y + ")";
  }

  function resizeGraph() {
    var bbox = circles.node().getBBox();
    var rect = svg.append("rect")
      .style("stroke", "red")
      .style("fill", "none")
      .attr(bbox);
  };
</script>

与Gerardo的方法相比,一个好处是,该解决方案将自动处理所有的计算,即,它将考虑圆的半径。如果圆的大小不同,而仅知道圆心还不够,这将很方便。

如果您还希望文本成为整体边框的一部分,只需将<g>环绕在圆圈上,然后使用该外部组即可。