-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Add an Annulus patch class #9888
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
Changes from all commits
e4697e7
a05fb1e
ecd6083
bd3f498
e1f863e
ad81c4b
7bc465a
83047be
32a5daf
9f12372
05cd23a
a5cf0c5
d2fc81a
23bd2da
f0392f6
0882ed8
0ba1cba
b21d4ee
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
Add ``Annulus`` patch | ||
--------------------- | ||
|
||
A new class for drawing elliptical annuli. | ||
|
||
.. plot:: | ||
|
||
import matplotlib.pyplot as plt | ||
from matplotlib.patches import Annulus | ||
|
||
fig, ax = plt.subplots() | ||
cir = Annulus((0.5, 0.5), 0.2, 0.05, fc='g') # circular annulus | ||
ell = Annulus((0.5, 0.5), (0.5, 0.3), 0.1, 45, # elliptical | ||
fc='m', ec='b', alpha=0.5, hatch='xxx') | ||
ax.add_patch(cir) | ||
ax.add_patch(ell) | ||
ax.set_aspect('equal') |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1552,9 +1552,191 @@ def get_angle(self): | |
angle = property(get_angle, set_angle) | ||
|
||
|
||
class Circle(Ellipse): | ||
"""A circle patch.""" | ||
class Annulus(Patch): | ||
""" | ||
An elliptical annulus. | ||
""" | ||
|
||
@docstring.dedent_interpd | ||
def __init__(self, xy, r, width, angle=0.0, **kwargs): | ||
""" | ||
xy : (float, float) | ||
xy coordinates of annulus centre. | ||
r : float or (float, float) | ||
The radius, or semi-axes. | ||
- If float: radius of the outer circle. | ||
- If two floats: semi-major and -minor axes of outer ellipse. | ||
width : float | ||
Width (thickness) of the annular ring. The width is measured inward | ||
from the outer ellipse so that for the inner ellipse the semi-axes | ||
are given by `r - width`. `width` must be less than or equal to the | ||
semi-minor axis. | ||
angle : float, default=0 | ||
Rotation angle in degrees (anti-clockwise from the positive | ||
x-axis). Ignored for circular annuli (ie. if *r* is a scalar). | ||
|
||
Valid kwargs are: | ||
|
||
%(Patch_kwdoc)s | ||
""" | ||
super().__init__(**kwargs) | ||
|
||
self.set_radii(r) | ||
self.center = xy | ||
self.width = width | ||
self.angle = angle | ||
self._path = None | ||
|
||
def __str__(self): | ||
astromancer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if self.a == self.b: | ||
r = self.a | ||
else: | ||
r = (self.a, self.b) | ||
|
||
return "Annulus(xy=(%s, %s), r=%s, width=%s, angle=%s)" % \ | ||
astromancer marked this conversation as resolved.
Show resolved
Hide resolved
|
||
(*self.center, r, self.width, self.angle) | ||
|
||
def set_center(self, xy): | ||
""" | ||
Set the center of the annulus. | ||
|
||
Parameters | ||
---------- | ||
xy : (float, float) | ||
""" | ||
self._center = xy | ||
self._path = None | ||
self.stale = True | ||
|
||
def get_center(self): | ||
"""Return the center of the annulus.""" | ||
return self._center | ||
|
||
center = property(get_center, set_center) | ||
|
||
def set_width(self, width): | ||
""" | ||
Set the width (thickness) of the annulus ring. The width is measured | ||
inwards from the outer ellipse. | ||
|
||
Parameters | ||
---------- | ||
width : float | ||
""" | ||
if min(self.a, self.b) <= width: | ||
raise ValueError( | ||
'Width of annulus must be less than or equal semi-minor axis') | ||
|
||
self._width = width | ||
self._path = None | ||
self.stale = True | ||
|
||
def get_width(self): | ||
""" | ||
Return the width (thickness) of the annulus ring. | ||
""" | ||
return self._width | ||
|
||
width = property(get_width, set_width) | ||
|
||
def set_angle(self, angle): | ||
""" | ||
Set the tilt angle of the annulus. | ||
|
||
Parameters | ||
---------- | ||
angle : float | ||
""" | ||
self._angle = angle | ||
self._path = None | ||
self.stale = True | ||
|
||
def get_angle(self): | ||
"""Return the angle of the annulus.""" | ||
return self._angle | ||
|
||
angle = property(get_angle, set_angle) | ||
|
||
def set_semimajor(self, a): | ||
""" | ||
Set the semi-major axis *a* of the annulus. | ||
|
||
Parameters | ||
---------- | ||
a : float | ||
""" | ||
self.a = float(a) | ||
self._path = None | ||
self.stale = True | ||
|
||
def set_semiminor(self, b): | ||
""" | ||
Set the semi-minor axis *b* of the annulus. | ||
|
||
Parameters | ||
---------- | ||
b : float | ||
""" | ||
self.b = float(b) | ||
self._path = None | ||
self.stale = True | ||
|
||
def set_radii(self, r): | ||
""" | ||
Set the both the semi-major (*a*) and -minor radii (*b*) of the | ||
annulus. | ||
|
||
Parameters | ||
---------- | ||
r : (float, float) | ||
""" | ||
if np.shape(r) == (2,): | ||
self.a, self.b = r | ||
elif np.shape(r) == (): | ||
self.a = self.b = float(r) | ||
else: | ||
raise ValueError("Parameter 'r' must be one or two floats.") | ||
|
||
self._path = None | ||
self.stale = True | ||
|
||
def get_radii(self): | ||
return self.a, self.b | ||
|
||
radii = property(get_radii, set_radii) | ||
|
||
def _transform_verts(self, verts, a, b): | ||
return transforms.Affine2D() \ | ||
.scale(*self._convert_xy_units((a, b))) \ | ||
.rotate_deg(self.angle) \ | ||
.translate(*self._convert_xy_units(self.center)) \ | ||
.transform(verts) | ||
|
||
def _recompute_path(self): | ||
# circular arc | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This looks great. However, do we understand why codecov is confused by this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have given up trying to understand codecov. Above it warns on a line that's part of a docstring. |
||
arc = Path.arc(0, 360) | ||
|
||
# annulus needs to draw an outer ring | ||
# followed by a reversed and scaled inner ring | ||
a, b, w = self.a, self.b, self.width | ||
v1 = self._transform_verts(arc.vertices, a, b) | ||
v2 = self._transform_verts(arc.vertices[::-1], a - w, b - w) | ||
v = np.vstack([v1, v2, v1[0, :], (0, 0)]) | ||
c = np.hstack([arc.codes, Path.MOVETO, | ||
arc.codes[1:], Path.MOVETO, | ||
Path.CLOSEPOLY]) | ||
self._path = Path(v, c) | ||
|
||
def get_path(self): | ||
if self._path is None: | ||
self._recompute_path() | ||
return self._path | ||
|
||
|
||
class Circle(Ellipse): | ||
""" | ||
A circle patch. | ||
""" | ||
def __str__(self): | ||
pars = self.center[0], self.center[1], self.radius | ||
fmt = "Circle(xy=(%g, %g), radius=%g)" | ||
|
Uh oh!
There was an error while loading. Please reload this page.