Skip to content

Commit 5f78c20

Browse files
committed
introduction of _ColorbarMappable
1 parent ecd8d56 commit 5f78c20

File tree

6 files changed

+95
-118
lines changed

6 files changed

+95
-118
lines changed

lib/matplotlib/colorbar.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1043,6 +1043,8 @@ def remove(self):
10431043

10441044
try:
10451045
ax = self.mappable.axes
1046+
if ax is None:
1047+
return
10461048
except AttributeError:
10471049
return
10481050
try:

lib/matplotlib/colorizer.py

Lines changed: 47 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,51 @@ def _sig_digits_from_norm(norm, data, n):
514514
return g_sig_digits
515515

516516

517-
class _ScalarMappable(_ColorizerInterface):
517+
class _ColorbarMappable(_ColorizerInterface):
518+
519+
def __init__(self, colorizer, **kwargs):
520+
"""
521+
Base class for objects that can connect to a colorbar.
522+
523+
All classes that can act as a mappable for `fig.colorbar(mappable)`
524+
will subclass this class.
525+
"""
526+
super().__init__(**kwargs)
527+
self._colorizer = colorizer
528+
self.colorbar = None
529+
self._id_colorizer = self._colorizer.callbacks.connect('changed', self.changed)
530+
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
531+
self._axes = None
532+
533+
@property
534+
def axes(self):
535+
return self._axes
536+
537+
@axes.setter
538+
def axes(self, axes):
539+
self._axes = axes
540+
541+
@property
542+
def colorizer(self):
543+
return self._colorizer
544+
545+
@colorizer.setter
546+
def colorizer(self, cl):
547+
_api.check_isinstance(Colorizer, colorizer=cl)
548+
self._colorizer.callbacks.disconnect(self._id_colorizer)
549+
self._colorizer = cl
550+
self._id_colorizer = cl.callbacks.connect('changed', self.changed)
551+
552+
def changed(self):
553+
"""
554+
Call this whenever the mappable is changed to notify all the
555+
callbackSM listeners to the 'changed' signal.
556+
"""
557+
self.callbacks.process('changed')
558+
self.stale = True
559+
560+
561+
class _ScalarMappable(_ColorbarMappable):
518562
"""
519563
A mixin class to map one or multiple sets of scalar data to RGBA.
520564
@@ -550,13 +594,9 @@ def __init__(self, norm=None, cmap=None, *, colorizer=None, **kwargs):
550594
cmap : str or `~matplotlib.colors.Colormap`
551595
The colormap used to map normalized data values to RGBA colors.
552596
"""
553-
super().__init__(**kwargs)
554597
self._A = None
555-
self._colorizer = self._get_colorizer(colorizer=colorizer, norm=norm, cmap=cmap)
556-
557-
self.colorbar = None
558-
self._id_colorizer = self._colorizer.callbacks.connect('changed', self.changed)
559-
self.callbacks = cbook.CallbackRegistry(signals=["changed"])
598+
colorizer = self._get_colorizer(colorizer=colorizer, norm=norm, cmap=cmap)
599+
super().__init__(colorizer, **kwargs)
560600

561601
def set_array(self, A):
562602
"""
@@ -600,14 +640,6 @@ def get_array(self):
600640
"""
601641
return self._A
602642

603-
def changed(self):
604-
"""
605-
Call this whenever the mappable is changed to notify all the
606-
callbackSM listeners to the 'changed' signal.
607-
"""
608-
self.callbacks.process('changed', self)
609-
self.stale = True
610-
611643
@staticmethod
612644
def _check_exclusionary_keywords(colorizer, **kwargs):
613645
"""
@@ -710,17 +742,6 @@ def __init__(self, colorizer, **kwargs):
710742
_api.check_isinstance(Colorizer, colorizer=colorizer)
711743
super().__init__(colorizer=colorizer, **kwargs)
712744

713-
@property
714-
def colorizer(self):
715-
return self._colorizer
716-
717-
@colorizer.setter
718-
def colorizer(self, cl):
719-
_api.check_isinstance(Colorizer, colorizer=cl)
720-
self._colorizer.callbacks.disconnect(self._id_colorizer)
721-
self._colorizer = cl
722-
self._id_colorizer = cl.callbacks.connect('changed', self.changed)
723-
724745
def _set_colorizer_check_keywords(self, colorizer, **kwargs):
725746
"""
726747
Raises a ValueError if any kwarg is not None while colorizer is not None.

lib/matplotlib/colorizer.pyi

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from matplotlib import cbook, colorbar, colors, artist
1+
from matplotlib import cbook, colorbar, colors, artist, axes as maxes
22

33
import numpy as np
44
from numpy.typing import ArrayLike
@@ -71,7 +71,24 @@ class _ColorizerInterface:
7171
def autoscale_None(self) -> None: ...
7272

7373

74-
class _ScalarMappable(_ColorizerInterface):
74+
class _ColorbarMappable(_ColorizerInterface):
75+
def __init__(
76+
self,
77+
colorizer: Colorizer | None,
78+
**kwargs
79+
) -> None: ...
80+
@property
81+
def colorizer(self) -> Colorizer: ...
82+
@colorizer.setter
83+
def colorizer(self, cl: Colorizer) -> None: ...
84+
def changed(self) -> None: ...
85+
@property
86+
def axes(self) -> maxes._base._AxesBase | None: ...
87+
@axes.setter
88+
def axes(self, new_axes: maxes._base._AxesBase | None) -> None: ...
89+
90+
91+
class _ScalarMappable(_ColorbarMappable):
7592
def __init__(
7693
self,
7794
norm: colors.Norm | None = ...,
@@ -82,7 +99,6 @@ class _ScalarMappable(_ColorizerInterface):
8299
) -> None: ...
83100
def set_array(self, A: ArrayLike | None) -> None: ...
84101
def get_array(self) -> np.ndarray | None: ...
85-
def changed(self) -> None: ...
86102

87103

88104
class ColorizingArtist(_ScalarMappable, artist.Artist):

lib/mpl_toolkits/mplot3d/art3d.py

Lines changed: 24 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@
1515

1616
from matplotlib import (
1717
_api, artist, cbook, colors as mcolors, lines, text as mtext,
18-
path as mpath, rcParams)
18+
path as mpath, rcParams, colorizer as mcolorizer)
1919
from matplotlib.collections import (
2020
Collection, LineCollection, PolyCollection, PatchCollection, PathCollection)
2121
from matplotlib.patches import Patch
2222
from . import proj3d
23+
import collections
2324

2425

2526
def _norm_angle(a):
@@ -1712,9 +1713,10 @@ def norm(x):
17121713
return colors
17131714

17141715

1715-
class VoxelDict(dict):
1716+
class VoxelDict(mcolorizer._ColorbarMappable,
1717+
collections.abc.MutableMapping):
17161718
"""
1717-
A dictionary subclass indexed by coordinate, where ``faces[i, j, k]``
1719+
A mapping indexed by coordinate, where ``faces[i, j, k]``
17181720
is a `.Poly3DCollection` of the faces drawn for the voxel
17191721
``filled[i, j, k]``. If no faces were drawn for a given voxel,
17201722
either because it was not asked to be drawn, or it is fully
@@ -1724,33 +1726,37 @@ class VoxelDict(dict):
17241726
for a colorbar.
17251727
"""
17261728

1727-
def __init__(self, axes, colorizer):
1729+
def __init__(self, colorizer, axes):
17281730
"""
17291731
Parameters
17301732
----------
1733+
colorizer : `mpl.colorizer.Colorizer`
1734+
The colorizer uset to convert data to color.
1735+
17311736
axes : `mplot3d.axes3d.Axes3D`
17321737
The axes the voxels are contained in.
17331738
1734-
colorizer : `mpl.colorizer.Colorizer`
1735-
The colorizer uset to convert data to color.
17361739
"""
1737-
super().__init__(self)
1740+
1741+
super().__init__(colorizer)
17381742
self.axes = axes
1739-
self.colorizer = colorizer
1740-
self._callbacks = cbook.CallbackRegistry(signals=["changed"])
17411743
self._A = None
1744+
self._mapping = dict()
1745+
1746+
def __getitem__(self, key):
1747+
return self._mapping[key]
17421748

1743-
@property
1744-
def cmap(self):
1745-
return self.colorizer.cmap
1749+
def __setitem__(self, key, val):
1750+
self._mapping[key] = val
17461751

1747-
@property
1748-
def norm(self):
1749-
return self.colorizer.norm
1752+
def __delitem__(self, key):
1753+
del self._mapping[key]
17501754

1751-
@property
1752-
def callbacks(self):
1753-
return self._callbacks
1755+
def __len__(self):
1756+
return len(self._mapping)
1757+
1758+
def __iter__(self):
1759+
return reversed(self._mapping)
17541760

17551761
def get_array(self):
17561762
"""
@@ -1772,72 +1778,3 @@ def set_array(self, A):
17721778
self._A = A
17731779
for a, k in zip(A, self.keys()):
17741780
self[k].set_array(a)
1775-
1776-
def add_callback(self, func):
1777-
"""
1778-
Add a callback function that will be called whenever one of the
1779-
`.Artist`'s properties changes.
1780-
1781-
Parameters
1782-
----------
1783-
func : callable
1784-
The callback function. It must have the signature::
1785-
1786-
def func(artist: Artist) -> Any
1787-
1788-
where *artist* is the calling `.Artist`. Return values may exist
1789-
but are ignored.
1790-
1791-
Returns
1792-
-------
1793-
int
1794-
The observer id associated with the callback. This id can be
1795-
used for removing the callback with `.remove_callback` later.
1796-
1797-
See Also
1798-
--------
1799-
remove_callback
1800-
"""
1801-
# Wrapping func in a lambda ensures it can be connected multiple times
1802-
# and never gets weakref-gc'ed.
1803-
return self._callbacks.connect("changed", lambda: func(self))
1804-
1805-
def remove_callback(self, oid):
1806-
"""
1807-
Remove a callback based on its observer id.
1808-
1809-
See Also
1810-
--------
1811-
add_callback
1812-
"""
1813-
self._callbacks.disconnect(oid)
1814-
1815-
def changed(self, *args):
1816-
"""
1817-
Call all of the registered callbacks.
1818-
1819-
This function is triggered internally when a property is changed.
1820-
1821-
See Also
1822-
--------
1823-
add_callback
1824-
remove_callback
1825-
"""
1826-
self._callbacks.process("changed")
1827-
1828-
def get_alpha(self):
1829-
return 1.0
1830-
1831-
def autoscale(self, A):
1832-
"""
1833-
Autoscale the scalar limits on the norm instance using the
1834-
current array
1835-
"""
1836-
self.colorizer.autoscale(A)
1837-
1838-
def autoscale_None(self):
1839-
"""
1840-
Autoscale the scalar limits on the norm instance using the
1841-
current array, changing only limits that are None
1842-
"""
1843-
self.colorizer.autoscale_None(self._A)

lib/mpl_toolkits/mplot3d/axes3d.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3592,7 +3592,7 @@ def permutation_matrices(n):
35923592

35933593
# iterate over the faces, and generate a Poly3DCollection for each
35943594
# voxel
3595-
polygons = art3d.VoxelDict(self, colorizer)
3595+
polygons = art3d.VoxelDict(colorizer, self)
35963596
for coord, faces_inds in voxel_faces.items():
35973597
# convert indices into 3D positions
35983598
if xyz is None:

lib/mpl_toolkits/mplot3d/tests/test_axes3d.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import itertools
33
import platform
44
import sys
5+
import collections.abc
56

67
from packaging.version import parse as parse_version
78
import pytest
@@ -1496,7 +1497,7 @@ def test_alpha(self):
14961497
colors[v1] = [0, 1, 0, 0.5]
14971498
v = ax.voxels(voxels, facecolors=colors)
14981499

1499-
assert type(v) is dict
1500+
assert issubclass(type(v), collections.abc.MutableMapping)
15001501
for coord, poly in v.items():
15011502
assert voxels[coord], "faces returned for absent voxel"
15021503
assert isinstance(poly, art3d.Poly3DCollection)

0 commit comments

Comments
 (0)