d3.jszoomingpanobservablehq

Cannot pan SVG when the cursor is on the svg itself in D3.js


I've made a simple Zoom & Pan notebook in ObservableHQ.com with D3.js I have two questions:

https://observablehq.com/d/c63434913a56fbb2

  1. If the curson is on the black square (i.e. SVG), it dosen't click and drag anymore! How to allow drag at all time?
  2. How to disable zoom function when mouse is scroling and keep only panning?

Thank you very much!

I'tried different code snnipets, but the simpler one I found don't behave like I would like.


Solution

  • You’re using d3.drag and d3.zoom, which both include functionality to let you drag stuff around. d3.drag lets you drag individual elements with finer control (like rearranging circles); d3.zoom only lets you drag the whole scene. They can be combined, but you only need one or the other here.

    In your notebook, the d3.zoom piece is working, but doing more than you want it to (zooming and panning when you only want panning); the d3.drag piece is broken.

    You call d3.zoom on the whole SVG, and d3.drag on just the group g. The group contains the bigger black-stroked empty square and the smaller black-filled square; if you click anywhere on that, the d3.drag code runs before the d3.zoom. (It's not just on the black-filled square; if you zoom in, you can see that dragging directly on the outer stroke also doesn't work.)

    But the d3.drag code is throwing an error (as you can see in the browser console), because it's trying to set d.x and d.y when d doesn't exist, so nothing happens when you drag. And it wouldn't work anyway, because it's trying to set the cx and cy attributes, which the SVG group element doesn't have; it was probably originally written for an SVG circle element. For a group element, the dragged event should be setting a transform on the group instead, which your d3.zoom code is already doing.

    Using d3.zoom

    In this approach, you can click and drag anywhere on the SVG. If you don’t want it to zoom when you scroll, you can make scaleExtent only allow a scale of 1:

    svg.call(
      d3.zoom()
        .extent([[0, 0], [w, w]])
        .scaleExtent([1, 1])
        .on("zoom", function(event) {
          g.attr("transform", event.transform);
        })
    );
    

    Here's a suggestion you can merge into your notebook to use the d3.zoom approach (you should only merge one or the other!): https://observablehq.com/compare/c63434913a56fbb2...a3c5334fa206bb61

    Using d3.drag

    In this approach, you can only click and drag on the group (where it has stroke or fill). You can give the group a datum with x and y values of 0 to start with, and use a transform instead of setting cx and cy:

    const g = svg.append("g")
      .datum({x: 0, y: 0})
      .call(d3.drag().on("drag", function(event, d) {
        d3.select(this)
          .attr("transform", `translate(${d.x = event.x}, ${d.y = event.y})`);
      }));
    

    If you wanted to be able to click and drag anywhere, you could add an invisible rectangle for pointer capture, as described here.

    Here's a suggestion you can merge into your notebook to use the d3.drag approach (you should only merge one or the other!): https://observablehq.com/compare/c63434913a56fbb2...3650eb69db864a42