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

Orientation of y-axis in Heatmap #413

Open
hackermd opened this issue Feb 25, 2016 · 23 comments
Open

Orientation of y-axis in Heatmap #413

hackermd opened this issue Feb 25, 2016 · 23 comments
Labels
bug something broken P3 backlog

Comments

@hackermd
Copy link

The default orientation of the y-axis in the Heatmap graph is, at least to me, unexpected. The 0,0 coordinate of the plot is in the lower left corner. A heatmap is generally used to display a matrix, such as an image, however, and the 0,0 index into the matrix would be in the top left corner. An image is thus displayed flipped upside down. One can of course flip the z array or provide the values for y in reversed order, but this keeps the labelling of the axis and the display of y,x in the hover window unaffected. I like the way this is handled in matplotlib.pyplot.imshow, which provides an origin argument.
An additional argument for the Heatmap graph to control the orientation of the y-axis would be nice.

@hackermd
Copy link
Author

hackermd commented Mar 3, 2016

Figured it out. Python example:

plotly.graph_objs.Layout(
    yaxis=dict(autorange='reversed')
)

@hackermd hackermd closed this as completed Mar 3, 2016
@elena-pascal
Copy link

I agree, while one can do
yaxis=dict(
autorange='reversed'
)
this actually changes the handedness of the coordinate system you're working in. If the heatmap is not the final step, let's say you want to compare this against other data this easily becomes a nightmare. I would find it useful this flip of the origin to be made explicit.

@deliciouslytyped
Copy link

@elena-pascal did this ever get solved?

@elena-pascal
Copy link

@deliciouslytyped the issue had been closed a while back and I didn't reopen it just called into the void. Wasn't sure if it's affecting only me. Feel free to reopen if you feel strongly about it.

@deliciouslytyped
Copy link

I think I'd have to post a new issue.

@Shivamshaiv
Copy link

@elena-pascal I think something truly needs to be done, as I am in a point in a project when a lot of time got wasted just to find ways around.

@emmanuelle
Copy link
Contributor

Hi @elena-pascal @Shivamshaiv @deliciouslytyped could you please explain why

yaxis=dict(
autorange='reversed'
)

is not a solution for you? Also note that px.imshow has an origin argument.

@empet
Copy link

empet commented Mar 9, 2020

The Plotly heatmap is intended to represent a discrete version of a scalar field, z= f(x, y), with x, y in a rectangle [a b] x [c, d].
All users participating in this discussion consider that a heatmap is generally used to display a matrix, such as an image, and pixel (0, 0) should be in the upper left corner.
This is the case when using matplotlib. Matplotlib defines a heatmap as an image, but its imshow provides the possibility to set the origin as 'lower' or 'upper'. With origin='lower', you'll get the equivalent of the default plotly heatmap, while origin='upper' has the same effect as setting yaxis_autorange='reversed'

Generally people consider as being the "default definition" that given in a library they used before.

@elena-pascal
Copy link

Hi @elena-pascal @Shivamshaiv @deliciouslytyped could you please explain why

yaxis=dict(
autorange='reversed'
)

is not a solution for you? Also note that px.imshow has an origin argument.

It is the matrix representation aspect as described above. Simply reversing the axis messes up the handedness (I need to differentiate between front and back too) of the plotted matrix, and for a complicated geometry loosing this bit of sanity is undesirable.

@emmanuelle
Copy link
Contributor

Thank you for your answer, but I don't understand the "handedness". Maybe a schematic drawing would help? Did you try passing your data to px.imshow? It should behave exactly like matplotlib's imshow with the same default for the origin.

@elena-pascal
Copy link

handedness

Most matrix maths implies right handed coordinate Cartesian systems (the left image). Flipping one axis is such a system results in having a left handed coordinate system (the right image). Moving from one to another is a pain as the definition of operations, eg rotation, must change. I know this can be a problem because we tried for years to compare different handedness images and left many scratching their heads.

Tbh, I don't quite remember now why I wanted to use heatmap and not imshow.

@deliciouslytyped
Copy link

deliciouslytyped commented Mar 11, 2020

Is there some reason this would be hard to implement? I don't remember the details - it's been a long time since I fought with this issue - but it should just be a simple coordinate transformation on the backend that if we try to fix from the calling side, ends up complicating things unnecessarily.

Since there's things like

plotly.graph_objs.Layout(
    yaxis=dict(autorange='reversed')
)

I see no reason why what we want shouldn't be.

TL;DR: I don't remember what the problem was, but I strongly believe it's valid and I don't see why it would be difficult to fix. And It should be fixed in the library. IIRC it's a major usability pain point if you need it.

@deliciouslytyped
Copy link

I'll just open a new issue pointing to this one.

@nicolaskruchten
Copy link
Contributor

I'm really having trouble understanding the nature of the concern here with "handedness"... Here is what I see today with the default option and the reversed-axis option. The underlying data doesn't change, there's just one argument set to flip the coordinate system at display-time. If this isn't sufficient, what would be?

image

image

@deliciouslytyped
Copy link

deliciouslytyped commented Mar 11, 2020

@nicolaskruchten I'm not sure this will help, but add another gradient on the other axis so that it's not symmetric.
You will see that there are two possible orientations (a flip across the diagonal).
Presumably the result is reversed in the way that is not visible right now.

@nicolaskruchten
Copy link
Contributor

Yes, the x and/or y axes can be flipped and in this case only the y axis was flipped.

@nicolaskruchten
Copy link
Contributor

You can actually tell by looking at the tick labels :)

@deliciouslytyped
Copy link

deliciouslytyped commented Mar 11, 2020

Oh yeah. I think that might have actually been my original problem. :) (not sure though)

@nicolaskruchten
Copy link
Contributor

nicolaskruchten commented Mar 11, 2020

Ok! “problem” in what sense? I’m struggling to see what is not possible today, give that all 4 possible combinations of x/y flip/no-flip are available :)

@elena-pascal
Copy link

elena-pascal commented Mar 12, 2020

I think what we're asking for is a matrix option for heatmap that defaults origin to top left. That would probably be the pythonic way.

The problem is that while heatmap is happy to take a numpy matrix as input, its shown origin is flipped with respect to the usual understanding of the y axis of a matrix. If the matrix has meaning in 3 dimensions than that also affects the z axis, or viewing direction. If a user is not careful and wants to look at matrices with histogram they will run into problems.

Let's consider a matrix:
img = np.matrix([[10, 20, 60], [20, 10, 30], [30, 60, 10]])

Let's say I want to visualize how a 3D 90 degrees rotation around the normal to the image plane looks like for this matrix. If I use the standard definition of rotation then it should be something like this:

rotation

The rotation behaviour is going to be opposite than expected if I were to be more naive about orientations.

While inversing y axis achieves the correct image, and it could well be a solution for visualizing individual images, I don't think it is a good (sane) solution for matrix plotting with heatmap. The user is required to have a good understanding of the effect of axis flipping and why they have to flip y back. This can be tricky to be comfortable with since inversing axes in a matrix comes with more implications, eg affecting handedness, and I think this thread was proof that thinking about 3D orientations is a pain. For me personally, I know that seeing a flipped axis in the code will make me want to double check every time the sanity of the orientations.

summary: I would prefer to have a matrix plotting option that does not invert axes because axes inversion in linear algebra makes me very nervous.

@nicolaskruchten
Copy link
Contributor

nicolaskruchten commented Mar 12, 2020

Thanks for the extra context @elena-pascal, I think I'm understanding.

A couple of points of context from the Plotly.py maintenance perspective:

  1. The meaning of "reversed" is with respect to an origin at the bottom left, which stems from the fact that the underlying layout engine for Plotly.py supports the plotting of multiple traces of different types on the same plot e.g. a heatmap and a scatter, and I suspect the scatter was implemented first.
  2. We cannot really change the defaults without making some possibly-large existing population of users unhappy, because whatever code they have today would unexpectedly result in upside-down heatmaps after an upgrade i.e. this would be a breaking change to our API. Notably, anyone using something like Datashader to overcome overplotting on scatterplots will have the same issue you are facing with respect to default behaviour.
  3. If we did change the defaults, the mechanism whereby we would do so is that we would set yaxis_autorange="reversed" on all plots containing only heatmap traces. This is actually what we do for plots containing only image traces. We made this choice basically because we agree with you! We were able to do this without triggering a "breaking change" when initially implementing image last fall because there was no pre-existing code relying on the other behaviour.
  4. Setting yaxis_autorange="reversed" is exactly what Plotly.js implicitly does under the hood for plots with only image traces and what px.imshow explicitly does under the hood to implement its default origin="upper" option, which works both with single-channel heatmap/matrix-like data and with multi-channel image-like data.

I guess the bottom-line good news is that we do in fact already have built-in functionality in Plotly to display matrix-like data the way you would expect, without forcing you to explicitly declare anything as "reversed": if you use px.imshow and leave the origin setting alone. The bad news is that if you inspect the figure (i.e. print(fig)), you will see a possibly-confusing autorange="reversed" in the y-axis, and there's no way we can change that without upsetting other people.

@elena-pascal
Copy link

elena-pascal commented Mar 12, 2020

Thank you for explaining the implementation implications and taking the time to listen to our concerns.

Yes, I do agree that changing the default would be a nightmare that would benefit way fewer people than those it would negatively affect. Under-the-hood implementation is all I hoped for: I imagined a matrix=True option could implement under the hood yaxis_autorange="reversed" and some sort of useful comment.

I'll remember to use imshow for matrix representation from now on.

@empet
Copy link

empet commented Mar 12, 2020

Taking into account that @elena-pascal considers that some users can have difficulties in undestanding why to get a heatmap as an image referenced to a left handed coordinate system we are performing an upside-down image flipping (i.e. yaxis_autorange='reversed', I suggest to give the following explanation in a plotly tutorials on heatmaps:
If we denote by xOy the left handed coordinate system with origin O in the upper left corner of an image (array) of resolution (shape) m x n, and by XO'Y the right handed coordinate system with origin O in the lower left corner (the usual cartesian system oriented like in math, physics and default Plotly heatmap), then the pixel in the row i, and column j has the coordinates:
x=j , y=i.

The relationship between the coordinates (x,y), and (X,Y) of a pixel is as follows:

X = x
Y = m-1-y

and conversely:

x = X
y = m-1-Y

But these relations point out that getting the coordinates (X Y) from (x,y) or reciprocally amounts
to performing np.flipud(image).

@ndrezn ndrezn reopened this Nov 20, 2024
@ndrezn ndrezn added bug something broken P3 backlog labels Nov 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something broken P3 backlog
Projects
None yet
Development

No branches or pull requests

8 participants