jczaplew
jczaplew

Reputation: 1753

Using d3.behavior.drag() to pan a map

I am having some issues applying D3's behavior.drag() on a map. In this example, dragging the map results in a very fast pan, and the point clicked does not stay with the mouse - http://bl.ocks.org/jczaplew/6453048

My drag behavior code looks like this -

var drag = d3.behavior.drag()
  .origin(function() { return {x: rotate[0], y: -rotate[1]}; })
  .on("drag", function() {
    rotate[0] = d3.event.x;
    rotate[1] = -d3.event.y;

   // Refresh the map
    projection.rotate(rotate);
    path = d3.geo.path().projection(projection);
    d3.selectAll("path").attr("d", path);
});

What I need:

  1. Panning without translating the entire SVG. The ability to pan off the map (a concern expressed in this question - Zooming and Panning a Mercator Map centered on the Pacific using d3.js and demonstrated in this example - http://bl.ocks.org/d3noob/4966228) is undesirable.
  2. When the user clicks and drags the map, the point clicked sticks to the cursor (i.e. clicking on and dragging Spain rotates the map around Spain)

I suspect this issue has something to do with drag.origin(), but perhaps I shouldn't be using d3.behavior.drag() at all? Any and all assistance is greatly appreciated!

Upvotes: 3

Views: 3171

Answers (1)

jczaplew
jczaplew

Reputation: 1753

I think I found a potential (albeit slightly unsatisfactory) answer. Using the logic from this example (http://mbostock.github.io/d3/talk/20111018/azimuthal.html) I created this - http://bl.ocks.org/jczaplew/6457917

The key bit is to keep track of the position of the start of the drag (i.e. when the user clicks). The logic for the lat and long of the desired rotation is

start projection rotation + (start screen position - destination screen position) / 4

and the drag behavior now looks like this

    var drag = d3.behavior.drag()
        .on("dragstart", function() {
          var proj = projection.rotate();
          m0 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY];
          o0 = [-proj[0],-proj[1]];
        })
       .on("drag", function() {
         if (m0) {
           var m1 = [d3.event.sourceEvent.pageX, d3.event.sourceEvent.pageY],
               o1 = [o0[0] + (m0[0] - m1[0]) / 4, o0[1] + (m1[1] - m0[1]) / 4];
           projection.rotate([-o1[0], -o1[1]]);
         }

      // Update the map
        path = d3.geo.path().projection(projection);
        d3.selectAll("path").attr("d", path);
    });

This still feels quite hacky, and it doesn't seem to scale very well. For example, changing the projection and the projection scale seems to require tweaking the normalization of the desired rotation coordinates from 4 to something else to achieve the same smoothness and behavior.

Anyone have alternative ideas? I'd love to see other, more elegant ways to accomplish this functionality.

Upvotes: 4

Related Questions