6
votes

How can one provide separate zoom scales for x- and y-axis in D3 version 4?

This example by Patrick Brockman gave a method that worked wonderfully under v3, which allowed binding zooming to scale-factor objects. The relevant code:

  xyzoom = d3.behavior.zoom()
    .x(xscale)
    .y(yscale)
    .on("zoom", zoomable ? draw : null);
  xzoom = d3.behavior.zoom()
    .x(xscale)
    .on("zoom", zoomable ? draw : null);
  yzoom = d3.behavior.zoom()
    .y(yscale)
    .on("zoom", zoomable ? draw : null);

Note that there are two scaling objects, one for each axis, and they are bound from both the axis label/ticks area and the main plot, so that pan/zoom activities are cumulative across all three areas.

In the new paradigm, there's a zoom-transform object linked with page elements where the zooming takes place, which has a single scale factor and two-dimensional offset. The support for affecting scaling objects transforms the transform without updating it in-place. Sharing a zoom transform between all three areas would give only one scale factor overall (not to mention the problem that zooming in an axis label/tick area would still affect the other axis), and allowing each to have its own zoom transform creates a horrible problem with order of application (transform multiplication is NOT commutative).

There doesn't even seem to be a straightforward way to get the transform for individual steps and combine them myself (trying to reset the zoom transform to identity raises all the same events as user actions).

How can one achieve anisotropic zoom (different stretch factors in different directions) in version 4?

Here's what I have so far, which is totally wrong (it accumulates the effect of a zoom transform which itself is cumulative of zoom events):

function zoom_update() {
  svg.select('rect.zoom.x.box').call(d3.zoom().on('zoom', function() {
              xscale = d3.event.transform.rescaleX(xscale);
              update();
         }));
  svg.select('rect.zoom.y.box').call(d3.zoom().on('zoom', function() {
              yscale = d3.event.transform.rescaleY(yscale);
              update();
         }));
  svg.select('rect.zoom.xy.box').call(d3.zoom().on('zoom', function() {
              xscale = d3.event.transform.rescaleX(xscale);
              yscale = d3.event.transform.rescaleY(yscale);
              update();
         }));

Is it necessary to skip over the zoom module and process mouse events directly?

1
Any progress on this, Ben?Jared

1 Answers

-1
votes

Take a look to this example

For your case, I'd suggest something like

// define the zoom 
d3.zoom().on('zoom', function() {
    zoom_update();
});

function zoom_update() {
    // re-scale
    svg.select('rect.zoom.x.box')
        .call(xAxis.scale(d3.event.transform.rescaleX(x)));
    svg.select('rect.zoom.y.box')
        .call(yAxis.scale(d3.event.transform.rescaleY(y)));

    svg.select('rect.zoom.xy.box')
        .call(xAxis.scale(d3.event.transform.rescaleX(x)));
    svg.select('rect.zoom.xy.box')
        .call(yAxis.scale(d3.event.transform.rescaleY(y)));

    update();
}