-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Adding 2d support to quadmesh set_array #16908
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
lib/matplotlib/collections.py
Outdated
def set_array(self, A): | ||
# Allow QuadMesh.set_array(A) to accept 2d input | ||
# as long is it is the same size as the current 1d data | ||
if A.ndim == 2 and A.size == self._A.size: |
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.
It might make sense to actually issue a warning here if A.size != self._A.size
to address some common issues of the edges (n+1, m+1) compared to the data (n, m). Thoughts?
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 a little confused as to why A needs to be raveled at all here? Why does QuadMesh store its array as 1-D?
I think we need to be careful here to handle all the cases from #16258. |
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 guess this needs to drop the last row and column if the old style co-ercion is to take place?
As noted below, I don't see why A
needs to be ravel-ed? Why don't we just store A
in place as an array?
I agree, @jklymak, I think that it would be ideal to store the actual data array. The current docstrings mentions matplotlib/lib/matplotlib/collections.py Lines 1907 to 1932 in d9b722f
The reason I chose this simplistic thing was that I didn't want to go down the rabbit hole of deprecation issues. Would it be possible to change that at this point, even? My guess is that people may be using Even more confusing through this all is that you can and The two methods handle the different sized arrays differently. These seem like they could be quite confusing mistakes to track down! After that long digression... I agree that this current implementation I have here is less than ideal and may lead to more confusion down the line. I would be willing to add 2d capabilities to |
639fa14
to
8ddf3ea
Compare
I looked a little more into this and the current backend implementors expect the shape of coordinates to be (m+1, n+1, 2), and facecolors/edgecolors to be (m*n). This is baked into the backends already to be flattened arrays and my guess is it would possibly cause a headache to change this. I just pushed up a new proposed modification that will allow There could be more checks done on the Python side of things to make sure arrays are the right size etc... But, that also wasn't done before so I'm not sure it should be done. |
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 seems an improvement to me. We can discuss doing something with more context later.
This is the expected behavior due to the way imshow works under the hood (it has both a data array and an extent see https://matplotlib.org/tutorials/intermediate/imshow_extent.html). The alternate behavior (the extent changing when you set the data) would be much more confusing. Does fail loudly (if from a not great place) if you set miss-shapen data? Are there cases that used to fail that now pass? |
@@ -782,7 +782,8 @@ def update_scalarmappable(self): | |||
"""Update colors from the scalar mappable array, if it is not None.""" | |||
if self._A is None: | |||
return | |||
if self._A.ndim > 1: | |||
# QuadMesh can map 2d arrays | |||
if self._A.ndim > 1 and not isinstance(self, QuadMesh): |
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.
Could we do the raveling here as
if isinstance(self, QuadMesh):
self._A = self._A.ravel()
else:
raise ValueError(...)
It keeps everything a bit more consistent shape wise?
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.
That's actually what I had originally, but force-pushed over. The downside to it is if someone calls get_array() it is a different shape returned, which could cause confusion?
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.
That is a fair point, but I am also worried about the shape stability of people who have code written against QuadMesh who are now going to be suprised that sometimes they get back 2d data.
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.
That should only happen if they pass in 2d data, which was not possible before. So, I think all the before cases were 1d inputs and will return 1d inputs still. This is really for my selfish future motivation of wanting to call update animations without forgetting to ravel() and get the ValueError thrown my way. I completely agree though, it does add another layer of potential confusion and that should be weighed on pros/cons.
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 am worried about the (hypothetical) that someone has written a function that takes in a QuadMesh
, uses get_array()
, and assumes 1d data. If we do the reshaping at the last minute then that assumption is no longer valid, but there is no way for the function author to reasonably know.
I do see both sides of this and neither is obviously better.
I am 👍 on this in principle, have a small concern that this is going to mask other bugs. |
So do we need tests for the various other input shape combinations? I guess at least, it would be nice to have tests for invalid shapes. |
Unfortunately, there aren't really any "bad" shapes now. Quadmesh will accept flattened arrays that are larger or smaller no problem and then either chop the data off or tile it up for you respectively. See this long comment for the background there: #16908 (comment) I'm hesitant to add a strict check on |
095618d
to
1df7e92
Compare
@greglucas we have a preference for rebasing rather than merging the master branch into feature branches. I took the liberty of doing the re-base and force pushed to your branch. |
1df7e92
to
5e5ac01
Compare
Thanks! I also noticed that one of the comments was incorrect now too, so I pushed up a change for that just now. |
I think this is OK as a logical improvement, but perhaps the next step should be to override Quadmesh.set_array so that it checks that the dimensions of its argument, whether 1-D or 2-D, are consistent with the mesh dimensions set when the Quadmesh was instantiated. |
PR Summary
Adds the ability to call
set_array()
with 2d input arrays, which are what users typically make quadmesh's with.It still calls
ravel()
under the hood so that the private data is still 1-dimensional, it is just a convenience method for users. Relates to a portion of #15388.PR Checklist