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

Skip to content

Commit 78de816

Browse files
committed
Add base class for collection with width, height and angle to be used as parent class of EllipseCollection and RectangleCollection
1 parent acfef85 commit 78de816

File tree

6 files changed

+110
-12
lines changed

6 files changed

+110
-12
lines changed

lib/matplotlib/collections.py

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,8 +1720,10 @@ def __init__(self, sizes, **kwargs):
17201720
self._paths = [mpath.Path.unit_circle()]
17211721

17221722

1723-
class EllipseCollection(Collection):
1724-
"""A collection of ellipses, drawn using splines."""
1723+
class _CollectionWithWidthHeightAngle(Collection):
1724+
"""
1725+
Base class for collections that have an array of widths, heights and angles
1726+
"""
17251727

17261728
def __init__(self, widths, heights, angles, *, units='points', **kwargs):
17271729
"""
@@ -1751,7 +1753,7 @@ def __init__(self, widths, heights, angles, *, units='points', **kwargs):
17511753
self._units = units
17521754
self.set_transform(transforms.IdentityTransform())
17531755
self._transforms = np.empty((0, 3, 3))
1754-
self._paths = [mpath.Path.unit_circle()]
1756+
self._paths = [self._path_generator()]
17551757

17561758
def _set_transforms(self):
17571759
"""Calculate transforms immediately before drawing."""
@@ -1797,12 +1799,12 @@ def _set_transforms(self):
17971799

17981800
def set_widths(self, widths):
17991801
"""Set the lengths of the first axes (e.g., major axis)."""
1800-
self._widths = 0.5 * np.asarray(widths).ravel()
1802+
self._widths = np.asarray(widths).ravel()
18011803
self.stale = True
18021804

18031805
def set_heights(self, heights):
18041806
"""Set the lengths of second axes (e.g., minor axes)."""
1805-
self._heights = 0.5 * np.asarray(heights).ravel()
1807+
self._heights = np.asarray(heights).ravel()
18061808
self.stale = True
18071809

18081810
def set_angles(self, angles):
@@ -1812,11 +1814,11 @@ def set_angles(self, angles):
18121814

18131815
def get_widths(self):
18141816
"""Get the lengths of the first axes (e.g., major axis)."""
1815-
return self._widths * 2
1817+
return self._widths
18161818

18171819
def get_heights(self):
18181820
"""Set the lengths of second axes (e.g., minor axes)."""
1819-
return self._heights * 2
1821+
return self._heights
18201822

18211823
def get_angles(self):
18221824
"""Get the angles of the first axes, degrees CCW from the x-axis."""
@@ -1828,6 +1830,59 @@ def draw(self, renderer):
18281830
super().draw(renderer)
18291831

18301832

1833+
class EllipseCollection(_CollectionWithWidthHeightAngle):
1834+
"""
1835+
A collection of ellipses, drawn using splines.
1836+
1837+
Parameters
1838+
----------
1839+
widths : array-like
1840+
The lengths of the first axes (e.g., major axis lengths).
1841+
heights : array-like
1842+
The lengths of second axes.
1843+
angles : array-like
1844+
The angles of the first axes, degrees CCW from the x-axis.
1845+
units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'}
1846+
The units in which majors and minors are given; 'width' and
1847+
'height' refer to the dimensions of the axes, while 'x' and 'y'
1848+
refer to the *offsets* data units. 'xy' differs from all others in
1849+
that the angle as plotted varies with the aspect ratio, and equals
1850+
the specified angle only when the aspect ratio is unity. Hence
1851+
it behaves the same as the `~.patches.Ellipse` with
1852+
``axes.transData`` as its transform.
1853+
**kwargs
1854+
Forwarded to `Collection`.
1855+
"""
1856+
_path_generator = mpath.Path.half_unit_circle
1857+
1858+
1859+
class RectangleCollection(_CollectionWithWidthHeightAngle):
1860+
"""
1861+
A collection of rectangles, drawn using splines.
1862+
1863+
Parameters
1864+
----------
1865+
widths : array-like
1866+
The lengths of the first axes (e.g., major axis lengths).
1867+
heights : array-like
1868+
The lengths of second axes.
1869+
angles : array-like
1870+
The angles of the first axes, degrees CCW from the x-axis.
1871+
units : {'points', 'inches', 'dots', 'width', 'height', 'x', 'y', 'xy'}
1872+
The units in which majors and minors are given; 'width' and
1873+
'height' refer to the dimensions of the axes, while 'x' and 'y'
1874+
refer to the *offsets* data units. 'xy' differs from all others in
1875+
that the angle as plotted varies with the aspect ratio, and equals
1876+
the specified angle only when the aspect ratio is unity. Hence
1877+
it behaves the same as the `~.patches.Ellipse` with
1878+
``axes.transData`` as its transform.
1879+
**kwargs
1880+
Forwarded to `Collection`.
1881+
1882+
"""
1883+
_path_generator = mpath.Path.unit_rectangle
1884+
1885+
18311886
class PatchCollection(Collection):
18321887
"""
18331888
A generic collection of patches.

lib/matplotlib/collections.pyi

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ class EventCollection(LineCollection):
168168
class CircleCollection(_CollectionWithSizes):
169169
def __init__(self, sizes: float | ArrayLike, **kwargs) -> None: ...
170170

171-
class EllipseCollection(Collection):
171+
class _CollectionWithWidthHeightAngle(Collection):
172172
def __init__(
173173
self,
174174
widths: ArrayLike,
@@ -187,6 +187,9 @@ class EllipseCollection(Collection):
187187
def get_heights(self) -> ArrayLike: ...
188188
def get_angles(self) -> ArrayLike: ...
189189

190+
class EllipseCollection(_CollectionWithWidthHeightAngle): ...
191+
class RectangleCollection(_CollectionWithWidthHeightAngle): ...
192+
190193
class PatchCollection(Collection):
191194
def __init__(
192195
self, patches: Iterable[Patch], *, match_original: bool = ..., **kwargs

lib/matplotlib/path.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -799,6 +799,21 @@ def unit_regular_asterisk(cls, numVertices):
799799
"""
800800
return cls.unit_regular_star(numVertices, 0.0)
801801

802+
_half_unit_circle = None
803+
804+
@classmethod
805+
def half_unit_circle(cls):
806+
"""
807+
Return the readonly :class:`Path` of the half unit circle.
808+
809+
For most cases, :func:`Path.circle` will be what you want.
810+
"""
811+
if cls._half_unit_circle is None:
812+
cls._half_unit_circle = cls.circle(
813+
center=(0, 0), radius=0.5, readonly=True
814+
)
815+
return cls._half_unit_circle
816+
802817
_unit_circle = None
803818

804819
@classmethod

lib/matplotlib/path.pyi

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,8 @@ class Path:
107107
@classmethod
108108
def unit_regular_asterisk(cls, numVertices: int) -> Path: ...
109109
@classmethod
110+
def half_unit_circle(cls) -> Path: ...
111+
@classmethod
110112
def unit_circle(cls) -> Path: ...
111113
@classmethod
112114
def circle(

lib/matplotlib/tests/test_collections.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,30 @@ def test_EllipseCollection():
410410
ax.autoscale_view()
411411

412412

413-
def test_EllipseCollection_setter_getter():
413+
@image_comparison(['RectangleCollection_test_image.png'], remove_text=True)
414+
def test_RectangleCollection():
415+
# Test basic functionality
416+
fig, ax = plt.subplots()
417+
x = np.arange(4)
418+
y = np.arange(3)
419+
X, Y = np.meshgrid(x, y)
420+
XY = np.vstack((X.ravel(), Y.ravel())).T
421+
422+
ww = X / x[-1]
423+
hh = Y / y[-1]
424+
aa = np.ones_like(ww) * 20 # first axis is 20 degrees CCW from x axis
425+
426+
ec = mcollections.RectangleCollection(
427+
ww, hh, aa, units='x', offsets=XY, offset_transform=ax.transData,
428+
facecolors='none')
429+
ax.add_collection(ec)
430+
ax.autoscale_view()
431+
432+
433+
@pytest.mark.parametrize(
434+
'Class', [mcollections.EllipseCollection, mcollections.RectangleCollection]
435+
)
436+
def test_WidthHeightAngleCollection_setter_getter(Class):
414437
# Test widths, heights and angle setter
415438
rng = np.random.default_rng(0)
416439

@@ -421,7 +444,7 @@ def test_EllipseCollection_setter_getter():
421444

422445
fig, ax = plt.subplots()
423446

424-
ec = mcollections.EllipseCollection(
447+
ec = Class(
425448
widths=widths,
426449
heights=heights,
427450
angles=angles,
@@ -430,8 +453,8 @@ def test_EllipseCollection_setter_getter():
430453
offset_transform=ax.transData,
431454
)
432455

433-
assert_array_almost_equal(ec._widths, np.array(widths).ravel() * 0.5)
434-
assert_array_almost_equal(ec._heights, np.array(heights).ravel() * 0.5)
456+
assert_array_almost_equal(ec._widths, np.array(widths).ravel())
457+
assert_array_almost_equal(ec._heights, np.array(heights).ravel())
435458
assert_array_almost_equal(ec._angles, np.deg2rad(angles).ravel())
436459

437460
assert_array_almost_equal(ec.get_widths(), widths)

0 commit comments

Comments
 (0)