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

Skip to content

Do not set clip path if it exists #23199

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 1 commit into from
Mar 31, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions lib/matplotlib/axes/_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -687,7 +687,8 @@ def text(self, x, y, s, fontdict=None, **kwargs):
**kwargs,
}
t = mtext.Text(x, y, text=s, **effective_kwargs)
t.set_clip_path(self.patch)
if t.get_clip_path() is None:
t.set_clip_path(self.patch)
self._add_text(t)
return t

Expand All @@ -700,7 +701,7 @@ def annotate(self, text, xy, xytext=None, xycoords='data', textcoords=None,
textcoords=textcoords, arrowprops=arrowprops,
annotation_clip=annotation_clip, **kwargs)
a.set_transform(mtransforms.IdentityTransform())
if 'clip_on' in kwargs:
if kwargs.get('clip_on', False) and a.get_clip_path() is None:
Copy link
Member Author

Choose a reason for hiding this comment

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

I guess that clip_on not only should be in kwargs, but also be set to True?

a.set_clip_path(self.patch)
self._add_text(a)
return a
Expand Down
6 changes: 4 additions & 2 deletions lib/matplotlib/axes/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -2218,7 +2218,8 @@ def add_artist(self, a):
self._children.append(a)
a._remove_method = self._children.remove
self._set_artist_props(a)
a.set_clip_path(self.patch)
if a.get_clip_path() is None:
a.set_clip_path(self.patch)
self.stale = True
return a

Expand Down Expand Up @@ -2426,7 +2427,8 @@ def add_table(self, tab):
_api.check_isinstance(mtable.Table, tab=tab)
self._set_artist_props(tab)
self._children.append(tab)
tab.set_clip_path(self.patch)
if tab.get_clip_path() is None:
tab.set_clip_path(self.patch)
tab._remove_method = self._children.remove
return tab

Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,7 @@ def add_artist(self, artist, clip=False):
if not artist.is_transform_set():
artist.set_transform(self.transSubfigure)

if clip:
if clip and artist.get_clip_path() is None:
artist.set_clip_path(self.patch)

self.stale = True
Expand Down
2 changes: 1 addition & 1 deletion lib/matplotlib/patheffects.py
Original file line number Diff line number Diff line change
Expand Up @@ -368,7 +368,7 @@ def draw_path(self, renderer, gc, tpath, affine, rgbFace):
self.patch.set_transform(affine + self._offset_transform(renderer))
self.patch.set_clip_box(gc.get_clip_rectangle())
clip_path = gc.get_clip_path()
if clip_path:
if clip_path and self.patch.get_clip_path() is None:
Copy link
Member Author

Choose a reason for hiding this comment

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

This one is bit doubtful...

self.patch.set_clip_path(*clip_path)
self.patch.draw(renderer)

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
39 changes: 38 additions & 1 deletion lib/matplotlib/tests/test_axes.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@

import matplotlib
import matplotlib as mpl
from matplotlib import rc_context
from matplotlib import rc_context, patheffects
from matplotlib._api import MatplotlibDeprecationWarning
import matplotlib.colors as mcolors
import matplotlib.dates as mdates
Expand Down Expand Up @@ -8455,6 +8455,43 @@ def test_zorder_and_explicit_rasterization():
fig.savefig(b, format='pdf')


@image_comparison(["preset_clip_paths.png"], remove_text=True, style="mpl20")
def test_preset_clip_paths():
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure we want to add an image here that we know is wrong just to have to change it again later?

Some possible alternatives:

  • Adding all of the lines/patches completely outside of the rectangle so they are completely clipped? This should make it simpler to compare against a plain rectangle in a check_figures_equal comparison.
  • Removing the annotation for now and adding one outside the axes later once that gets fixed.
  • Use a Cairo backend if the text clipping works properly there...
  • Perhaps simplest though, can you check the expected clip_path after you set it with artists.get_clip_path() is axes.patch, artist.get_clip_path() is polygon for the various cases.

Copy link
Member Author

@oscargus oscargus Mar 5, 2023

Choose a reason for hiding this comment

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

Comparing get_clip_path doesn't seem to work:

 assert <matplotlib.transforms.TransformedPatchPath object at 0x000002BE4A717F70> is <matplotlib.patches.Polygon object at 0x000002BE4A5465E0>

Removing annotation and then adding it doesn't really seem better? Now, the test will fail once it is fixed so one will notice this and automatically have to update it (big risk that this test will not be updated.

For patheffects, this happens at draw time, so that will not be tested (not 100% sure that it is now as I do not fully understand that part).

I'd take it that one actually would like to take the intersection of e.g. the axes patch and the provided clip path (although I am not sure if that will ever become a problem). So even if the get_clip_path approach did work, it would break if this was fixed.

I think this is an example that shows that it is better to use the intersection:

import matplotlib as mpl
from matplotlib import pyplot as plt

fig, ax = plt.subplots()
poly3 = mpl.patches.Polygon([[-0.5, 0], [-0.5, 0.5], [0.5, 0.5], [0.5, 0]],
                           facecolor="g", edgecolor="y", linewidth=2, alpha=0.3)

fig.add_artist(poly3, clip=True)

line = mpl.lines.Line2D((-1, 1), (0.25, 0.25), color='r', clip_on=True, clip_path=poly3)
ax.add_artist(line)
# or
# fig.add_artist(line, clip=True)

With this PR one gets
image

while current master gives

image

or

image

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure I totally follow these recent images... The first one looks like the Polygon isn't actually containing the entire line which I would have expected from the clip, so is there still an error present?

The images from main seem to show that the default clip paths are the axes and figure respectively which I expected.

Comparing get_clip_path doesn't seem to work:

Ahh, yes, if you look at the def set_clip_path implementation, I think you can just call TransformedPathPatch(artist)?

Sorry for sending you down this rabbit hole :)

Copy link
Member Author

Choose a reason for hiding this comment

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

The first one looks like the Polygon isn't actually containing the entire line which I would have expected from the clip, so is there still an error present?

This is a new error. Although probably this case is artificial, it is the consequence of not setting the previous default clip. So as long as we cannot do an intersection of the clip paths, we have to select which error we get.

Then the question is if these compare the same? Will have to look, but pretty sure that one will have to overload the __eq__ method here. Or extract the path. (I guess is will not work as they will not be the same object.)

fig, ax = plt.subplots()

poly = mpl.patches.Polygon(
[[1, 0], [0, 1], [-1, 0], [0, -1]], facecolor="#ddffdd",
edgecolor="#00ff00", linewidth=2, alpha=0.5)

ax.add_patch(poly)

line = mpl.lines.Line2D((-1, 1), (0.5, 0.5), clip_on=True, clip_path=poly)
line.set_path_effects([patheffects.withTickedStroke()])
ax.add_artist(line)

line = mpl.lines.Line2D((-1, 1), (-0.5, -0.5), color='r', clip_on=True,
clip_path=poly)
ax.add_artist(line)

poly2 = mpl.patches.Polygon(
[[-1, 1], [0, 1], [0, -0.25]], facecolor="#beefc0", alpha=0.3,
edgecolor="#faded0", linewidth=2, clip_on=True, clip_path=poly)
ax.add_artist(poly2)

# When text clipping works, the "Annotation" text should be clipped
ax.annotate('Annotation', (-0.75, -0.75), xytext=(0.1, 0.75),
arrowprops={'color': 'k'}, clip_on=True, clip_path=poly)

poly3 = mpl.patches.Polygon(
[[0, 0], [0, 0.5], [0.5, 0.5], [0.5, 0]], facecolor="g", edgecolor="y",
linewidth=2, alpha=0.3, clip_on=True, clip_path=poly)

fig.add_artist(poly3, clip=True)

ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)


@mpl.style.context('default')
def test_rc_axes_label_formatting():
mpl.rcParams['axes.labelcolor'] = 'red'
Expand Down