Krimson
Krimson

Reputation: 7664

What Transformation values to calculate for scale and translate if you want to zoom

Im looking at this example which shows how one can use the zoom functionality to zoom in a specified domain range

https://bl.ocks.org/mbostock/431a331294d2b5ddd33f947cf4c81319

Im confused about this part:

var d0 = new Date(2003, 0, 1),
    d1 = new Date(2004, 0, 1);

// Gratuitous intro zoom!
svg.call(zoom).transition()
      .duration(1500)
      .call(zoom.transform, d3.zoomIdentity
          .scale(width / (x(d1) - x(d0))) // I think this is to caulcuate k which is the zoom factor
          .translate(-x(d0), 0)); // but what is this?

I'm having trouble understanding the calculations that are done. Correct me if my assumptions are wrong

d3.zoomIdentity This is a transformation that does nothing when applied.

.scale(width / (x(d1) - x(d0))) This is to calculate how much scale to apply by calculating the ratio between the width and the pixel difference between the two data points d0 and d1

.translate(-x(d0), 0)) I don't understand this part. Why is x(d0) negated and how does the x coordinate of d(0) relate to how much translation need to be applied?

Upvotes: 2

Views: 312

Answers (1)

Andrew Reid
Andrew Reid

Reputation: 38161

The translate value is aligning the graph so that x(d0) is the leftmost x value visible in the plot area. This ensures the visible portion of the plot area extends from d0 through d1 (the visible subdomain). If our full domain for the x scale has a minimum of 0, then x(0) will be shifted left (negative shift) x(d0) pixels.

I'll use a snippet to demonstrate:

var svg = d3.select("svg"),
    margin = {top: 10, right: 50, bottom: 70, left: 200},
    width = +svg.attr("width") - margin.left - margin.right,
    height = +svg.attr("height") - margin.top - margin.bottom;

// Scale for Data:
var x = d3.scaleLinear()
  .range([0, width])
  .domain([0,20]);
  
// Scale for Zoom:
var xZoom = d3.scaleLinear()
  .range([0,width])
  .domain([0,width]);
  
var xAxis = d3.axisBottom(x).ticks(5);
var xZoomAxis = d3.axisBottom(xZoom);

var zoom = d3.zoom()
    .scaleExtent([1, 32])
    .translateExtent([[0, 0], [width, height]])
    .extent([[0, 0], [width, height]])
    .on("zoom", zoomed);
    
var g = svg.append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

// plot area
g.append("rect")
  .attr("width",width)
  .attr("height",height)
  .attr("fill","url(#stripes)");
  
g.append("text")
  .attr("x",width/2)
  .attr("y",height/2)
  .style("text-anchor","middle")
  .text("plot area");
  
g.append("line")
  .attr("y1",0)
  .attr("y2",height)
  .attr("stroke-width",1)
  .attr("stroke","black");

// zoomed plot area:
var rect = g.append("rect")
  .attr("width",width)
  .attr("height",height)
  .attr("fill","lightgrey")
  .attr("opacity",0.4);
  
// Axis for plot:
g.append("g")
  .attr("class", "axis axis--x")
  .attr("transform", "translate(0," + height + ")")
  .call(xAxis);
  
// Axis for zoom:
g.append("g")
  .attr("class", "axis axis-zoom-x")
  .attr("transform", "translate(0,"+(height+30)+")")
  .call(xZoomAxis);  
  
var text = g.append("text")
  .attr("y", height+60)
  .attr("text-anchor","middle")
  .text("zoom units")
  .attr("x",width/2);
  
// Gratuitous intro zoom:   
var d1 = 18;
var d0 = 8;

svg.call(zoom).transition()
 .duration(2000)
 .call(zoom.transform, d3.zoomIdentity
 .scale(width / (x(d1) - x(d0)))
 .translate(-x(d0), 0));


function zoomed() {
  var t = d3.event.transform, xt = t.rescaleX(x);
  xZoom.range([xt(0),xt(20)]);
  g.select(".axis--x").call(xAxis.scale(xt));
  g.select(".axis-zoom-x").call(xZoomAxis.scale(xZoom));
  rect.attr("x", xt(0));
  rect.attr("width", xt(20) - xt(0));
  text.attr("x", xt(10));
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg width="400" height="180">
<defs>
  <pattern id="stripes" patternUnits="userSpaceOnUse" width="8" height="8" patternTransform="rotate(45 0 0)">
  <rect width="3" height="8" fill="orange"></rect>
</pattern>
</defs>

</svg>

Snippet Explanation:

  • Plot area: orange stripes
  • Full scaled extent of data: grey box.
  • Left hand side of plot area is x=0 (pixels) for the parent g that holds everything.

As we zoom in the bounds of our data exceeds the plot area. We want to show a specific subdomain of our data. We achieve part of that with the scale (as you correctly deduce) but the other portion is with the translate: we push values less than the lowest value of our x subdomain to the left. By pushing the entire graph left by an amount equal to x(d0), x(d0) appears as the leftmost coordinate of the plot area.

Upvotes: 1

Related Questions