37

I have a force layout that I have created using d3.js

I would like to have both the normal functionality of a draggable force layout as well as the ability to zoom.

I have basically copy/pasted the zooming code from (http://jsfiddle.net/nrabinowitz/QMKm3/). This is the same way of zooming that Mike Bostock uses in (http://bl.ocks.org/mbostock/3680957).

Here is my code: http://jsfiddle.net/kM4Hs/6/

The zooming works fine, but I am unable to select single nodes in the force layout and drag them around.

I have found the culprit to be the fact that both authors use d3.v2.js rather than the newer d3.v3.js. When I change my import to v2 it works perfectly. However, I would like to use v3 if possible.

<script type='text/javascript' src='http://d3js.org/d3.v3.min.js'></script>

versus

<script type='text/javascript' src='http://d3js.org/d3.v2.min.js'></script>

why does v3 break the force layout when v2 doesn't, and more importantly, what can I do to fix it?

Thanks in advance!

Matthew Herbst
  • 29,477
  • 23
  • 85
  • 128

1 Answers1

81

If you peruse the release notes, you’ll see a full explanation of everything that’s changed between the final release of 2.x (2.10.3) and the most recent release, 3.2.7. In particular, from release 3.2.2:

Better handling of drag gestures in d3.behavior.drag, d3.behavior.zoom and d3.svg.brush by not preventing default behaviors or stopping propagation. For example, mousedown now changes focus, mouseup outside an iframe works correctly, and touchstart does not stutter.

So, in V2, the drag behavior could take priority over the zoom behavior by stopping propagation on zoom events. In V3, this no longer happens automatically, giving you the choice of which behavior takes priority, and when.

If you want to give the drag behavior priority when dragging nodes, then you need to stopPropagation on input events while dragging so that these events are not simultaneously interpreted as panning by the zoom behavior. Stopping propagation on dragstart is sufficient:

var drag = d3.behavior.drag()
    .on("dragstart", function() { d3.event.sourceEvent.stopPropagation(); })
    .on("drag", function() { /* handle drag event here */ });

If using a force layout, the code is:

var drag = force.drag()
    .on("dragstart", function() { d3.event.sourceEvent.stopPropagation(); });

Working example:

drag and zoom

Note: combining these two behaviors means that gesture interpretation is ambiguous and highly sensitive to position. A click on a circle is interpreted as dragging that circle, whereas a click one pixel away could be interpreted as panning the background. A more robust method of combining these behaviors is to employ modality. For example, if the user holds down the SPACE key, clicking and dragging is interpreted as panning, rather than dragging, regardless of the click location. This approach is commonly employed in commercial software such as Adobe Photoshop.

Denilson Sá Maia
  • 47,466
  • 33
  • 109
  • 111
mbostock
  • 51,423
  • 13
  • 175
  • 129
  • I've realized that doing this on a force layout seems to override the natural way the layout responds to the movement of a node by readjusting some of the other nodes/edges. I half fixed this by having the drag method also update all the links, so that was easy enough to fix. The part I can't figure out is fixing the force functionality. I tried doing a force.resume() at the end of dragended, but while that resumed force functionality, it also reset the positions of any nodes I had dragged (I am setting d.fixed=true at the end of a drag, since I want dragged nodes to be fixed). – Matthew Herbst Jul 31 '13 at 22:48
  • 6
    Take a look at the [sticky force layout example](http://bl.ocks.org/mbostock/3750558/5093e88c0462173a3d7b5859d7db75fbf5a7d8b8) and the documentation for [force.drag](https://github.com/mbostock/d3/wiki/Force-Layout#wiki-drag). I just updated them. – mbostock Aug 04 '13 at 16:08
  • @mbstock thanks. sticky force layout example saved me tons of time! – UserBSS1 Sep 30 '13 at 09:09
  • does someone know why the zooming works perfect in webkit but in firefox the pinch gesture increase the zooming factor of the whole page? i tried to fix the problem with a meta viewport attribut, but it doesnt help. – Hansinger Nov 11 '13 at 21:16
  • 4
    I was also having issues with force layout. From reading the links posted by @mbostock the answer for me was creating `var drag` from `force.drag` instead of `d3.behavior.drag` – anderspitman Aug 22 '14 at 19:20
  • thank you for the example. I am having trouble implementing basic zoom behavior and it seems like I'm missing the `rect` with the `pointer-events: all;` -- is that required for zoom? can you point me to docs that explain it? I can't find anything about that. thank you. – isapir Jul 06 '15 at 21:25
  • the `rect` is used to capture the zoom event regardless of where you use it in the svg - it effectively puts a box around the whole thing saying "capture the mouse events in this box" (and that includes zoom). – JabbaWook Apr 11 '16 at 12:31