8000 Allow changing the vertical axis in 3d plots by Illviljan · Pull Request #19873 · matplotlib/matplotlib · GitHub
[go: up one dir, main page]

Skip to content

Allow changing the vertical axis in 3d plots #19873

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

Merged
merged 39 commits into from
May 25, 2021

Conversation

Illviljan
Copy link
Contributor
@Illviljan Illviljan commented Apr 5, 2021

PR Summary

Add support for 3d plots to align the y axis vertically instead of the z axis by setting ax.view_init(vertical_axis="y").

Closes #19791.

Requires #20242

Background

I have a hard time grasping what happens when jumping between 2d and 3d scatters because the y changes from the vertical direction to the depth direction.

image

Results

import matplotlib.pyplot as plt
import numpy as np

x = np.array([0, 1, 2, 4])
y = np.array([5, 10])
z = np.array([100, 150, 200])
X, Y, Z = np.meshgrid(*[x, y, z], indexing="ij")
c = np.arange(0, X.size)

plt.figure()
for j, (a, e) in enumerate([(0, 0), (30, 30)]):
    for i, vert_a in enumerate(["z", "y", "x"]):
        ax = plt.subplot(2, 3, j * 3 + 1 + i, projection="3d")
        pc = ax.scatter(X, Y, Z, c=c)
        ax.view_init(azim=a, elev=e, vertical_axis=vert_a)
        ax.set_title(f"azim={a}, elev={e}, vertical_axis='{vert_a}'")
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.set_zlabel("z")

image

PR Checklist

  • Has pytest style unit tests (and pytest passes).
  • Is Flake 8 compliant (run flake8 on changed files to check).
  • New features are documented, with examples if plot related.
  • Documentation is sphinx and numpydoc compliant (the docs should build without error).
  • Conforms to Matplotlib style conventions (install flake8-docstrings and run flake8 --docstring-convention=all).
  • New features have an entry in doc/users/next_whats_new/ (follow instructions in README.rst there).
  • [N/A] API changes documented in doc/api/next_api_changes/ (follow instructions in README.rst there).

@jklymak
Copy link
Member
jklymak commented Apr 8, 2021

I'm sure I'm simply missing the point here, but...

If I have a 2-D axes, and do plot(x, y) and I really want y on the horizontal axes, I don't ask for a kwarg to say which is the horizontal axes, I call the routine plot(y, x) . Similarly here, if I want the y-data to be on the vertical axes I'd just call plot3d(x, z, y).

The larger complaint that our camera angle manipulation is not very flexible is valid, but I don't see that this is a fix for that.

@jklymak
Copy link
Member
jklymak commented Apr 8, 2021

Ooops, sorry I should have read the original proposal (ahem, it actually helps to put more description with the PR if possible).

This appears to be tied only to view_init(az, el, vertical_axis='y'), which I guess makes some sense if the same view cannot be achieved otherwise. This PR would need some examples/tests to show that it works as expected.

@Illviljan
Copy link
Contributor Author

It's a draft for a reason. :) But maybe I should start copy/pasting the issue texts in here as well.

The background is that I want to make it easy jumping between ax.scatter(x, y) and ax.scatter(x, y, z).
Just take your phone and try jumping between a 2d and a 3d view in the shortest possible way.
Then compare that to the gymnastics you have to do follow matplotlibs default view, and then imaging having to do these gymnastics mentally with a difficult dataset.

This is the issues I've managed to figure out:

  • Sure you can just switch the inputs, like ax.scatter(x, z, y). But then processing it further using for example set_zlabel gets rather confusing.
    • Use view_init.
  • Sure you can change the elev and azim a little with view_init but you can only get so far. And then if you want rotate it interactively everything will feel opposite to the default settings.
    • Change the elev/azim equations to match the new vertical axis.
  • Sure you can manage to rotate it until it matches ax.scatter(x, z, y) decently but then you'll notice that the box aspect is different and looks worse to the default values.
    • Change the box aspect to follow the vertical axis as well.

Issues I haven't figured out yet:

  • Labels aren't positioned on the same place as the default view
  • Axis values are positioned to the opposite directions compared to the default view.
  • + A few I probably haven't noticed yet...

@jklymak
Copy link
Member
jklymak commented Apr 8, 2021

Appreciate that it is still draft, but this could get messy fast, so we wanted to give some feedback before you spent too much time on it ;-)

@timhoffm
Copy link
Member
timhoffm commented Apr 8, 2021

In today's dev call, we have decided that view_init(azim, elev, vertical_axis='z') is a reasonable API and semantics.

The semantics should be that for azim=0, elev=0 the three axis should point in the same 3d direction, vertical_axis only defining which one is which in the following way:

grafik

Note: I did not yet look into the implementation. But I assume to achieve this, one should rotate about the (1, 1, 1) direction by 120° / 240°.

@Illviljan
Copy link
Contributor Author

This is how it looks like at the moment:

plt.figure()
for i, vert_a in enumerate(["z", "y", "x"]):
    ax = plt.subplot(1, 3, i+1, projection="3d")
    pc = ax.scatter(X, Y, Z, c=c)
    ax.view_init(azim=0, elev=0, vertical_axis=vert_a)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")

image

And with default angles:

plt.figure()
for i, vert_a in enumerate(["z", "y", "x"]):
    ax = plt.subplot(1, 3, i+1, projection="3d")
    pc = ax.scatter(X, Y, Z, c=c)
    ax.view_init(vertical_axis=vert_a)
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")

image

@Illviljan
Copy link
Contributor Author

Added a test, it should cover most changes I think.

Where in the code does matplotlib determine where the axis values are positioned? I can't find it, any ideas are appreciated:
image

@timhoffm
Copy link
Member

Where in the code does matplotlib determine where the axis values are positioned? I can't find it, any ideas are appreciated:

Check

# Draw main axis line

unfortunately, this code is quite ancient and not one of the clearest.

@WeatherGod
Copy link
Member
WeatherGod commented Apr 12, 2021 via email

@Illviljan
Copy link
Contributor Author

Gotten the black axis lines to match for each vertical axis. It's a little hardcoded unfortunately but I haven't managed to figure out anything better, any ideas here are appreciated.

import matplotlib.pyplot as plt
import numpy as np

x = np.array([0, 1, 2, 4])
y = np.array([5, 10])
z = np.array([100, 150, 200])
X, Y, Z = np.meshgrid(*[x, y, z], indexing="ij")
c = np.arange(0, X.size)

plt.figure()
for j, (a, e) in enumerate([(0, 0), (30, 30)]):
    for i, vert_a in enumerate(["z", "y", "x"]):
        ax = plt.subplot(2, 3, j * 3 + 1 + i, projection="3d")
        pc = ax.scatter(X, Y, Z, c=c)
        ax.view_init(azim=a, elev=e, vertical_axis=vert_a)
        ax.set_title(f"azim={a}, elev={e}, vertical_axis='{vert_a}'")
        ax.set_xlabel("x")
        ax.set_ylabel("y")
        ax.set_zlabel("z")

image

Now I see that the ticks are not aligned correctly so that's the next thing to fix. It'll likely be something similarly hardcoded though, unless it's possible to make use of the gridlines somehow to determine the direction of the ticks?

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
@jklymak jklymak marked this pull request as draft May 11, 2021 15:57
@Illviljan Illviljan marked this pull request as ready for review May 11, 2021 20:18
Copy link
Member
@QuLogic QuLogic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IIUC, this should look approximately the same no matter which axis is vertical, assuming you didn't plot anything or add any axis labels. Would it make sense to use check_figures_equal to compare each pair, instead?

Illviljan and others added 5 commits May 13, 2021 11:39
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
@Illviljan
Copy link
Contributor Author

@QuLogic Eventhough I don't see any difference between the different vertical axes I don't think the plots are exactly equal still, because Axis._AXINFO have different colors for each axis for some reason.
I would prefer the colors to be same though. But I think that will mess with a lot of other tests so it might be for a future PR.

@QuLogic
Copy link
Member
QuLogic commented May 13, 2021

Ah right, I forgot about those. I wonder if those colours should also be juggled, with one consistently indicating the 'bottom'. But I'm not exactly sure what the meaning of those colours was intended to mean in the first place.

@Illviljan
Copy link
Contributor Author
Illviljan commented May 16, 2021

@QuLogic Here's how it looks like with Axis._AXINFO["color"] changed to something more noticeable:
image

I'm leaning towards not changing this. I like that the colors continue to match, it seems like a nice trick to quickly tell where the axes has been rotated.

Copy link
Member
@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This look great! IMHO close to the finish line.

@QuLogic
Copy link
Member
QuLogic commented May 18, 2021

This needs a rebase now that that's been merged.

@QuLogic
Copy link
Member
QuLogic commented May 19, 2021

Are you able to rebase this? Now the unrelated changes moved to the other PR appear in two different commits. Or this could be squash merged.

@Illviljan
Copy link
Contributor Author

No clue how to do that, so I suggest squash merge. :)

Illviljan and others added 2 commits May 19, 2021 07:46
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
@QuLogic QuLogic added this to the v3.5.0 milestone May 25, 2021
@QuLogic QuLogic merged commit b12d983 into matplotlib:master May 25, 2021
@timhoffm
Copy link
Member

@Illviljan thanks for fiddling out the details and going through the somewhat tedious review process. This is a great improvement! We hope to hear from you again. 👍

jklymak pushed a commit to jklymak/matplotlib that referenced this pull request Jun 13, 2021
* allow changing the vertical axis

* change aspect to follow vertical axis,

* add projection test

* Update axes3d.py

* Update test_mplot3d.py

* axis lines behaves the same as default

* Update axis3d.py

* Rotate ticks correctly.

* undo comments for unchanged code

* generalize slightly

* add tickdir test

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update axes3d.py

* move func to method

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/mpl_toolkits/tests/test_mplot3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/mpl_toolkits/mplot3d/axis3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* docstring styling

* Add test for axis lines.

* Update axis3d.py

* Add whats new

* filename typo

* Update lib/mpl_toolkits/mplot3d/axis3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* Update lib/mpl_toolkits/tests/test_mplot3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* Update lib/mpl_toolkits/mplot3d/axes3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* Update lib/mpl_toolkits/mplot3d/axis3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* Use _api.check_getitem

* Update lib/mpl_toolkits/tests/test_mplot3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

* Update lib/mpl_toolkits/tests/test_mplot3d.py

Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>

Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com>
Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add support for 3d plots to align the y axis vertically instead of the z axis
5 participants
0