-
Notifications
You must be signed in to change notification settings - Fork 143
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
Comments
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. |
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. 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 In my opinion, it would be very beneficial and likely a low risk feature to extend the transform to 4 values, 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. |
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? |
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.
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:
Agreed. In that case, it might be good for |
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. |
I have D3 v4 working with a forked build of the |
Agreed. Any thoughts on the 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 |
Here's the monkey-patch I apply. It's definitely "just get it done" quality, but ...
Then I use it like |
i've the same issue. |
I'd like to be able to set a different |
I just learned about https://github.com/trinary/d3-transform/ There's also a PR to update it for V4 trinary/d3-transform#19 |
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 ( 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 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? |
I released an independent d3 plugin: d3-xyzoom. |
Perhaps as d3-zoom 2.0 we should generalize the matrix to match CSS 2D matrix transformations: a c tx 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. |
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:
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 |
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. |
Hi guys, any news about this issue? Just like @DaWaD3's example shows. @mbostock could you please consider declaring this issue as bug, as this works on d3v3? |
@Root-Core I had same trouble about this issue too .. it had work easily in d3 v3 . hi, @joshuahiggins , i can't find your version. what about your fork/pr now ? |
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. ;) |
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? |
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? |
@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. |
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
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.
The X/time scale handles the unmodified transform, as it can both pan and zoom
However, the data scales need to get a transform without the zoom component. If you just modify You unfortunately can't call
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
Of course, this all falls apart with any device that can create simultaneous (combined) zoom and pan |
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. |
@ohenrik Would #48 (comment) help? You can see it in action in #48 (comment) |
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: |
I tried implementing the code from #48 (comment) however Transform is not defined. What object is this? Do i get it from d3? |
Hello All, I am wondering what could be an appropriate way to zoom in matrix scatter plot. 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. |
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) |
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. |
I made a hack-like workaround to achieve zooming only one axis and pan both x and y axes. 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 |
Here's a working example using |
aldanor, How could I get the complete code for your working copy? It looks pretty clean. Thanks |
@kaltal the linked codepen contains a complete self-contained example. |
How do I apply this plugin to d3 v4. I'm using react 16.4 and create-react-app 2.0. Thanks |
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. |
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> |
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 |
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 |
Have been struggling with this implementation related to this problem for several weeks. Open bounty, any help appreciated. |
I've made this example of a double zoom chart; somewhat arbitrarily, the features are:
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. |
The solution / example notebook is published https://observablehq.com/@d3/x-y-zoom |
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! |
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];
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. |
Thanks! I've fixed this issue (using this instead of |
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:
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. |
An additional issue with the workaroud example above is that it is not possible to apply Edit: ah, translation extents do work as long as you assert them on |
Please re-open this issue and provide an API for it via a |
With
zoom.x()
andzoom.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 singlek
value withkx
andky
values and extending thetransform.scale
method to accept an optional second parameter. By default, when called with a single parameter,kx
andky
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.
The text was updated successfully, but these errors were encountered: