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

Skip to content

Commit abbb891

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 abbb891

File tree

4 files changed

+96
-12
lines changed

4 files changed

+96
-12
lines changed

lib/matplotlib/collections.py

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1720,8 +1720,11 @@ 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+
"""
1727+
_factor = 1
17251728

17261729
def __init__(self, widths, heights, angles, *, units='points', **kwargs):
17271730
"""
@@ -1751,7 +1754,7 @@ def __init__(self, widths, heights, angles, *, units='points', **kwargs):
17511754
self._units = units
17521755
self.set_transform(transforms.IdentityTransform())
17531756
self._transforms = np.empty((0, 3, 3))
1754-
self._paths = [mpath.Path.unit_circle()]
1757+
self._paths = [self._path_generator()]
17551758

17561759
def _set_transforms(self):
17571760
"""Calculate transforms immediately before drawing."""
@@ -1797,12 +1800,12 @@ def _set_transforms(self):
17971800

17981801
def set_widths(self, widths):
17991802
"""Set the lengths of the first axes (e.g., major axis)."""
1800-
self._widths = 0.5 * np.asarray(widths).ravel()
1803+
self._widths = self._factor * np.asarray(widths).ravel()
18011804
self.stale = True
18021805

18031806
def set_heights(self, heights):
18041807
"""Set the lengths of second axes (e.g., minor axes)."""
1805-
self._heights = 0.5 * np.asarray(heights).ravel()
1808+
self._heights = self._factor * np.asarray(heights).ravel()
18061809
self.stale = True
18071810

18081811
def set_angles(self, angles):
@@ -1812,11 +1815,11 @@ def set_angles(self, angles):
18121815

18131816
def get_widths(self):
18141817
"""Get the lengths of the first axes (e.g., major axis)."""
1815-
return self._widths * 2
1818+
return self._widths / self._factor
18161819

18171820
def get_heights(self):
18181821
"""Set the lengths of second axes (e.g., minor axes)."""
1819-
return self._heights * 2
1822+
return self._heights / self._factor
18201823

18211824
def get_angles(self):
18221825
"""Get the angles of the first axes, degrees CCW from the x-axis."""
@@ -1828,6 +1831,60 @@ def draw(self, renderer):
18281831
super().draw(renderer)
18291832

18301833

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

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,31 @@ 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,scale',
435+
[(mcollections.EllipseCollection, 0.5), (mcollections.RectangleCollection, 1)]
436+
)
437+
def test_WidthHeightAngleCollection_setter_getter(Class, scale):
414438
# Test widths, heights and angle setter
415439
rng = np.random.default_rng(0)
416440

@@ -421,7 +445,7 @@ def test_EllipseCollection_setter_getter():
421445

422446
fig, ax = plt.subplots()
423447

424-
ec = mcollections.EllipseCollection(
448+
ec = Class(
425449
widths=widths,
426450
heights=heights,
427451
angles=angles,
@@ -430,8 +454,8 @@ def test_EllipseCollection_setter_getter():
430454
offset_transform=ax.transData,
431455
)
432456

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)
457+
assert_array_almost_equal(ec._widths, np.array(widths).ravel() * scale)
458+
assert_array_almost_equal(ec._heights, np.array(heights).ravel() * scale)
435459
assert_array_almost_equal(ec._angles, np.deg2rad(angles).ravel())
436460

437461
assert_array_almost_equal(ec.get_widths(), widths)

0 commit comments

Comments
 (0)