Is there a way to zoom into a D3 force layout graph?

JavascriptJqueryd3.jsZoomingForce Layout

Javascript Problem Overview


D3 has a force directed layout here. Is there a way to add zooming to this graph? Currently, I was able to capture the mouse wheel event but am not really sure how to write the redraw function itself. Any suggestions?

var vis = d3.select("#graph")
  .append("svg:svg")
  .call(d3.behavior.zoom().on("zoom", redraw)) // <-- redraw function
  .attr("width", w)
  .attr("height", h);

Javascript Solutions


Solution 1 - Javascript

Update 6/4/14

See also Mike Bostock's answer here for changes in D3 v.3 and the related example. I think this probably supersedes the answer below.

Update 2/18/2014

I think @ahaarnos's answer is preferable if you want the entire SVG to pan and zoom. The nested g elements in my answer below are really only necessary if you have non-zooming elements in the same SVG (not the case in the original question). If you do apply the behavior to a g element, then a background rect or similar element is required to ensure that the g receives pointer events.

Original Answer

I got this working based on the zoom-pan-transform example - you can see my jsFiddle here: http://jsfiddle.net/nrabinowitz/QMKm3/

It was a bit more complex than I had hoped - you have to nest several g elements to get it to work, set the SVG's pointer-events attribute to all, and then append a background rectangle to receive the pointer events (otherwise it only works when the pointer is over a node or link). The redraw function is comparatively simple, just setting a transform on the innermost g:

var vis = d3.select("#chart")
  .append("svg:svg")
    .attr("width", w)
    .attr("height", h)
    .attr("pointer-events", "all")
  .append('svg:g')
    .call(d3.behavior.zoom().on("zoom", redraw))
  .append('svg:g');

vis.append('svg:rect')
    .attr('width', w)
    .attr('height', h)
    .attr('fill', 'white');

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

This effectively scales the entire SVG, so it scales stroke width as well, like zooming in on an image.

There is another example that illustrates a similar technique.

Solution 2 - Javascript

Why the nested <g>'s?

This code below worked well for me (only one <g>, with no random large white <rect>:

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 + ")");
}

Where all the elements in your svg are then appended to the vis element.

Solution 3 - Javascript

The provided answers work in D3 v2 but not in v3. I've synthesized the responses into a clean solution and resolved the v3 issue using the answer provided here: https://stackoverflow.com/questions/17953106/why-does-d3-js-v3-break-my-force-graph-when-implementing-zooming-when-v2-doesnt

First the main code. This is a cleaned up version of @ahaarnos' answer:

	var svg = d3.select("body")
		.append("svg")
		.attr("width", width)
		.attr("height", height)
			.call(d3.behavior.zoom().on("zoom", redraw))
		.append('g');

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

Now you have pan and zoom, but you won't be able to drag nodes because the pan functionality will override the drag functionality. So we need to do this:

var drag = force.stop().drag()
.on("dragstart", function(d) {
	d3.event.sourceEvent.stopPropagation(); // to prevent pan functionality from 
                                            //overriding node drag functionality.
	// put any other 'dragstart' actions here
});

Here's @nrabinowitz' fiddle modified to use this cleaner zoom implementation, but illustrating how D3v3 breaks node drag: http://jsfiddle.net/QMKm3/718/

And here's the same fiddle modified to work with D3v3: http://jsfiddle.net/QMKm3/719/

Solution 4 - Javascript

I got my graph to work without the second "svg:g" append.

[...].attr("pointer-events", "all")
     .attr("width", width2)
     .attr("height", height2)
     .append('svg:g')
     .call(d3.behavior.zoom().on("zoom", redraw));

The rest is the same.

Solution 5 - Javascript

I got a solution for D3 force directed graph with zooming option.

    var m = [40, 240, 40, 240],
    width = 960,
    height = 700,
    root;
var svg = d3.select("body").append("svg")
    .attr("class", "svg_container")
    .attr("width", width)
    .attr("height", height)
    .style("overflow", "scroll")
    .style("background-color", "#EEEEEE")
    .append("svg:g")
    .attr("class", "drawarea")
    .append("svg:g")
    .attr("transform", "translate(" + m[3] + "," + m[0] + ")");

//applying zoom in&out for svg
d3.select("svg") 
.call(d3.behavior.zoom()
    .scaleExtent([0.5, 5])
    .on("zoom", zoom));

//zooming 
function zoom() { //zoom in&out function 
    var scale = d3.event.scale,
        translation = d3.event.translate,
        tbound = -height * scale,
        bbound = height * scale,
        lbound = (-width + m[1]) * scale,
        rbound = (width - m[3]) * scale;
    // limit translation to thresholds
    translation = [
        Math.max(Math.min(translation[0], rbound), lbound),
        Math.max(Math.min(translation[1], bbound), tbound)
    ];
    d3.select(".drawarea")
        .attr("transform", "translate(" + translation + ")" +
            " scale(" + scale + ")");
}

Solution 6 - Javascript

If you want to zoom and pan force layout without changing node-size, try below. You can also drag nodes without trembling. This code is based on original force layout example. As for nodes and links data, please refer to original sample data. http://bl.ocks.org/mbostock/4062045

Plz note the variables xScale and yScale, the functions dragstarted(), dragged(), and dragended(). Function tick() was changed as well.

You can see the result at http://steelblue.tistory.com/9 The language on the site is Korean. However you can easily find the result at the third example on the page.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionLegendView Question on Stackoverflow
Solution 1 - JavascriptnrabinowitzView Answer on Stackoverflow
Solution 2 - JavascriptaaaronicView Answer on Stackoverflow
Solution 3 - JavascriptDavid MarxView Answer on Stackoverflow
Solution 4 - JavascriptcemView Answer on Stackoverflow
Solution 5 - JavascriptAravind CheekkallurView Answer on Stackoverflow
Solution 6 - Javascriptseungbum kimView Answer on Stackoverflow