d3 / d3-zoom

Pan and zoom SVG, HTML or Canvas using mouse or touch input.
https://d3js.org/d3-zoom
ISC License
501 stars 144 forks source link

Create transform for going between two scales #244

Closed camdecoster closed 2 years ago

camdecoster commented 2 years ago

I've been using d3-zoom and d3-scale in an application where we show a chart representing the current (up to today) data back to some point in the past (1d, 2d, 1yr, etc.). The scale controls what is shown on the chart and the zoom transform controls tracking of panning/zooming. One of the features of the application is to be able to load a chart showing a timeframe in the past. This loads up fine with the scale set appropriately, but the transform starts at (1, 0, 0), even though the chart is in the past. This results in the user not being able to get back to the current view, as we've limited the user to not be able to go into the future, assuming that it's to the right of (1, 0, 0). The solution was to find a way to calculate what the transform needs to be to get from one scale domain to the other. I'd like to add this as a utility in d3-zoom, but I'm not sure what the best place would be. Would it be more appropriate to make it part of the zoom object or the transform?

Fil commented 2 years ago

Interesting. Can you share as a notebook or web page? I'm struggling to see how a specific function is necessary on top of what we have already (d3.zoomTransform etc).

camdecoster commented 2 years ago

@Fil, it's been a while, but here's an Observable notebook demonstrating what I'm talking about for an X scale. The idea is that, if you have two scales and one is a modified version of the initial scale (panned/zoomed), there's a function to calculate the transform to get from the initial scale to the modified scale. I wasn't able to find an existing function to do that.

Fil commented 2 years ago

I think this works as well:

  const [lo0, hi0] = x.domain();
  const [lo1, hi1] = xMock.domain();
  const tx = xMock(lo0) - xMock(lo1);
  const k = (hi0 - lo0) / (hi1 - lo1):
  svg.call(zoom)
    .call(zoom.transform, d3.zoomIdentity.translate(tx, 0).scale(k));
camdecoster commented 2 years ago

Yes, that's a cleaner implementation. If you think that would be valuable, I'll create a PR.

Fil commented 2 years ago

Possibly better, in d3@7:

  const [lo0, hi0] = x.domain();
  const [lo1, hi1] = xMock.domain();
  svg
    .call(zoom)
    .call(zoom.transform, new d3.ZoomTransform((hi0 - lo0) / (hi1 - lo1), xMock(lo0) - xMock(lo1), 0));

I don't think it needs to be implemented as a method in the d3-zoom module, but it could make a nice example for the documentation of d3.ZoomTransform.

camdecoster commented 2 years ago

Would that be in the README, here?

camdecoster commented 2 years ago

In case anyone wants to read about how I did this, I wrote about it here.