Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bugfix #18600 by using the MarkerStyle copy constructor #18603

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 10 commits into from
Oct 2, 2020
Merged

bugfix #18600 by using the MarkerStyle copy constructor #18603

merged 10 commits into from
Oct 2, 2020

Conversation

evanberkowitz
Copy link
Contributor

@evanberkowitz evanberkowitz commented Sep 29, 2020

#Deeper marker copies in Line2D update_from

Markers already support a copy construction: MarkerStyle.set_marker
accepts other instances of MarkerStyle. However, Line2D, when
copying markers, instead construct the copy from the other instance's
get_marker and get_fillstyle methods. This destroys (unsupported)
manipulations the user might make to the marker, as discovered in
issue #18600.

By switching constructors, we can get a more faithful copy without
explicitly relying on any of MarkerStyle's private methods in places
they do not belong.

PR Summary

This change is also more in line with the self = other style of the rest of the method's body.

PR Checklist

  • Has pytest style unit tests (and pytest passes).

    • Some tests failed, but they also failed when I cloned master:
      • test_pcolormesh_alpha[pdf]
      • test_transparent_markers
      • test_xelatex[pdf]
      • test_pdflatex[pdf]
      • test_rcupdate
      • test_mixedmode[pdf]
      • 6 failed, 7729 passed, 89 skipped, 15 xfailed in 1031.87s (0:17:11)
  • Is Flake 8 compliant (run flake8 on changed files to check).

  • [N/A] New features are documented, with examples if plot related.

  • [N/A] Documentation is sphinx and numpydoc compliant (the docs should build without error).

  • Conforms to Matplotlib style conventions (install flake8-docstrings and pydocstyle<4 and run flake8 --docstring-convention=all).

  • [N/A] 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).

Markers already support a copy construction: `MarkerStyle.set_marker`
accepts other instances of `MarkerStyle`.  However, some Artists, when
copying markers, instead construct the copy from the other instance's
`get_marker` and `get_fillstyle` methods.  This destroys (unsupported)
manipulations the user might make to the marker, as discovered in
issue #18600.

By switching constructors, we can get a more faithful copy without
explicitly relying on any of `MarkerStyle`'s private methods in places
they do not belong.
Copy link
Contributor

@dopplershift dopplershift left a comment

Choose a reason for hiding this comment

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

Change seems good. Would you be able to update the docstrings for MarkerStyle.__init__ and MarkerStyle.set_marker()? I had to go spelunking in the code to know that this was even correct.

@evanberkowitz
Copy link
Contributor Author

I had to go spelunking in the code to know that this was even correct.

Believe you me I understand THAT feeling!

I will update the doc strings (maybe tomorrow, maybe Thursday) and re-ping you here.

@QuLogic QuLogic added this to the v3.4.0 milestone Sep 29, 2020
 - Mention errorbar in the module docs.
 - Add details of the copy constructor.
 - Move errorbar mention to new line.
 - Remove whitespace from blank lines.
@evanberkowitz
Copy link
Contributor Author

@dopplershift I updated the docs of set_marker and __init__ and decided that I disagreed with the remaining flake8 criticisms. I will 'fix' them if you require it, but I think it's extremely legible as is and that obeying the line length requirement would actually reduce legibility in this case.

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.

You still have some flake8 issues as well.

@tacaswell
Copy link
Member

Could you also add a (smoke) test that this does not blow up?

👍 modulo fixing the style issues and adding a test.

@evanberkowitz
Copy link
Contributor Author

@tacaswell Can you explain what a smoke test means? You are talking to a programming scientist, not a programmer :)

What should I be testing? That Line2D, when given an unusual marker, actually copies the marker? The previous implementation would have worked for all the "standard" markers.

@tacaswell
Copy link
Member

Sorry for the jargon, it is from "turn it on and see if it smokes!".

As your original bug generated a stack trace when it should not have so the minimal test is if you can run that code and not raise (e.g. does not start smoking because it blew up).

A better and more complete test is verifying that it also actually set the marker to what it should be.

@evanberkowitz
Copy link
Contributor Author

@tacaswell If I add the test, aren't we saying that the weird thing I did is in a nebulous not-supported-but-not-NOT-supported thing?

Creating markers from MarkerStyle and passing them to axes could cause
both `plot` and `errorbar` to raise

```
TypeError: float() argument must be a string or a number, not 'MarkerStyle'
```

in 3.2.2, as found in #18600 and discussed in the comments of #18603.

`test_marker_as_MarkerStyle` ensures that a MarkerStyle instance can be
used as the marker argument to plot, scatter, and errorbar.
@evanberkowitz
Copy link
Contributor Author

evanberkowitz commented Oct 1, 2020

OK. I actually was able to reduce my problem even further:

#!/usr/bin/env python3

import matplotlib
import matplotlib.pyplot as plt
from matplotlib.markers import MarkerStyle

fix, ax = plt.subplots()
plt.errorbar([1,2,3],[3,4,5], yerr=[0.5,0.5,0.5], marker=MarkerStyle('d'), linestyle='none', color='green')
plt.show()

which has nothing to do with my transform. In 3.2.2 this throws a TypeError for either errorbar or plot, surprisingly, though scatter is OK. I added a test lib/matplotlib/tests/test_axes.py::test_marker_as_MarkerStyle to make sure that this functionality is and remains supported. This test does not test that the marker is correctly copied, which is what the actual code part of this PR does.

The good news is that 3.3.2 already passes / can run this mwe.

Rather than new paragraphs, each option is a new rst
 - style list item.
 - Add extra blank lines around test_marker_as_MarkerStyle
 - Add spaces in arrays.
(compare with similar test in test_lines)
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.

Still have flake8 issues. You can find them on the Files changed tab.

@tacaswell
Copy link
Member

Modulo the plt vs ax change, the test looks conceptually right!

What you did is definitely in the "supported" category of things.

@evanberkowitz
Copy link
Contributor Author

OK, Here's another very strange thing, but it may warrant its own, separate issue. I suspected that my problem with my explicit legend example was with Line2D. So I wrote this little example

#!/usr/bin/env python3

import matplotlib
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
from matplotlib.markers import MarkerStyle

print(f"{matplotlib.__version__=}")
print(f"{matplotlib.get_backend()=}")

# But when I use it in errorbar
fig_ref = plt.figure()
fig_ref.add_artist(mlines.Line2D([0.25,0.75],[0.25,0.75], marker='d'))
fig_test = plt.figure()
fig_test.add_artist(mlines.Line2D([0.25,0.75],[0.25,0.75], marker=MarkerStyle('d')))
plt.show()

Which outputs

matplotlib.__version__='3.2.2'
matplotlib.get_backend()='MacOSX'

markerstyle-comparison

And then I wrote a little test that would check for that difference in test_lines.py:

@check_figures_equal()
def test_marker_as_markerstyle_comparison(fig_test, fig_ref):
    fig_ref.add_artist(mlines.Line2D([0.25,0.75],[0.25,0.75], marker='d'))
    fig_test.add_artist(mlines.Line2D([0.25,0.75],[0.25,0.75], marker=MarkerStyle('d')))

and the test passes!

> pytest lib/matplotlib/tests/test_lines.py::test_marker_as_markerstyle_comparison
=========================== test session starts ============================
platform darwin -- Python 3.8.5, pytest-6.1.0, py-1.9.0, pluggy-0.13.1
rootdir: /Users/evanberkowitz/src/matplotlib, configfile: pytest.ini
collected 3 items

lib/matplotlib/tests/test_lines.py ...                               [100%]

============================ 3 passed in 2.62s =============================

What?! I tried adding to the above test

    fig_ref.savefig('ref')
    fig_test.savefig('test')

to the test to see if they're REALLY the same or not and sure enough, the both look like this:

test

What gives?

Note that I observe a difference in 3.2.2 but not the latest that my PR is written against.

@QuLogic
Copy link
Member

QuLogic commented Oct 2, 2020

That was fixed in #16692, I think.

@evanberkowitz
Copy link
Contributor Author

OK, that seems correct. I will not add my test then.

@evanberkowitz
Copy link
Contributor Author

@QuLogic---as I mentioned in my comment towards the top of this PR, if you actually look at the code for the flake8 complaints and add a newline, the docstrings become harder to read. If you tell me that you understand that, then I will change them. Otherwise, I advocate ignoring those complaints.

@QuLogic
Copy link
Member

QuLogic commented Oct 2, 2020

We do not ignore flake8 arbitrarily. We have plenty of these lists wrapped, so I don't see how it is less legible.

@evanberkowitz
Copy link
Contributor Author

Widows and orphans are known to typesetters to cause ugly looking or hard-to-read text. In this case the enormous amount of whitespace introduced by the orphans created by wrapping these only-slightly-over-length lines cause visual interruption. A linter usually doesn't take that sort of "global appearance" into account. But I'll change it against my better judgement.

@evanberkowitz
Copy link
Contributor Author

Um... now eslint simply failed to set up? I don't think this one was my fault.

@evanberkowitz
Copy link
Contributor Author

eslint reran and succeeded, so all tests have now passed.

@tacaswell
Copy link
Member

But I'll change it against my better judgement.

On projects that only have a handful (or just one) person working on them it is plausible to have use your judgement on the formatting, but on a project the scale of Matplotlib (we have over 1k people who have touched the code in some way at this point!) we have to rely on automated tools to maintain any consistency in the codebase. It also means we can have the robot tell you the style is wrong than have a person tell you ;)

@tacaswell tacaswell merged commit a66501d into matplotlib:master Oct 2, 2020
@tacaswell
Copy link
Member

Thanks for finding and fixing this @evanberkowitz.

Congratulations on your first Matplotlib PR 🎉 hopefully we will hear from you again!

@evanberkowitz
Copy link
Contributor Author

Glad to contribute. Hopefully there will never be further bugs for me to encounter ;) !

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants