Skip to content

fix quiver3d incorrect arrow colors #27754

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 5 commits into from
Mar 3, 2024
Merged

Conversation

AlecVercruysse
Copy link
Contributor

@AlecVercruysse AlecVercruysse commented Feb 7, 2024

PR summary

Closes #11759, which identifies that when setting individual arrow colors of a quiver3d plot, the arrow heads are colored incorrectly (differently than the bodies). PR #13742 attempted to address this issue but is now stale.

The default cycling logic in LineCollection should be good enough to handle the logic of assigning the correct property to each line (body, head 1, head 2) in an arrow. I switched the order that the lines were passed to the Line3DCollection to make use of this cycling. I also removed the logic that got rid of length 0 lines, since that messed up cycling (e.g., in the example below, only 9 lines are plotted but 10 colors are provided).

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

x = np.zeros(10)
y = np.zeros(10)
z = np.arange(10.)
dx = np.zeros(10)
dy = np.arange(10.)
dz = np.zeros(10)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

arrow_color = plt.cm.Reds(dy/dy.max())
ax.set_xlabel('x')
ax.set_ylabel('y')
ax.quiver(x, y, z, dx, dy, dz, colors=arrow_color)
ax.set_ylim(0, 10)
plt.show()

I filled out the below checklist with the idea that no changes need to be made to examples or documentation, as this is a bugfix. Running pytest test_quiver.py in matplotlib/lib/matplotlib/tests passes.

Thank you for reviewing!

PR checklist

Copy link

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

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

Thank you for opening your first PR into Matplotlib!

If you have not heard from us in a week or so, please leave a new comment below and that should bring it to our attention. Most of our reviewers are volunteers and sometimes things fall through the cracks.

You can also join us on gitter for real-time discussion.

For details on testing, writing docs, and our review process, please see the developer guide

We strive to be a welcoming and open project. Please follow our Code of Conduct.

@AlecVercruysse
Copy link
Contributor Author

As an aside, at first I tried to run pytest "in the root directory of my development repository" (e.g. matplotlib/) as advised in the docs. That resulted in the below error:

E   ImportError: cannot import name '_version' from partially initialized module 'lib.matplotlib' (most likely due to a circular import) (/home/avercruysse/repos/matplotlib/lib/matplotlib/__init__.py)

I later got the test working by running pytest in lib/matplotlib/tests/. Am I running the tests correctly? Should that doc be updated to say that pytest needs to be run in lib/matplotlib/tests? Thanks!

@AlecVercruysse
Copy link
Contributor Author

Whoops, looks like I was running the wrong tests. Now when I run pytest in lib/mpl_toolkits/mplot3d/tests, I get some interesting failures:

  1. test_quiver3d image comparison fails with RMS of 0.002, but I can't find a difference:
    result:
    quiver3d
    expected:
    quiver3d-expected
    diff:
    quiver3d-failed-diff

  2. test_errorbar3d_errorevery fails similarly (RMS 0.003):
    result:
    errorbar3d_errorevery
    expected:
    errorbar3d_errorevery-expected
    diff:
    errorbar3d_errorevery-failed-diff

Is it fair to replace the expected images with the result images?

@oscargus
Copy link
Member

oscargus commented Feb 7, 2024

It is possible to set a tol argument to the image_comparison decorator. This is the slightly preferred method since the tolerance is indeed very small. If you can add a comment that says something like "remove tolerance when regenerating the test image" it would be appreciated (so that we know that it is not, e.g. architecture dependent, but an outcome of a minor numerical change).

@AlecVercruysse
Copy link
Contributor Author

Thank you for the input! I've edited the test case accordingly.

@AlecVercruysse
Copy link
Contributor Author

Hi, bumping this for review. I think this is a pretty minor change that makes a big difference, since as of now, attempting to color-code quiver3d arrows doesn't work without a convoluted work-around that requires people to understand the internals of quiver3d. Judging by Stack Overflow, it's something a lot of people (including me) run into. I've pasted a before and after image below in case that's necessary.

before:
before

after:
after

I think the fix as presented in this PR would cover 99% of all use cases (i.e., the length of the color list being the same length as the number of quiver arrows being plotted). Only in some edgecase where the length of the list of colors is not an even divisor of the length of the list of arrows would the old behavior revert, where arrowheads have a different color than their bodies.

If this edge-case needs to be covered, an alternative solution would be to construct a single line for each arrow (shaft and each head included), as I tried on another branch. This requires drawing the same line twice (i.e. arrow tip to the end of one head, back to an arrow tip, to the end of another head), and it results in a minor, but visible, change to the look of the arrowhead. It also changes the number of lines that are returned.

Thank you for your time!

@timhoffm
Copy link
Member

timhoffm commented Mar 1, 2024

As a quick confirmation, without the time to look into the code: This approach is to be preferred over the single line due to back compatibility.

@scottshambaugh
Copy link
Contributor

scottshambaugh commented Mar 1, 2024

Hi @AlecVercruysse, these changes look good to me. Could you add another image test for this behavior so we catch future regressions? The one in your most recent comment looks like a good example.

Copy link
Contributor

@scottshambaugh scottshambaugh left a comment

Choose a reason for hiding this comment

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

Once the CI tests pass, this looks good to me for merge. Congratulations @AlecVercruysse on your first contribution to matplotlib, and thank you for implementing this fix! We welcome future improvements and hope to see more of you!

Once this is merged in, I'd recommend linking to this PR in a new stackoverflow answer, and saying which matplotlib version this will be available in.

@AlecVercruysse
Copy link
Contributor Author

@scottshambaugh Thank you! To confirm, this will appear in 3.8.4?

@scottshambaugh scottshambaugh added this to the v3.8.4 milestone Mar 1, 2024
@scottshambaugh
Copy link
Contributor

scottshambaugh commented Mar 1, 2024

Yes, this is a bugfix and has no API changes so I think this is appropriate for a patch release.

@timhoffm timhoffm merged commit 365c950 into matplotlib:main Mar 3, 2024
meeseeksmachine pushed a commit to meeseeksmachine/matplotlib that referenced this pull request Mar 3, 2024
dstansby pushed a commit to meeseeksmachine/matplotlib that referenced this pull request Mar 3, 2024
ksunden added a commit that referenced this pull request Mar 6, 2024
…754-on-v3.8.x

Backport PR #27754 on branch v3.8.x (fix quiver3d incorrect arrow colors)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Waiting for author
Development

Successfully merging this pull request may close these issues.

The color of the 3D arrow head does not match that of the arrow body
4 participants