Nick Brunt
Nick Brunt

Reputation: 10057

How to accurately zoom d3 maps which have already been translated

I have a map which has been translated to make it fit on the canvas properly.

I'm trying to implement a way to zoom it and it does work, but it moves away from center when you zoom in, rather than centering on the mouse or even the canvas.

This is my code:

function map(data, total_views) {
  var xy = d3.geo.mercator().scale(4350),
      path = d3.geo.path().projection(xy),
      transX = -320,
      transY = 648,
      init = true;

  var quantize = d3.scale.quantize()
    .domain([0, total_views*2/Object.keys(data).length])
    .range(d3.range(15).map(function(i) { return "map-colour-" + i; }));

  var map = d3.select("#map")
        .append("svg:g")
        .attr("id", "gb-regions")
        .attr("transform","translate("+transX+","+transY+")")
        .call(d3.behavior.zoom().on("zoom", redraw));

  d3.json(url_prefix + "map/regions.json", function(json) {
    d3.select("#regions")
        .selectAll("path")
            .data(json.features)
        .enter().append("svg:path")
            .attr("d", path)
            .attr("class", function(d) { return quantize(data[d.properties.fips]); });
  });

  function redraw() {
    var trans = d3.event.translate;
    var scale = d3.event.scale;

    if (init) {
      trans[0] += transX;
      trans[1] += transY;
      init = false;
    }
    console.log(trans);

    map.attr("transform", "translate(" + trans + ")" + " scale(" + scale + ")");
  }
}

I've found that adding the initial translation to the new translation (trans) works for the first zoom, but for all subsequent zooms it makes it worse. Any ideas?

Upvotes: 3

Views: 738

Answers (1)

Glenn
Glenn

Reputation: 5342

Here's a comprehensive starting-point: semantic zooming of force directed graph in d3

And this example helped me specifically (just rip out all the minimap stuff to make it simpler): http://codepen.io/billdwhite/pen/lCAdi?editors=001

 var zoomHandler = function(newScale) {
        if (!zoomEnabled) { return; }
        if (d3.event) {
            scale = d3.event.scale;
        } else {
            scale = newScale;
        }
        if (dragEnabled) {
            var tbound = -height * scale,
                bbound = height  * scale,
                lbound = -width  * scale,
                rbound = width   * scale;
            // limit translation to thresholds
            translation = d3.event ? d3.event.translate : [0, 0];
            translation = [
                Math.max(Math.min(translation[0], rbound), lbound),
                Math.max(Math.min(translation[1], bbound), tbound)
            ];
        }

        d3.select(".panCanvas, .panCanvas .bg")
            .attr("transform", "translate(" + translation + ")" + " scale(" + scale + ")");

        minimap.scale(scale).render();
    }; // startoff zoomed in a bit to show pan/zoom rectangle

Though I had to tweak that function a fair bit to get it working for my case, but the idea is there. Here's part of mine. (E.range(min,max,value) just limits value to be within the min/max. The changes are mostly because I'm treating 0,0 as the center of the screen in this case.

// limit translation to thresholds
            var offw = width/2*scale;
            var offh = height/2*scale;
            var sw = width*scale/2 - zoomPadding;
            var sh = height*scale/2- zoomPadding;

            translate = d3.event ? d3.event.translate : [0, 0];
            translate = [
                E.range(-sw,(width+sw), translate[0]+offw),
                E.range(-sh,(height+sh), translate[1]+offh)
            ];
        }


        var ts = [translate[0], translate[1]];
        var msvg  = [scale, 0, 0, scale, ts[0], ts[1]];

Upvotes: 1

Related Questions