Why does d3.js v3 break my force graph when implementing zooming when v2 doesn’t?

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.

Leave a Comment