-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Quadmesh.set_array validates dimensions #20215
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
Conversation
There was a problem hiding this 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 while, please feel free to ping @matplotlib/developers
or anyone who has commented on the PR. 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.
This needs to pass the "checks" above, whereas it seems a lot is broken ;-) |
Drill down on those tests to see what fails, and then I usually work on them on my home machine until they pass, but that does mean you need to setup py.test locally. |
I'm a little confused on how the coverage is decreasing, all of the new code is tested and works fine but the coverage seems to be an issue. |
@jklymak Does this need a mention in the api_changes? |
I'm not sure - did it raise a type error before? If the error type is different that probably merits a API change note |
The changes raise an error when faulty data is passed to |
Sure, but presumably faulty data raised an error before, at least at draw time? |
At draw time yes, but not while using |
lib/matplotlib/cm.py
Outdated
if isinstance(self, mpl.collections.QuadMesh): | ||
A_ = A.ravel() | ||
width, height = self._meshWidth, self._meshHeight | ||
if self._shading in ['flat', 'nearest', 'auto']: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I should have seen this sooner. This check isn't compatible with flat
or auto
- please see the docstring for pcolormesh...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function needs to be called when the QuadMesh is initialized to make sure it is compatible with the initialization. I don't know if that means we want to drop extra data for flat
or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah sorry I misunderstood how auto
works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this function needs to be called when the QuadMesh is initialized to make sure it is compatible with the initialization. I don't know if that means we want to drop extra data for
flat
or not.
When quadmesh is initialized the arguments are already validated for faulty data right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, when shading = flat
, should set_array
only accept data of same shape as X and Y (where extra data is dropped) or should it accept data of where it's dimensions are one less than X and Y, or should it accept both?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the shading for A [NxM]
nearest
: x[M], y[N]
flat
: x [M+1], y[N+1] is preferred, but for back-combat x[M], y[N] works by dropping last row and column of A (so it is now [N-1 x M-1]
auto
: Chooses nearest
if x [M], y[N], or flat
if x [M+1], y [N+1]
gouraud
: x[M+1], y[N+1].
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, okay thanks for explaining it.
One more small question I've got is
matplotlib/lib/matplotlib/axes/_axes.py
Line 5727 in 81f0624
shading = 'flat' |
When shading is set to
auto
and like you've mentioned auto
: Chooses nearest
if x [M], y[N], or flat
if x [M+1], y [N+1].Shading is set back to being flat when x[M[, y[N] and C is of the same shape, this basically causes
Quadmesh._shading
to be set to flat
regardless of the shading
set to auto
, it's never nearest
when set to auto
.
@jklymak I've made these changes and it should account for when shading is |
lib/matplotlib/cm.py
Outdated
A_ = A.ravel() | ||
width, height = self._meshWidth, self._meshHeight | ||
if self._shading == 'flat': | ||
if not (len(A_) == width * height or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we now need to check all these, not only that they error, but that they work if the set_array is correct? Like I'm still not sure what happens if you have shading=flat and pass len(A) == width*height after the fact.
Sorry, this might not have really been a good fist issue because of the goofy semantics of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, no worries :) before any of the changes made, I had a look at this, when len(A) == (width+1)(height+1) and shading
= flat
, the same thing happens where the last dimensions are dropped. When len(A) == widthheight, then it's the same thing as setting A with the dimensions of one less than X and Y.
Here, when shading is set to flat
, regardless of the shape of C
passed to pcolormesh
, the height (self._meshHeight) and width (self._meshWidth) are always the same shape as one less than X and Y.
For example :
# shape of xe is (10,)
# shape of ye is (14,)
# shape of c is (14,10)
col = plt.pcolormesh(xe, ye, c, shading='flat') # works with drops the dimensions to get (13, 9) accordingly.
# But self.+_meshHeight, self._meshWidth is = (13, 9)
# when
# shape of xe is (10,)
# shape of ye is (14,)
# shape of c is (13,9)
col = plt.pcolormesh(xe, ye, c, shading='flat') # works as intended without dropping any dimensions.
# And self.+_meshHeight, self._meshWidth is = (13, 9), same as that when c is of (14, 9)
So my point is regardless of the shape of A passed to set_array
, in the example listed above it should either be (13, 9) or (14, 10) as self._meshHeight and self._meshWidth is already automatically set to (13, 9) if the dimensions are dropped
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, just checking. I'd forgotten if the dropped row/column happened when it was ingested or when it was drawn...
lib/matplotlib/collections.py
Outdated
A_ = A.ravel() | ||
width, height = self._meshWidth, self._meshHeight | ||
if self._shading == 'flat': | ||
if not (len(A_) == width * height or |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Um, don't you have to check width and height separately? Otherwise, the dfiffernce between (3, 4) and (4, 3) would not be catched.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yikes I forgot about that case. My apologies, I'll fix this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The simplest way seemed to be doing it with a flag, otherwise we'd have to repeat
raise TypeError(f"Dimensions of A {shape} are incompatible with X ({width}) and/or Y ({height})")
multiple times
lib/matplotlib/collections.py
Outdated
width, height = self._meshWidth, self._meshHeight | ||
|
||
if self._shading == 'flat': | ||
if len(A.shape) == 1: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we allow len(A.shape())==1
arrays to be passed and they get reshaped somehow? If so, I'm not sure why we would block other arrays that fit? I tried to track down what happens but could not quickly...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the assumption that we only allow flattened arrays that fit, isn't that true? I'm not sure I understand what other arrays that fit mean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was under the assumption that we only allow flattened arrays that fit, isn't that true?
I don't know. That is what I am asking.
Actually, before any of the changes were made, So based on @timhoffm's suggestion, I don't really think we should be checking for height and width separately. |
Right, so the new checks you've added would be an API change, which I don't think we want to get into here. So maybe you should go back to what you had before where you just checked that len(A.ravel()) == height*width (etc). Unless @timhoffm wanted an API change (which wouldn't be terrible, but we would need to make sure the API change note reflected that) |
Yeah, makes sense. I'll revert back to those changes. Thanks for explaining it :) |
I'd wait for Tim to comment. He is the API boss but he may not have realized this is a change. |
Yes, will do |
In the light of #18472 (comment) and my reply below that I propose the following:
While color values are flattened before passing them to the renderer, it does not make sense to accept other shapes of matching total number of elements or even higher-dimensional data. Whether we should deprecate 1D inputs as well should be discussed separately. This would violate the Liskov substitution principle. We'd have to weigh that against a cleaner more narrow API. |
@jeffreypaul15 I'm sorry, I currently don't have bandwidth to look into this. Will take a couple of days for a response. |
No worries |
I think your other PRs are in? You should rebase to remove those changes from this one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Almost good to go. Only some comment/documentation to do.
@@ -2047,6 +2046,40 @@ def set_paths(self): | |||
self._paths = self._convert_mesh_to_paths(self._coordinates) | |||
self.stale = True | |||
|
|||
def set_array(self, A): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is public API, so it needs a docstring.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, will do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not really sure how to structure this docstring, please make any changes if required
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it would have inherited a docstring from ScalarMappable
, I think.
matplotlib/lib/matplotlib/cm.py
Lines 363 to 373 in 63261cc
""" | |
Set the value array from array-like *A*. | |
Parameters | |
---------- | |
A : array-like or None | |
The values that are mapped to colors. | |
The base class `.ScalarMappable` does not make any assumptions on | |
the dimensionality and shape of the value array *A*. | |
""" |
Though some of that needs to be overridden anyway, so it's good to write one here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, my docstring suggestions apparently had unneeded spaces 🙄 , which cause flake8 to complain.
You can directly merge the corrections from the github interface. We'll squash-merge this PR anyway.
@@ -2047,6 +2046,40 @@ def set_paths(self): | |||
self._paths = self._convert_mesh_to_paths(self._coordinates) | |||
self.stale = True | |||
|
|||
def set_array(self, A): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it would have inherited a docstring from ScalarMappable
, I think.
matplotlib/lib/matplotlib/cm.py
Lines 363 to 373 in 63261cc
""" | |
Set the value array from array-like *A*. | |
Parameters | |
---------- | |
A : array-like or None | |
The values that are mapped to colors. | |
The base class `.ScalarMappable` does not make any assumptions on | |
the dimensionality and shape of the value array *A*. | |
""" |
Though some of that needs to be overridden anyway, so it's good to write one here.
The requested change has been addressed.
This is great @jeffreypaul15 Did you want to rebase to just one commit? We can squash it from our end if you prefer... |
https://www.internalpointers.com/post/squash-commits-into-one-git is as good a guide as any to how to do this... |
Thanks, but I've got a history of messing things up when squashing. I'll have a look at this and in the meantime you can squash it, thanks for reviewing all of this repeatedly 😄 |
.. I pinged you on gitter |
b9bfdb3
to
f4d2d61
Compare
Congratulation @jeffreypaul15 Sorry that was such a tough one! |
Haha glad that it's done. Thanks for the help and multiple reviews |
PR Summary
PR Checklist
Fixes #17508
pytest
passes).flake8
on changed files to check).flake8-docstrings
and runflake8 --docstring-convention=all
).doc/users/next_whats_new/
(follow instructions in README.rst there).doc/api/next_api_changes/
(follow instructions in README.rst there).