Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated#29919
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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
lib/matplotlib/path.py Outdated
@@ -667,13 +667,27 @@ def intersects_bbox(self, bbox, filled=True): | |||
def interpolated(self, steps): | |||
""" | |||
Return a new pathresampled to length N x *steps*. | |||
Return a new pathwith each `LINETO` resampled to *steps* `LINETO`'s. |
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.
Returnanewpathwitheach`LINETO`resampledto*steps*`LINETO`'s. | |
Returnanewpathwitheachsegmentisdividedinto*steps*parts. |
Note that "segment" is the term for one "step" (c.f.https://matplotlib.org/devdocs/api/path_api.html#matplotlib.path.Path.iter_segments)
Edit: I've just read one maybe "each LINETO step ..."
lib/matplotlib/path.py Outdated
Codes other than `LINETO` are not handled correctly. | ||
Codes other than `LINETO` and `MOVETO` are not handled correctly. |
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.
Not checked, but what about CLOSEPOLY? That should also be sub sampled.
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 can go wrong. Adapting the example fromhttps://matplotlib.org/stable/gallery/shapes_and_collections/compound_path.html
importmatplotlib.pyplotaspltfrommatplotlib.patchesimportPathPatchfrommatplotlib.pathimportPathcodes= [Path.MOVETO]+ [Path.LINETO]*2+ [Path.CLOSEPOLY]vertices= [(4,4), (5,5), (5,4), (0,0)]path=Path(vertices,codes)pathpatch=PathPatch(path,facecolor='none',edgecolor='blue',linewidth=3,label='original')pathpatch2=PathPatch(path.interpolated(2),facecolor='none',edgecolor='red',linewidth=3,linestyle='dashed',label='interpolated')fig,ax=plt.subplots()ax.add_patch(pathpatch)ax.add_patch(pathpatch2)ax.autoscale_view()ax.legend()plt.show()
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 vertex corresponding toCLOSEPOLY
is not correctly ignored here; it should be interpolated from the previous point to the first point in the path (i.e., the most recentMOVETO
or the first point in the whole thing.)
lib/matplotlib/path.py Outdated
if self.codes[-1] == self.CLOSEPOLY and not np.all(self.vertices[-1] == | ||
self.vertices[0]): |
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 assuming that after splitting byMOVETO
, anyCLOSEPOLY
will be at the end of the path. Is that correct?
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 looks like_iter_connected_components
only splits onMOVETO
, so it wouldn't noticeCLOSEPOLY
. I'm not sure what happens to aPath
withCLOSEPOLY
stuck in the middle.
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.
A CLOSEPOLY the middle can only happen if it was not followed by a MOVETO in the original path. But that means you closed a shape, and continue to draw from that position. People would likely just use LINETOs in such a case, But AFAIK there's nothing in the rules prohibitingLINETO, CLOSEPOLY, LINETO
, and there could be rare cases where it makes sense/could happen.
That said, I haven't checked what we actually do here and whether that pattern renders reasonably.
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.
Hmmmm
importmatplotlib.pyplotaspltfrommatplotlib.patchesimportPathPatchfrommatplotlib.pathimportPathcodes= [Path.MOVETO]+ ([Path.LINETO]*2+ [Path.CLOSEPOLY])*2vertices= [(4,4), (5,5), (5,4), (0,0), (3,3), (4,3), (0,0)]path=Path(vertices,codes)pathpatch=PathPatch(path,facecolor='none',edgecolor='blue',linewidth=3,label='original')pathpatch2=PathPatch(path.interpolated(2),facecolor='none',edgecolor='red',linewidth=3,linestyle='dashed',label='interpolated')fig,ax=plt.subplots()ax.add_patch(pathpatch)ax.add_patch(pathpatch2)ax.autoscale_view()ax.legend()plt.show()
It seems that the rendering at least handles internalCLOSEPOLY
's. So probably we should handle them here too.
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.
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.
Edit: given the axes limits here go down beyond (0,0), I guess that whatever tells
autoscale
the extent of the path also does not know to ignoreCLOSEPOLY
.
I'd say this is a separate topic. Without checking: I suspect that autoscale just takes all the points, no matter what the associated code is (likely also the control points for curve).
At least forCLOSEPOLY
people can work around this by using existing coordinates.
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'd say this is a separate topic.
Sure, that was very much an aside - not proposing to investigate it 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.
This seems like it could be considered a bug-fix and backported?
def test_interpolated_closepoly(): | ||
codes = [Path.MOVETO] + [Path.LINETO]*2 + [Path.CLOSEPOLY] |
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.
Test a compound path with multiple closepolys by doubling this again similar to your above test to make sure the implementation handles multiple MOVETOs and CLOSEPOLYS together?
I could go either way. I figured it's a New Feature since we are making a public method do something it didn't before (and the docstring stated it didn't). OTOH this clearly fixes a bug for the geo projections, and I'll be astonished if anyone is relying on these codesnot being supported. |
I just realized that this does not in fact close#29917. I had not checked unfilled contours, but they are even more broken than filled contours. |
rcomer commentedApr 17, 2025 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
OK the problem with the unfilled contours is just that it has an empty path, so ended up at
This is an easy fix, though I am slightly worried that different input data could turn up different bugs. |
if self.codes is not None and self.CLOSEPOLY in self.codes and not np.all( | ||
self.vertices[self.codes == self.CLOSEPOLY] == self.vertices[0]): | ||
vertices = self.vertices.copy() | ||
vertices[self.codes == self.CLOSEPOLY] = vertices[0] |
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 patch is not sufficient if you have a path with mixtures of CLOSEPOLY AND MOVETO. The MOVETO creates a new separate separate component with a starting point that is generally different fromvertices[0]
.
Maybe a variation of_iter_connected_components
is the way to go?
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.
But if we got to here, we have no internalMOVETO
's because we already split on them and called the function again on the subpath. I think this case is covered by the newtest_interpolated_moveto_closepoly
.
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.
Yes, you are right.
Uh oh!
There was an error while loading.Please reload this page.
I don't have a strong opinion on 3.10.2 vs 3.11. Either is ok, and their releases will likely not be far apart. |
OK, I think there are two votes for v3.10.2 and none against, so I removed the whatsnew. |
5ff6a93
intomatplotlib:mainUh oh!
There was an error while loading.Please reload this page.
…paths in Path.interpolated
…919-on-v3.10.xBackport PR#29919 on branch v3.10.x (Handle MOVETO's, CLOSEPOLY's and empty paths in Path.interpolated)
Uh oh!
There was an error while loading.Please reload this page.
PR summary
Fixes#29917. The geo projection's path transform relies on
Path.interpolated
matplotlib/lib/matplotlib/projections/geo.py
Lines 249 to 252 inb7e7663
At v3.8
ContourSet
changed from having one path per contour to one path per level, and so its paths can now contain internalMOVETO
's. We can make theinterpolated
method handle those by just splitting the path up, applying itself to each sub-path, and re-joining the result. The example code provided in#29917 now givesFor
contour
the given example also had one or more empty paths which caused an exception in the interpolation. We can just pass those straight out again.I welcome better wording for the docstring, though I do not think the original was correct since a unclosed path with N vertices would have become a path with
(N-1) * steps + 1
vertices.PR checklist