plotly / plotly.js

Open-source JavaScript charting library behind Plotly and Dash
https://plotly.com/javascript/
MIT License
16.8k stars 1.85k forks source link

enable zooming of any rectangular shape on a Plotly heatmap #6586

Open josephernest opened 1 year ago

josephernest commented 1 year ago

Currently there are 2 “zooming” behaviours in Plotly.JS heatmaps:

But often, it is useful to have both, i.e. being able to draw a rectangle selection zoom of any shape + keep square-shape pixels.

Eric found a solution here: https://stackoverflow.com/a/76142244/1422096

IMHO this feature should be available directly in Plotly heatmaps (because it's the standard expected behaviour, which is common in Matplotlib etc.), what do you think? @lvlte @archmoj

alexcjohnson commented 1 year ago

Can you say more about

then you can zoom only with a certain aspect ratio rectangular shape, which is sometimes a limiting factor for the UX/UI

Is your concern that the point where you mouse down is always a corner of the final viewport, but you think the behavior you describe better reflects user intention? That may be the case, ie you zoom by essentially “highlighting” the points you care about, and if that’s smaller than the region needed to satisfy the scale constraint you’d prefer to expand the range so your whole “selection” is visible and centered.

I don’t like the idea of the final viewport being different from what you saw, but if we agree that this mechanism is better we could just change to doing it this way, ie rather than the mouse down point always being a corner, it’s always on an edge, as is the final cursor location, but the box expands symmetrically in whichever dimension it needs to to maintain the constraint.

One potentially weird effect: if you drag across an edge, using this rule the box would expand to include some areas outside the current viewport. Maybe better to avoid that, and expand only as far as the edge of the current viewport, and expand more on the other side when that happens.

josephernest commented 1 year ago

Thank you for your feedback @alexcjohnson.

A few answers to your question

Is your concern that the point where you mouse down is always a corner of the final viewport

No no, this is not really the concern.

I don’t like the idea of the final viewport being different from what you saw

You're right, this is the drawback. But I don't find it problematic. What do you think about Eric's solution from the runnable snippet code here: https://stackoverflow.com/questions/75792497/zoom-on-a-plotly-heatmap/76142244#76142244 ?

About the UX: I showed the current Plotly way to some users in our team, and they were unconfortable by the fact the zooming rectangle aspect ratio is fixed, they wanted a free-shape rectangle.

Matplotlib does another way (which is not optimal though ;))

Zooming: 20230502_145449_b3b5c255

Zoomed: 20230502_145453_d2f983e1

lvlte commented 1 year ago

I don't think one behavior is really better than the other. The scaleanchor constraint as it is currently implemented is constraining, but in the other hand I agree with @alexcjohnson, the fact that the final zoom range is not (or not always) what the user drawn can be disturbing.

One option would be to improve the current implementation by applying the rules used in my code directly to the dragbox, ie. as it is right now (so that the user is still able to see what happens when he drags), but with the behavior Alex suggested if I understood correctly :

rather than the mouse down point always being a corner, it’s always on an edge, as is the final cursor location, but the box expands symmetrically in whichever dimension it needs to to maintain the constraint.

The end result would be a bit less constraining, and without the disturbing effect.

This does not exclude a second option, that would be to introduce a parameter, say constraindragbox: true | false, that tells whether to constrain the dragbox, or only the final zoom range like in the SO answer.

One potentially weird effect: if you drag across an edge, using this rule the box would expand to include some areas outside the current viewport. Maybe better to avoid that, and expand only as far as the edge of the current viewport, and expand more on the other side when that happens.

Exactly, the SO answer does not handle those "edge cases" ;) I will try to improve it. [Edit] : I updated the answer so that the zoom ranges always stay inside the viewport.

josephernest commented 1 year ago

@alexcjohnson Do you think @lvlte's idea could be included in the next release?

josephernest commented 1 year ago

This is the behaviour used in most tools that display data as image or heatmap (or cartography map) : when we zoom, the UI uses all the available width to make it easier to visualize data: here in Photopea but it's the same in Photosthop etc.:

https://github.com/plotly/plotly.js/assets/6168083/dc5a8811-e5d3-4551-8a4e-b3bb82593384

On the other hand, the current status in Plotly heatmaps is this:

https://github.com/plotly/plotly.js/assets/6168083/5c0e1298-7813-4ea6-a86b-dd8c55bd649e

All the free space is lost on left and right, thus the visualization during zoom is not optimal.

If the width is small, and height is high, this effect is even more extreme.

What do you think @lvlte @alexcjohnson ?

lvlte commented 1 year ago

The scaleanchor constrain is one thing (which also makes the shape of the zoom window fixed), but how it is applied on a given axis (that is, either by increasing its "range" or by decreasing its "domain") is another thing which is determined by constrain and constraintoward.

In your example the constrain is on the domain whereas you expect it to be on the range. This happens because you also have an image trace on the plot :

constrain - [...] Default is "domain" for axes containing image traces, "range" otherwise.

You can't remove the axes scaleanchor that comes with an image trace, but you can still set the constrain you want.

josephernest commented 4 months ago

Hi @lvlte, do you think there have been updates about this since our last emails? i.e. is this behaviour now available out-of-the-box in Plotly.js or is your patch (https://stackoverflow.com/a/76142244) still required for this? Thanks in advance!

PS: after using this patch, sometimes I want to change the xaxis / yaxis range manually. Then when using:

Plotly.relayout("heatmap", { 'xaxis.range': xrange,   'yaxis.range': yrange })

breaks the behaviour from the patch.