Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ability to set X & Y transform scale #48

Closed
joshuahiggins opened this issue Jul 29, 2016 · 51 comments
Closed

Ability to set X & Y transform scale #48

joshuahiggins opened this issue Jul 29, 2016 · 51 comments

Comments

@joshuahiggins
Copy link

With zoom.x() and zoom.y() removed, there doesn't appear to be an elegant way to zoom a single axis while leaving the opposite axis's domain untouched.

I'd like to propose the idea of extending the transform methods to support 1 or 2 scales, replacing the single k value with kx and ky values and extending the transform.scale method to accept an optional second parameter. By default, when called with a single parameter, kx and ky would be assigned with the same scale, resulting in the same functionality as today. However, users would have more control over their transforms through the added ability to compress and expand axises as needed.

I'm happy to work on this functionality but wanted to get a feel for it's perceived viability before I dove in.

@mbostock
Copy link
Member

Here’s an example of one-dimensional zooming:

https://bl.ocks.org/mbostock/34f08d5e11952a80609169b7917d4172

If you want to zoom the x-scale, use transform.rescaleX. If you want to zoom the y-scale, use transform.rescaleY.

@joshuahiggins
Copy link
Author

That doesn't solve the problem for all cases, just for that specific use case where you only need to work off of a single zoom scale. What about the case where X and Y need to be transformed at different scales? Take this example: http://bl.ocks.org/jgbos/9752277

The above block is essentially a hack to accommodate dual scales in v3. They essentially wipe the scale clean by reapplying the zoom behavior, thus effectively setting the scale back to 1. Not a great solution, as it prevents you from using the provided zoom extents, however it was an okay workaround.

However, in v4, the transform is a bit more engrained, which feels like a huge step forward until you realize you're still limited to a single scale and now working around that limitation is far more difficult. rescale, invert, apply, and scale all operate under the assumption that you will never need to control k and that the transform.k is the scale you want to use for all scaling. For example, switching from X axis to XY axis zooming will result in a reset of the X axis to the same scale as Y, which is not the desired functionality.

There doesn't appear to be a feasible method of managing individual kx and ky scales that doesn't require writing your own implementation of the transform state and methods that accept kx and ky. Correct me if I'm wrong?

In my opinion, it would be very beneficial and likely a low risk feature to extend the transform to 4 values, x, y, kx, ky. The provided methods can be updated as follows:

Proposed changes:

d3.zoomTransform(node)

Return a slight different, but still two dimensional matrix: kx 0 tx 0 ky ty 0 0 1

transform.scale(kx [, ky = kx])

Returns a transform whose scale kx₁ is equal to kx₀ × kx, and whose scale ky₁ is equal to ky₀ × ky, where kx₀ is this transform’s x scale and ky₀ is this transform’s y scale.

transform.apply(point)

Returns the transformation of the specified point which is a two-element array of numbers [x, y]. The returned point is equal to [x × kx + tx, y × ky + ty].

transform.applyX(x)

Returns the transformation of the specified x-coordinate, x × kx + tx.

transform.applyY(y)

Returns the transformation of the specified x-coordinate, x × ky + ty.

transform.invert(point)

Returns the inverse transformation of the specified point which is a two-element array of numbers [x, y]. The returned point is equal to [(x - tx) / kx, (y - ty) / ky].

transform.invertX(x)

Returns the inverse transformation of the specified x-coordinate, (x - tx) / kx.

transform.invertY(y)

Returns the inverse transformation of the specified y-coordinate, (y - ty) / ky.

transform.toString()

Returns a string representing the SVG transform corresponding to this transform. Implemented as:

function toString() {
  return "translate(" + this.x + "," + this.y + ") scale(" + this.kx + "," + this.ky + ")";
}

d3.zoomIdentity

The identity transform, where kx = ky = 1, tx = ty = 0.

@mbostock
Copy link
Member

Okay, I see what you mean now. And yes the zoom behavior is limited to uniform scaling at the moment; you can ignore one of those dimensions in how you apply the transform, but the zoom behavior internally represent the transform with a single k for both x and y.

It feels a bit weird to me to introduce a feature that is only accessible programmatically—I can’t think of a natural way to allow independent zooming of x and y in the current model (outside of multitouch and even then I don’t think you would want it enabled by default). Also I expect this would have ramifications on how zoom transitions are calculated.

(Somewhat related, but supporting rotation would be nice in the future, too.)

I guess I am not sure whether this makes more sense as a feature on the zoom behavior or another class of behavior. And adding this as a feature to the existing zoom behavior would likely not be backwards compatible, so maybe a new class makes more sense?

@mbostock mbostock reopened this Jul 29, 2016
@joshuahiggins
Copy link
Author

It feels a bit weird to me to introduce a feature that is only accessible programmatically—I can’t think of a natural way to allow independent zooming of x and y in the current model (outside of multitouch and even then I don’t think you would want it enabled by default).

I agree to an extent. Axis specific zooming has it's use cases, even if there's not a common pattern for it. I don't see an issue with providing the programmatic controls to allow people to build functionality that is outside of the box, considering that feels like the whole purpose of D3 in the first place.

Also I expect this would have ramifications on how zoom transitions are calculated.

That's possible. I haven't found any issues with zooming in the first pass I took, but I haven't tried transitions yet. I don't think it'll be an issue though, personally.

You can view the proposed changes in action here: http://bl.ocks.org/joshuahiggins/2512b0790c0feb2afec70c372530edbd

As well as a diff against the prod build of D3 here: https://www.diffchecker.com/hgtigw7g

A couple notes on the code:

  1. It's an afternoon monkey patch against the distributable code, so it clearly needs some love.
  2. It addresses all instances of scaling in the d3-zoom package that I could find.
  3. To expedite the patch, I updated Transform(k, x, y) to Transform(k, x, y, ky)... Obviously would need to address that before sending a proper PR.
  4. I added a zoom.scaleLock([x, y]) method which accepts an array of 2 booleans or a function that returns an array of 2 booleans. This is the magic sauce to making the proposed changes worthwhile to developers. This function adds in a check at the very start of a zoom event that allows the user to essentially stop each axis individually before an event even fires. You can see that in action in the Block.
  5. The changes in the patch are backwards compatible, as ky is simply an option parameter that defaults to k.

Somewhat related, but supporting rotation would be nice in the future, too.

Agreed. In that case, it might be good for Transform to become a helper function for a more robust Matrix function that takes all 6 parameters.

@joshuahiggins
Copy link
Author

Oh and now that I've posted it, I realize my hover events are garbage and sometimes don't trigger the proper mouseover events to unlock the axis for scaling. Just hover out and hover back in if it gives you trouble, hah.

@jfsiii
Copy link

jfsiii commented Sep 1, 2016

I recently ran into this issue while working on a chart that had brushes on the X & Y axes.

Using uniform scaling:
brush and zoom - no scale y2

Using independent scaling:
brush and zoom - scale y

I'm not sure of the solution, but I think the use case is reasonable.

@joshuahiggins
Copy link
Author

joshuahiggins commented Sep 1, 2016

I have D3 v4 working with a forked build of the d3-zoom library which allows for dual scaling. Been using it for a few weeks without any issues and no apparent conflicts with core functionality of D3. I haven't had time to open a PR yet, but I'm hoping to get one submitted in the next week or two.

@jfsiii
Copy link

jfsiii commented Sep 1, 2016

(Somewhat related, but supporting rotation would be nice in the future, too.)

Agreed. Any thoughts on the skew, matrix, or the *3d properties? Is there any interest in a d3-transform?

I don't want to hijack this thread, so I'll stop here, but I want to mention I made a related library. It uses matrices internally, and allows you to create/update transforms using .translate(x,y,z), .rotate(x,y,z), etc. That particular lib is made to match a specific interface, but I'm glad to supply any code or tests 1 2

@jfsiii
Copy link

jfsiii commented Sep 1, 2016

Here's the monkey-patch I apply. It's definitely "just get it done" quality, but ...

Object.assign(Transform.prototype, {
  invertY(y) {
    const scaleY = this.ky || this.k;
    return (y - this.y) / scaleY;
  },
  scaleY(k) {
    this.ky = k;
    return this;
  },
  toString() {
    const translate = `translate(${this.x},${this.y})`;
    const scale = `scale(${this.k},${this.ky || this.k})`;
    return `${translate} ${scale}`;
  }
});

Then I use it like const transform = (new Transform(scaleX, xPos, yPos)).scaleY(scaleY). Again, not ideal, but posting in case it helps anyone.

@crapthings
Copy link

i've the same issue.

d3/d3#2970

@timfish
Copy link

timfish commented Oct 1, 2016

I'd like to be able to set a different scaleExtent for each of x and y too. This would make it easier to disable zoom on a single axis.

@jfsiii
Copy link

jfsiii commented Oct 17, 2016

I just learned about https://github.com/trinary/d3-transform/

There's also a PR to update it for V4 trinary/d3-transform#19

@etiennecrb
Copy link

etiennecrb commented Nov 25, 2016

Hi, I'm working on this subject too because I need user to zoom out only on a single axis once scaling on the other would not respect translate extent constraints. Moreover, in my use case, I sometimes need to zoom with a different "scale factor" on x and y.

As @joshuahiggins did, I rewrite the Transform object which now has 4 values (x, y, kx, ky). Thus, it is possible to constrain scales so that translate extent constraints are respected, even on zoom out. This is done on the zoom object each time zoom.extent() or zoom.translateExtent() is called with this function:

function constrainScaleExtent() {
  kx0 = Math.max(kx0, (extent()[1][0] - extent()[0][0]) / (x1 - x0));
  ky0 = Math.max(ky0, (extent()[1][1] - extent()[0][1]) / (y1 - y0));
}

Moreover, it adds the zoom.scaleRatio() method in which you can provide a scale factor for x and y axis. It has an impact on event listeners (touch events currently not supported).

Demo
Diff
Github repo

I'm not sure about the consequences of what I'm doing. Except touch support and zoom transition, it seems to work great for my use case. Any advice?

@etiennecrb
Copy link

I released an independent d3 plugin: d3-xyzoom.

@mbostock
Copy link
Member

mbostock commented Dec 2, 2016

Perhaps as d3-zoom 2.0 we should generalize the matrix to match CSS 2D matrix transformations:

a c tx
b d ty
0 0 1

This corresponds to matrix(a b c d tx ty). Then d3-zoom could express both independent scaling of x and y, as well as rotation and skew. We could still define the default user interaction to be limited to translate and symmetric scale, but have options to allow rotation or other more general transformations, as well as being exposed programmatically.

@HamsterHuey
Copy link

Great discussion and glad to see that this is being considered for a future release. I just wanted to pipe in with another use case:

I have been struggling for a couple of days to implement a line chart with a button/checkbox that lets you switch between panning/scroll-zoom mode and a 2-D rectangular brush based zooming a-la MATLAB/Matplotlib. Due to the lack of support for independent zoom scales for X and Y, you are forced to use a separate path to implement the 2D brush zoom (Mike's block for Brush and Zoom II was invalauable), while the panning/scroll-zooming can be implemented in the tradition manner using d3.zoom. This led to 2 related issues:

  1. Having duplication of code since each path requires very similar code for updating plot elements/scales/axes

  2. It is tricky to update the zoom.transform when zooming with the 2-D brush. I believe that this was possible in d3 v3, but not in the new version? The only way to update it seems to be to use something like selection.call(zoom.transform, newTransform); which also fires off a bunch of events that I don't really want it to do due to having performed the rectangular 2D selection based zooming externally.

Perhaps this is easily accomplished within D3 v4 (I only just dipped my feet into the world of D3 a couple of weeks back), but I couldn't achieve this without a bit of a hacky approach that works but results in unnecessary overhead of the update operation on DOM elements being performed twice (once by the 2D brush method and once by the selection.call method to update the zoom transform so that subsequent pan/scroll-zoom operations are aware of the changes due to the 2D brush based zooming).

@DaWaD3
Copy link

DaWaD3 commented Mar 8, 2017

I use a zoom event for each axis and one for the global chart. Each zoom works fine but in combination the chart jumps.

See https://jsfiddle.net/DaWa/vn0rxd5g/

I think it has something to do with that problem. I am happy about any help.

@Root-Core
Copy link

Hi guys, any news about this issue?
We really need to achieve an zoom on a single axis together with an "global" multi axis zoom.

Just like @DaWaD3's example shows.

@mbostock could you please consider declaring this issue as bug, as this works on d3v3?
I'm confident that this is a regression to some point.

@houfeng0923
Copy link

@Root-Core I had same trouble about this issue too .. it had work easily in d3 v3 .
One change about zoom in v4 is that transform being held in selection . so i try to modify/reset the __zoom prop in selection . look at this demo : https://jsfiddle.net/1g68fggd/2/
but i not think it 's a nice way . @mbostock , hope your advice .

hi, @joshuahiggins , i can't find your version. what about your fork/pr now ?

@Root-Core
Copy link

If you want to apply the transformation of two scales to an given html-element, this should work with a bit of work.

But the problem here is, that we acquire the data dynamically and draw it scaled, but do not apply the events transform to the svg element. The data can be "infinitely" long, gets prefiltered to have one value per pixel and might be "live". This is why we need to restore the behavior from v3. ;)

@DaWaD3
Copy link

DaWaD3 commented May 10, 2017

I am quite not sure if it is the right way to do it but I think I have a solution for a multiple axis zoom.

https://jsfiddle.net/DaWa/dLmp8zk8/2/

In that example I always reset the transform for each zoom to ensure that I start zooming and do not have to pay attetion on the other zooms.

Does that make sense or am I doing something terribly wrong?

@lteacher
Copy link

Just started with trying to chart stuff and immediately have this issue. Basically I wanted to just say on ctrl + mousewheel zoom / stretch or shrink the Y axis and retain that scale for Y when doing regular zoom but instead it they both just spring back to the same scale.

Any plans to resolve this issue? I need the functionality and am having a hard time getting the builds to work when forking and linking locally. Since I already need to rescale both X and Y where in this case I conditionally choose to not rescale X, I don't see why they should be linked?

@ghyatzo
Copy link

ghyatzo commented Jun 9, 2017

@DaWaD3 Resetting the current transform state held in the selection seems to be the only viable solution as of now.

The only problem could be that any set scale extent would be useless since the scale would always be at 1. Moreover if you for example set a scale extent of [1, Infinity] you would be unable to zoom out, so not such a nice thing...

As a work around i fear that for applying this method to a scale extent you'd have to store the initial state of the graph and then at each redraw check to see if it's matched or not: Say compare the current axis domain with an instance of the same axis initial domain previously saved somewhere in your code.

@jeffsf
Copy link

jeffsf commented Jan 6, 2018

I've been bitten by this one as well when trying to plot time-series data. A good visualization lets the user compress and expand the time scale to be able to see long-term trends and behavior, as well as short-term details, without changing the scaling on the data axis. If you've worked with a `scope before, think of the time-base knob that runs from microseconds per division up through seconds.

While wickedly ugly compared to the rest of my admittedly novice D3 usage, I found a marginally reasonable way to deal with this. A "hack" at best, I'll admit.

I can't say that it was either quick or easy to figure out how to do something that was so natural for me; a "simple" chart / strip recorder or digital `scope, depending on your era and experiences.

First, I observed that there are two kinds of d3.event.transform that the zoom handler gets, at least that I could generate:

  • zoom events
  • pan events

When I say "zoom events" here, I mean events where the transform's scale has changed relative to its last version. As far as I can generate with a normal mouse, Apple touchpad, or Android touch-screen browser, these events do not simultaneously zoom and pan the "event point" as well. While there is change in the translation, it appears to be just to keep the same point under the cursor.

const t = d3.event.transform;

The X/time scale handles the unmodified transform, as it can both pan and zoom

updatingGraph.current.timeScale = t.rescaleX(updatingGraph.unzoomed.timeScale);

However, the data scales need to get a transform without the zoom component. If you just modify t directly by setting t.k = 1; you end up with never being able to zoom as t is a reference to the transform of whatever is seeing the events and storing the cumulative transform.

You unfortunately can't call d3.zoomIdentity.translate(t.x, t.y).rescaleY(someScale) since it isn't a full-fledged transform, only the matrix coefficients. This means creating a compatible DOM object to "back" the transform, just to be able to call .rescaleY() as well as someplace to tuck away the last transform, to know if it is a "zoom" or a "pan" event.

        updatingGraph.zoomBehavior = d3.zoom().on("zoom", updatingGraph.doZoom);

        updatingGraph.zoom_catcher = updatingGraph.active_area.append("rect")
            .attr("class", "zoom-catcher")
            .attr("id", "zoom-catcher")
            .attr("width", updatingGraph.activeAreaExtent.width)
            .attr("height", updatingGraph.activeAreaExtent.height)
            .call(updatingGraph.zoomBehavior);

        // a functional zoomTransform needs to be associated with an element

        updatingGraph.t_holder = updatingGraph.zoom_catcher.append("g")
            .attr("id", "t-holder");

        updatingGraph.lastZoomTransform =
            Object.assign({}, d3.zoomTransform(updatingGraph.zoom_catcher));

Now when I get a zoom event, I can branch on zoom vs. pan. If a pan-only event, I ignore the scale when calling .rescaleY(). For a zoom-only event, I reverse-out the translation change in Y (otherwise the data appears to shift up and down "for no reason")

        let t_tmp = d3.zoomTransform("#t-holder");  // Get a "real" zoomTransform

        if (t.k === updatingGraph.lastZoomTransform.k) {

            t_tmp = d3.zoomIdentity.translate(t.x, t.y);

            updatingGraph.current.tempScale = t_tmp.rescaleY(updatingGraph.unzoomed.tempScale);
            updatingGraph.current.humidityScale = t_tmp.rescaleY(updatingGraph.unzoomed.humidityScale)

        } else {

            t.y = updatingGraph.lastZoomTransform.y

        }

        updatingGraph.lastZoomTransform = Object.assign({}, t);

Of course, this all falls apart with any device that can create simultaneous (combined) zoom and pan

@ohenrik
Copy link

ohenrik commented Apr 2, 2018

So I’m about to go crazy trying to solve this... There is definitely a use case for this.

I need to be able to zoom the time axis (x-axis) while controlling zoom on the price axis (y-axis) separately. Lastly when i drag the window i want to be able to drag both the x and the y axis.

Are there any plans to add support for kx and ky? @DaWaD3 solution is only for a spesific use case.

@jfsiii
Copy link

jfsiii commented Apr 2, 2018

@ohenrik Would #48 (comment) help? You can see it in action in #48 (comment)

@ohenrik
Copy link

ohenrik commented Apr 2, 2018

Thank you for responding :) @jfsiii, It looks close to what i want, however how do i make this? Do you have any example code?

This is another example that is close to what i want:
https://jsfiddle.net/o8vaecyn/35/
However i need to be able to be able to zoom the y-axis by scrolling over the Y axis. In the example above Y is forever unzoomable.

@ohenrik
Copy link

ohenrik commented Apr 2, 2018

I tried implementing the code from #48 (comment) however Transform is not defined. What object is this? Do i get it from d3?

@Abhijeet-Das
Copy link

Hello All,

I am wondering what could be an appropriate way to zoom in matrix scatter plot.
For example , in this https://bl.ocks.org/Fil/6d9de24b31cb870fed2e6178a120b17d
we can see both x and y axis have different scales.

Could anyone please suggest what can be ideal approach to apply zooming feature in this visualization or share any example which has zoom in matrix scatter plot d3 V4.
Thanks in advance

@aldanor
Copy link

aldanor commented Sep 29, 2018

Late to join the party, been bit by the same horrible problem. Wondering if any of you guys have come up to any of the cleaner or more stable solutions than described above?

(since it doesn’t look it’s making it in d3-zoom prod anytime soon)

@mbostock
Copy link
Member

I haven’t had any time to work on this feature, but I’d be willing to review an implementation of #48 (comment) if anyone is willing to work on it.

@10lojzo
Copy link

10lojzo commented Oct 23, 2018

I made a hack-like workaround to achieve zooming only one axis and pan both x and y axes.
I am not completely sure it addresses the exact problem of this thread, as long as panning is involved in my case. I would appreciate some comment with better(proper) solution or some final word if this feature is going to be implemented or not.

https://jsfiddle.net/xpr364uo/

What is going on in short:

In transform function I check the property d3.event.sourceEvent.type and decide if it is zooming or panning. If it is panning, I store the delta y corresponding to panning by comparing with t.y in last stored transform object. Then I am able to tell, which portion of the t.y in new transform is produced by all pannings in previous transforms.

Thanks

@aldanor
Copy link

aldanor commented Oct 24, 2018

Here's a working example using d3-xyzoom if anyone's interested: https://codepen.io/aldanor/pen/WabmoL

@kaltal
Copy link

kaltal commented Nov 29, 2018

Here's a working example using d3-xyzoom if anyone's interested: https://codepen.io/aldanor/pen/WabmoL

aldanor, How could I get the complete code for your working copy? It looks pretty clean. Thanks

@aldanor
Copy link

aldanor commented Nov 29, 2018

@kaltal the linked codepen contains a complete self-contained example.

@akhilkurnool
Copy link

akhilkurnool commented Dec 19, 2018

I released an independent d3 plugin: d3-xyzoom.

How do I apply this plugin to d3 v4. I'm using react 16.4 and create-react-app 2.0. Thanks

@mbostock
Copy link
Member

mbostock commented Aug 7, 2019

I am currently working on a new release for this library, but I would like to add support for multitouch rotation (as is now commonly supported by vector sloppy maps, for instance), along with easier support for one-dimensional zooming. I plan to revisit this after d3-zoom 2.0 is released.

@klausz
Copy link

klausz commented Aug 18, 2019

Here's a working example using d3-xyzoom if anyone's interested: https://codepen.io/aldanor/pen/WabmoL

I liked it. But there are two libraries added.

<script src="//cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <script src="//cdn.rawgit.com/wiremind/d3-xyzoom/9bd110c6/build/d3-xyzoom.min.js"></script>

@Neeraj-swarnkar
Copy link

I am facing some similar type of issue - https://stackoverflow.com/questions/60412324/in-my-d3-chart-appended-rect-is-not-coming-properly can you guide me

@adelriosantiago
Copy link

The best workaround is to simply create faux data entries. This allows to simulate asymmetric X, Y zoom and pan. See my answer here for more info: https://stackoverflow.com/a/61164185/1996066

@parliament718
Copy link

parliament718 commented May 17, 2020

Have been struggling with this implementation related to this problem for several weeks.
https://stackoverflow.com/questions/61071276/d3-synchronizing-2-separate-zoom-behaviors

Open bounty, any help appreciated.

@Fil
Copy link
Member

Fil commented May 18, 2020

I've made this example of a double zoom chart; somewhat arbitrarily, the features are:

  • zoom both dimensions at the same rate when mousing within the main part of the chart
  • reverse zoom rate on the vertical axis when holding the shift key
  • zoom only one dimension when mousing on any of the axes

I tried to write it in a way that I hope is somewhat readable, and can be adapted to various needs.

Let me know if there are things that are unclear, and don't hesitate to send suggestions.
https://observablehq.com/d/0b496b3c9cf2144e

@Fil
Copy link
Member

Fil commented Jun 25, 2020

The solution / example notebook is published https://observablehq.com/@d3/x-y-zoom

@Fil Fil closed this as completed Jun 25, 2020
@joshuahiggins
Copy link
Author

This looks slick. Going to poke around the code later, but from a quick glance over it it looks like an elegant solution to the problem. Appreciate it!

@cybae0804
Copy link

The solution / example notebook is published https://observablehq.com/@d3/x-y-zoom

For those of you trying to make use of this example, I'd recommend changing

const point = e.sourceEvent ? d3.pointer(e) : [width / 2, height / 2];

to

const point = e.sourceEvent ? d3.pointer(e, e.sourceEvent.target) : [width / 2, height / 2];

wheelEvent from zooming works correctly, and d3.pointer(e) correctly uses the svg element from currentTarget.
However mouseEvent from dragging returns the pageX and pageY coordinate because it uses the window as the currentTarget. I'm not sure if this is a chrome specific behavior, but basically always passing the svg element solves this.

You can see this behavior by dragging the graph above vertically, and if it's near the bottom x axis, it will not drag. However as you drag upwards toward the top of the page, it'll start dragging properly.

@Fil
Copy link
Member

Fil commented Jan 14, 2021

Thanks! I've fixed this issue (using this instead of e.sourceEvent.target, but it's the same thing), and made it work with touch events too by switching from d3.pointer to d3.pointers.

@martinblostein
Copy link

martinblostein commented Jan 18, 2021

I appreciate the example posted by @Fil (https://observablehq.com/@d3/x-y-zoom), but that seems to me to be a clever workaround rather than a resolution to this issue and so I don't agree that this issue should be closed.

There are some quirks about that solution:

  1. It only works on pure translation and pure scaling events. Touch users can do both at the same time by pinching. Using d3-zoom the normal way fully supports this out of the box.
  2. It is not straightforward to combine that solution with other programmatic control of the zoom/pan.
  3. The user must track previous zoom state themselves.
  4. There's redundancy -- each d3 zoom instances tracks separate 2D transformations, but only half of each one is used. I think this makes the code harder to understand and maintain.

Again, it is a nice workaround, but I think a resolution to this issue would mean supporting independent X/Y transforms within the existing d3 zoom API, not a workaround that tracks the transformations manually and combines multiple 2 dimensional d3 zoom instances.

@richard-biarri
Copy link

richard-biarri commented Apr 14, 2021

An additional issue with the workaroud example above is that it is not possible to apply translationExtent to the zoom behaviour: they are not scaled along the axes correctly.

Edit: ah, translation extents do work as long as you assert them on zoomX and zoomY!

@ricardomatias
Copy link

Please re-open this issue and provide an API for it via a .scaleFactor as shown in https://github.com/wiremind/d3-xyzoom, f.ex. 5-years later this is still a perfect common problem that needs addressing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests