From cf995d1304bfa7f660e7158b5121a46e54f869f2 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Thu, 23 Jun 2022 21:53:43 +0200 Subject: [PATCH] Remove ineffective exclusion of Arcs without parent Axes. The `if not hasattr(self, 'axes'): raise RuntimeError(...)` check was ineffectual, as artists now always have an Axes attribute, which can just be None for some artists. In fact, small Arcs are drawn just fine without a parent Axes; e.g. ``` from pylab import * from matplotlib.patches import * fig = figure() fig.add_artist(Ellipse((.2, .2), .1, .3, angle=45)) # for comparison fig.add_artist(Arc((.2, .2), .1, .3, angle=45, theta1=0, theta2=45)) ``` works just fine. Remove the check, and adjust the docs accordingly. On the other hand, large arcs *did* previously fail, but that occurred a bit further down, when computing `transforms.BboxTransformTo(self.axes.bbox)` (`self.axes` is None --> AttributeError). Fix that by using the figure bbox in that case (as the point is to limit the drawing to the unclipped area, which is the whole figure for Arcs without a parent Axes). --- lib/matplotlib/patches.py | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/lib/matplotlib/patches.py b/lib/matplotlib/patches.py index b3a742699e3c..46abe0e56f12 100644 --- a/lib/matplotlib/patches.py +++ b/lib/matplotlib/patches.py @@ -1907,14 +1907,9 @@ class Arc(Ellipse): """ An elliptical arc, i.e. a segment of an ellipse. - Due to internal optimizations, there are certain restrictions on using Arc: - - - The arc cannot be filled. - - - The arc must be used in an `~.axes.Axes` instance. It can not be added - directly to a `.Figure` because it is optimized to only render the - segments that are inside the axes bounding box with high resolution. + Due to internal optimizations, the arc cannot be filled. """ + def __str__(self): pars = (self.center[0], self.center[1], self.width, self.height, self.angle, self.theta1, self.theta2) @@ -1997,12 +1992,11 @@ def draw(self, renderer): with each visible arc using a fixed number of spline segments (8). The algorithm proceeds as follows: - 1. The points where the ellipse intersects the axes bounding - box are located. (This is done be performing an inverse - transformation on the axes bbox such that it is relative - to the unit circle -- this makes the intersection - calculation much easier than doing rotated ellipse - intersection directly). + 1. The points where the ellipse intersects the axes (or figure) + bounding box are located. (This is done by performing an inverse + transformation on the bbox such that it is relative to the unit + circle -- this makes the intersection calculation much easier than + doing rotated ellipse intersection directly.) This uses the "line intersecting a circle" algorithm from: @@ -2016,8 +2010,6 @@ def draw(self, renderer): pairs of vertices are drawn using the Bezier arc approximation technique implemented in `.Path.arc`. """ - if not hasattr(self, 'axes'): - raise RuntimeError('Arcs can only be used in Axes instances') if not self.get_visible(): return @@ -2104,10 +2096,12 @@ def segment_circle_intersect(x0, y0, x1, y1): & (y0e - epsilon < ys) & (ys < y1e + epsilon) ] - # Transforms the axes box_path so that it is relative to the unit - # circle in the same way that it is relative to the desired ellipse. - box_path_transform = (transforms.BboxTransformTo(self.axes.bbox) - + self.get_transform().inverted()) + # Transform the axes (or figure) box_path so that it is relative to + # the unit circle in the same way that it is relative to the desired + # ellipse. + box_path_transform = ( + transforms.BboxTransformTo((self.axes or self.figure).bbox) + - self.get_transform()) box_path = Path.unit_rectangle().transformed(box_path_transform) thetas = set()