57

I have a graph using force layout, but it has a fixed width w and height h:

var svg = d3.select("#viz").append("svg")
            .attr("id", "playgraph")
            .attr("width", w)
            .attr("height", h)

var force = d3.layout.force()
              .nodes(nodes)
              .links(links)
              .charge(-1600)
              .linkDistance(45)
              .size([w, h]); 

which results in a svg graph that does not scale or down despite of changes in screen or browser window size. In order to make it responsive (i.e. automatically resizes itself), I tried using viewBox and preserveAspectRatio attributes:

var svg = d3.select("#viz").append("svg")
            .attr("id", "playgraph")
            .attr("width", w)
            .attr("height", h)
            .attr("viewBox", "0, 0, 600, 400")
            .attr("preserveAspectRatio", "xMidYMid meet");

Unfortunately, this didn't work as nothing happens when I adjust the browser window size. I wonder if the .size([w, h]) of the force graph has anything to do with this.

Please shed some light on how to use viewBox and preserveAspectRatio attributes with force layout graphs.

VividD
  • 10,456
  • 6
  • 64
  • 111
MLister
  • 10,022
  • 18
  • 64
  • 92
  • You may want to also attach your redraw function to `window.onresize` – Wex Aug 14 '12 at 15:57
  • This question is fairly old, but for those who find it useful, you may also want to consider also restricting the nodes to a bounded box. On resize, the nodes will stay on screen. Example: http://bl.ocks.org/mbostock/1129492 – joshfindit Aug 01 '16 at 16:25

3 Answers3

62

The problem is not within .size(), it's that you are stating the SVG dimensions in .attr("width", w) .attr("height", h). Remove these two attributes and you'll get it right...

var svg = d3.select("#viz").append("svg")
            .attr("id", "playgraph")
             //better to keep the viewBox dimensions with variables
            .attr("viewBox", "0 0 " + w + " " + h )
            .attr("preserveAspectRatio", "xMidYMid meet");

http://jsfiddle.net/aaSjd/

methodofaction
  • 70,885
  • 21
  • 151
  • 164
  • thanks! So now the svg image resizes. However, without setting `attr("width", ...)` and `attr("height", ...)`, the width/height of the image seems to be the width/height of current browser window. Is there any way I can set these two dimensions of the `svg` element somewhere else, without affecting `viewBox` and `preserveAspectRatio` attributes? – MLister Aug 14 '12 at 16:08
  • 5
    `` should get you started. – methodofaction Aug 14 '12 at 23:09
  • Just a warning ; `viewBox` attribute is case-sensitive! Notice it is should be `camelCase`. Using an attribute `viewbox` will not work. Also crosslinking other similar questions/answers: https://stackoverflow.com/questions/9400615/whats-the-best-way-to-make-a-d3-js-visualisation-layout-responsive https://stackoverflow.com/questions/44833788/making-svg-container-100-width-and-height-of-parent-container-in-d3-v4-instead/44848367#44848367 – Nate Anderson Sep 26 '19 at 22:17
10

The solution shown here: http://bl.ocks.org/mbostock/3355967 worked well for me!

window.addEventListener('resize', resize); 

function resize() {
    var width = window.innerWidth, height = window.innerHeight;
    svg.attr("width", width).attr("height", height);
    force.size([width, height]).resume();
}

Make sure to run resize() after you have appended all your lines, nodes etc as well.

Matt Jensen
  • 1,495
  • 17
  • 21
6

Duopixel is very close to what I needed, except I don't know why he nested two <g> elements and attached the event listeners to the outermost <g> (also requiring an invisible rectangle behind everything to make the g react to events over its whole space).

It's easier to attach the listeners to the <svg> itself and then you only need one internal <g>.

Here is my full-screen force-directed example:

var width = 1000,
    height = 1000;

var color = d3.scale.category20();

var svg = d3.select("body")
    .append("svg")
      .attr({
        "width": "100%",
        "height": "100%"
      })
      .attr("viewBox", "0 0 " + width + " " + height )
      .attr("preserveAspectRatio", "xMidYMid meet")
      .attr("pointer-events", "all")
    .call(d3.behavior.zoom().on("zoom", redraw));

var vis = svg
    .append('svg:g');

function redraw() {
  vis.attr("transform",
      "translate(" + d3.event.translate + ")"
      + " scale(" + d3.event.scale + ")");
}

function draw_graph(graph) {
  var force = d3.layout.force()
      .charge(-120)
      .linkDistance(30)
      .nodes(graph.nodes)
      .links(graph.links)
      .size([width, height])
      .start();

  var link = vis.selectAll(".link")
      .data(graph.links)
      .enter().append("line")
      .attr("class", "link")
      .style("stroke-width", function(d) { return Math.sqrt(d.value); });

  var node = vis.selectAll(".node")
      .data(graph.nodes)
      .enter().append("circle")
      .attr("class", "node")
      .attr("r", 5)
      .style("fill", function(d) { return color(d.group); })
      .call(force.drag);

  node.append("title")
      .text(function(d) { return d.name; });

  force.on("tick", function() {
    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; });
  });
};

draw_graph(data);
aaaronic
  • 435
  • 5
  • 7