Search code examples
svgzoomingscaletransformation

How do I adjust my SVG transform based on the viewport?


I'm working with the d3 library and have had success working with the chloropleth example, as well as getting a click action to zoom in to a particular state (see this question for details). In particular, here is the code I'm using for my click to zoom event on a state:

// Since height is smaller than width, 
var baseWidth = 564;
var baseHeight = 400;

d3.selectAll('#states path')
    .on('click', function(d) {
        // getBBox() is a native SVG element method
        var bbox = this.getBBox(),
            centroid = [bbox.x + bbox.width/2, bbox.y + bbox.height/2],
            // since height is smaller than width, I scale based off of it.
            zoomScaleFactor = baseHeight / bbox.height,
            zoomX = -centroid[0],
            zoomY = -centroid[1];

        // set a transform on the parent group element
        d3.select('#states')
            .attr("transform", "scale(" + scaleFactor + ")" +
                "translate(" + zoomX + "," + zoomY + ")");
    });

However, when I click to view on the state, my transform is not in the center of my viewport, but off to the top left, and it might not have the proper scale to it as well. If I make minor adjustments manually to the scaleFactor or zoomX/zoomY parameters, I lose the item altogether. I'm familiar with the concept that doing a scale and transform together can have significantly different results, so I'm not sure how to adjust.

The only other thing I can think of is that the original chloropleth image is set for a 960 x 500 image. To accomodate for this. I create an albersUSA projection and use my d3.geo.path with this projection and continue to add my paths accordingly.

Is my transform being affected by the projection? How would I accomodate for it if it was?


Solution

  • The scale transform needs to be handled like a rotate transform (without the optional cx,cy parameters), that is, the object you want to transform must first be moved to the origin.

    d3.select('#states')
                .attr("transform",
                      "translate(" + (-zoomX) + "," + (-zoomY) + ")" +
                      "scale(" + scaleFactor + ")" +
                      "translate(" + zoomX + "," + zoomY + ")");