diff --git a/ci/mypy-stubtest-allowlist.txt b/ci/mypy-stubtest-allowlist.txt index 9f3efd283906..a7cdd288b1bb 100644 --- a/ci/mypy-stubtest-allowlist.txt +++ b/ci/mypy-stubtest-allowlist.txt @@ -32,22 +32,127 @@ matplotlib.ticker.LogitLocator.nonsingular matplotlib.backend_bases._Mode.__new__ matplotlib.units.Number.__hash__ +# Property read-write vs read-only weirdness, fix if possible +matplotlib.offsetbox.DraggableBase.canvas +matplotlib.offsetbox.DraggableBase.cids +matplotlib.transforms.BboxTransform.is_separable +matplotlib.transforms.BboxTransformFrom.is_separable +matplotlib.transforms.BboxTransformTo.is_separable +matplotlib.transforms.BlendedAffine2D.is_separable +matplotlib.transforms.CompositeGenericTransform.is_separable +matplotlib.transforms.TransformWrapper.input_dims +matplotlib.transforms.TransformWrapper.is_separable +matplotlib.transforms.TransformWrapper.output_dims + # 3.6 Pending deprecations matplotlib.figure.Figure.set_constrained_layout matplotlib.figure.Figure.set_constrained_layout_pads matplotlib.figure.Figure.set_tight_layout -# 3.7 deprecations -matplotlib.cm.register_cmap -matplotlib.cm.unregister_cmap +# 3.8 deprecations +matplotlib.cbook.get_sample_data +matplotlib.contour.ContourSet.allkinds +matplotlib.contour.ContourSet.allsegs +matplotlib.contour.ContourSet.tcolors +matplotlib.contour.ContourSet.tlinewidths +matplotlib.ticker.LogLocator.__init__ +matplotlib.ticker.LogLocator.set_params # positional-only argument name lacking leading underscores matplotlib.axes._base._AxesBase.axis +# Aliases (dynamically generated, not type hinted) +matplotlib.collections.Collection.get_aa +matplotlib.collections.Collection.get_antialiaseds +matplotlib.collections.Collection.get_dashes +matplotlib.collections.Collection.get_ec +matplotlib.collections.Collection.get_edgecolors +matplotlib.collections.Collection.get_facecolors +matplotlib.collections.Collection.get_fc +matplotlib.collections.Collection.get_linestyles +matplotlib.collections.Collection.get_linewidths +matplotlib.collections.Collection.get_ls +matplotlib.collections.Collection.get_lw +matplotlib.collections.Collection.get_transOffset +matplotlib.collections.Collection.set_aa +matplotlib.collections.Collection.set_antialiaseds +matplotlib.collections.Collection.set_dashes +matplotlib.collections.Collection.set_ec +matplotlib.collections.Collection.set_edgecolors +matplotlib.collections.Collection.set_facecolors +matplotlib.collections.Collection.set_fc +matplotlib.collections.Collection.set_linestyles +matplotlib.collections.Collection.set_linewidths +matplotlib.collections.Collection.set_ls +matplotlib.collections.Collection.set_lw +matplotlib.collections.Collection.set_transOffset +matplotlib.lines.Line2D.get_aa +matplotlib.lines.Line2D.get_c +matplotlib.lines.Line2D.get_ds +matplotlib.lines.Line2D.get_ls +matplotlib.lines.Line2D.get_lw +matplotlib.lines.Line2D.get_mec +matplotlib.lines.Line2D.get_mew +matplotlib.lines.Line2D.get_mfc +matplotlib.lines.Line2D.get_mfcalt +matplotlib.lines.Line2D.get_ms +matplotlib.lines.Line2D.set_aa +matplotlib.lines.Line2D.set_c +matplotlib.lines.Line2D.set_ds +matplotlib.lines.Line2D.set_ls +matplotlib.lines.Line2D.set_lw +matplotlib.lines.Line2D.set_mec +matplotlib.lines.Line2D.set_mew +matplotlib.lines.Line2D.set_mfc +matplotlib.lines.Line2D.set_mfcalt +matplotlib.lines.Line2D.set_ms +matplotlib.patches.Patch.get_aa +matplotlib.patches.Patch.get_ec +matplotlib.patches.Patch.get_fc +matplotlib.patches.Patch.get_ls +matplotlib.patches.Patch.get_lw +matplotlib.patches.Patch.set_aa +matplotlib.patches.Patch.set_ec +matplotlib.patches.Patch.set_fc +matplotlib.patches.Patch.set_ls +matplotlib.patches.Patch.set_lw +matplotlib.text.Text.get_c +matplotlib.text.Text.get_family +matplotlib.text.Text.get_font +matplotlib.text.Text.get_font_properties +matplotlib.text.Text.get_ha +matplotlib.text.Text.get_name +matplotlib.text.Text.get_size +matplotlib.text.Text.get_style +matplotlib.text.Text.get_va +matplotlib.text.Text.get_variant +matplotlib.text.Text.get_weight +matplotlib.text.Text.set_c +matplotlib.text.Text.set_family +matplotlib.text.Text.set_font +matplotlib.text.Text.set_font_properties +matplotlib.text.Text.set_ha +matplotlib.text.Text.set_ma +matplotlib.text.Text.set_name +matplotlib.text.Text.set_size +matplotlib.text.Text.set_stretch +matplotlib.text.Text.set_style +matplotlib.text.Text.set_va +matplotlib.text.Text.set_variant +matplotlib.text.Text.set_weight +matplotlib.axes._base._AxesBase.get_fc +matplotlib.axes._base._AxesBase.set_fc + +# Other dynamic python behaviors not type hinted +matplotlib.rcsetup.defaultParams + # Maybe should be abstractmethods, required for subclasses, stubs define once matplotlib.tri.*TriInterpolator.__call__ matplotlib.tri.*TriInterpolator.gradient +# Functionally a method call, but actually a class instance, type hinted as former +matplotlib.rcsetup.validate_fillstyle + # TypeVar used only in type hints matplotlib.backend_bases.FigureCanvasBase._T matplotlib.backend_managers.ToolManager._T diff --git a/doc/api/next_api_changes/removals/26965-ER.rst b/doc/api/next_api_changes/removals/26965-ER.rst new file mode 100644 index 000000000000..f815d0b2f45c --- /dev/null +++ b/doc/api/next_api_changes/removals/26965-ER.rst @@ -0,0 +1,27 @@ +As part of a `multi-step process +`_ we are refactoring +the global state for managing the registered colormaps. + +In Matplotlib 3.5 we added a `.ColormapRegistry` class and exposed an instance +at the top level as ``matplotlib.colormaps``. The existing top level functions +in `matplotlib.cm` (``get_cmap``, ``register_cmap``, ``unregister_cmap``) were +changed to be aliases around the same instance. + +In Matplotlib 3.7 those top level functions were deprecated. The following +functions have been removed: + +- ``matplotlib.cm.get_cmap``; use ``matplotlib.colormaps[name]`` instead if you + have a `str`. + + Use `matplotlib.cm.ColormapRegistry.get_cmap` if you + have a string, `None` or a `matplotlib.colors.Colormap` object that you want + to convert to a `matplotlib.colors.Colormap` instance. +- ``matplotlib.cm.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead +- ``matplotlib.cm.unregister_cmap``; use `matplotlib.colormaps.unregister + <.ColormapRegistry.unregister>` instead +- ``matplotlib.pyplot.register_cmap``; use `matplotlib.colormaps.register + <.ColormapRegistry.register>` instead + +The `matplotlib.pyplot.get_cmap` function will stay available for backward +compatibility. diff --git a/doc/api/prev_api_changes/api_changes_0.99.rst b/doc/api/prev_api_changes/api_changes_0.99.rst index 5d544eaec7f5..e03face0d075 100644 --- a/doc/api/prev_api_changes/api_changes_0.99.rst +++ b/doc/api/prev_api_changes/api_changes_0.99.rst @@ -7,7 +7,7 @@ Changes in 0.99 NumPy arrays. * User-generated colormaps can now be added to the set recognized - by :func:`matplotlib.cm.get_cmap`. Colormaps can be made the + by ``matplotlib.cm.get_cmap``. Colormaps can be made the default and applied to the current image using :func:`matplotlib.pyplot.set_cmap`. diff --git a/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst b/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst index 407494bdb53f..cb711b8373f9 100644 --- a/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst +++ b/doc/api/prev_api_changes/api_changes_3.2.0/behavior.rst @@ -294,7 +294,7 @@ Exception changes ~~~~~~~~~~~~~~~~~ Various APIs that raised a `ValueError` for incorrectly typed inputs now raise `TypeError` instead: `.backend_bases.GraphicsContextBase.set_clip_path`, -``blocking_input.BlockingInput.__call__``, `.cm.register_cmap`, `.dviread.DviFont`, +``blocking_input.BlockingInput.__call__``, ``matplotlib.cm.register_cmap``, `.dviread.DviFont`, `.rcsetup.validate_hatch`, ``.rcsetup.validate_animation_writer_path``, `.spines.Spine`, many classes in the :mod:`matplotlib.transforms` module and :mod:`matplotlib.tri` package, and Axes methods that take a ``norm`` parameter. diff --git a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst index d9d79adcfbd5..256c33ed762f 100644 --- a/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst +++ b/doc/api/prev_api_changes/api_changes_3.3.0/deprecations.rst @@ -55,7 +55,7 @@ Please pass capstyles ("miter", "round", "bevel") and joinstyles ("butt", Passing raw data to ``register_cmap()`` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Passing raw data via parameters *data* and *lut* to `.register_cmap()` is +Passing raw data via parameters *data* and *lut* to ``matplotlib.cm.register_cmap()`` is deprecated. Instead, explicitly create a `.LinearSegmentedColormap` and pass it via the *cmap* parameter: ``register_cmap(cmap=LinearSegmentedColormap(name, data, lut))``. diff --git a/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst index df08097dba03..e35301c11986 100644 --- a/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.4.0/behaviour.rst @@ -203,7 +203,7 @@ time, not at draw time. Raise or warn on registering a colormap twice ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -When using `matplotlib.cm.register_cmap` to register a user provided or +When using ``matplotlib.cm.register_cmap`` to register a user provided or third-party colormap it will now raise a `ValueError` if trying to over-write one of the built in colormaps and warn if trying to over write a user registered colormap. This may raise for user-registered colormaps in the diff --git a/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst index a35584b04961..b54cf315f602 100644 --- a/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.6.0/behaviour.rst @@ -4,7 +4,7 @@ Behaviour changes ``plt.get_cmap`` and ``matplotlib.cm.get_cmap`` return a copy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Formerly, `~.pyplot.get_cmap` and `.cm.get_cmap` returned a global version of a +Formerly, `~.pyplot.get_cmap` and ``matplotlib.cm.get_cmap`` returned a global version of a `.Colormap`. This was prone to errors as modification of the colormap would propagate from one location to another without warning. Now, a new copy of the colormap is returned. diff --git a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst index a4fd8b57e419..e8d7bee3ab41 100644 --- a/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst +++ b/doc/api/prev_api_changes/api_changes_3.7.0/behaviour.rst @@ -20,7 +20,7 @@ also be based on ``mpl_toolkits.axisartist``. This behavior is consistent with ``plt.get_cmap`` and ``matplotlib.cm.get_cmap`` return a copy ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Formerly, `~.pyplot.get_cmap` and `.cm.get_cmap` returned a global version of a +Formerly, `~.pyplot.get_cmap` and ``matplotlib.cm.get_cmap`` returned a global version of a `.Colormap`. This was prone to errors as modification of the colormap would propagate from one location to another without warning. Now, a new copy of the colormap is returned. diff --git a/doc/users/prev_whats_new/whats_new_3.4.0.rst b/doc/users/prev_whats_new/whats_new_3.4.0.rst index e26a7c70d253..003cd85fa49d 100644 --- a/doc/users/prev_whats_new/whats_new_3.4.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.4.0.rst @@ -547,7 +547,7 @@ for out-of-range and masked values. New ``cm.unregister_cmap`` function ----------------------------------- -`.cm.unregister_cmap` allows users to remove a colormap that they have +``matplotlib.cm.unregister_cmap`` allows users to remove a colormap that they have previously registered. New ``CenteredNorm`` for symmetrical data around a center diff --git a/doc/users/prev_whats_new/whats_new_3.5.0.rst b/doc/users/prev_whats_new/whats_new_3.5.0.rst index 5a55cff17d02..e67573702218 100644 --- a/doc/users/prev_whats_new/whats_new_3.5.0.rst +++ b/doc/users/prev_whats_new/whats_new_3.5.0.rst @@ -148,9 +148,9 @@ To register new colormaps use:: plt.colormaps.register(my_colormap) -We recommend to use the new API instead of the `~.cm.get_cmap` and -`~.cm.register_cmap` functions for new code. `matplotlib.cm.get_cmap` and -`matplotlib.cm.register_cmap` will eventually be deprecated and removed. +We recommend to use the new API instead of the ``matplotlib.cm.get_cmap`` and +``matplotlib.cm.register_cmap`` functions for new code. ``matplotlib.cm.get_cmap`` and +``matplotlib.cm.register_cmap`` will eventually be deprecated and removed. Within `.pyplot`, ``plt.get_cmap()`` and ``plt.register_cmap()`` will continue to be supported for backward compatibility. diff --git a/lib/matplotlib/cm.py b/lib/matplotlib/cm.py index 53c60c8a8883..399a61191353 100644 --- a/lib/matplotlib/cm.py +++ b/lib/matplotlib/cm.py @@ -83,8 +83,6 @@ class ColormapRegistry(Mapping): def __init__(self, cmaps): self._cmaps = cmaps self._builtin_cmaps = tuple(cmaps) - # A shim to allow register_cmap() to force an override - self._allow_override_builtin = False def __getitem__(self, item): try: @@ -146,10 +144,8 @@ def register(self, cmap, *, name=None, force=False): # unless explicitly asked to raise ValueError( f'A colormap named "{name}" is already registered.') - elif (name in self._builtin_cmaps - and not self._allow_override_builtin): - # We don't allow overriding a builtin unless privately - # coming from register_cmap() + elif name in self._builtin_cmaps: + # We don't allow overriding a builtin raise ValueError("Re-registering the builtin cmap " f"{name!r} is not allowed.") @@ -229,136 +225,6 @@ def get_cmap(self, cmap): ) -# public access to the colormaps should be via `matplotlib.colormaps`. For now, -# we still create the registry here, but that should stay an implementation -# detail. -_colormaps = ColormapRegistry(_gen_cmap_registry()) -globals().update(_colormaps) - - -@_api.deprecated("3.7", alternative="``matplotlib.colormaps.register(name)``") -def register_cmap(name=None, cmap=None, *, override_builtin=False): - """ - Add a colormap to the set recognized by :func:`get_cmap`. - - Register a new colormap to be accessed by name :: - - LinearSegmentedColormap('swirly', data, lut) - register_cmap(cmap=swirly_cmap) - - Parameters - ---------- - name : str, optional - The name that can be used in :func:`get_cmap` or :rc:`image.cmap` - - If absent, the name will be the :attr:`~matplotlib.colors.Colormap.name` - attribute of the *cmap*. - - cmap : matplotlib.colors.Colormap - Despite being the second argument and having a default value, this - is a required argument. - - override_builtin : bool - - Allow built-in colormaps to be overridden by a user-supplied - colormap. - - Please do not use this unless you are sure you need it. - """ - _api.check_isinstance((str, None), name=name) - if name is None: - try: - name = cmap.name - except AttributeError as err: - raise ValueError("Arguments must include a name or a " - "Colormap") from err - # override_builtin is allowed here for backward compatibility - # this is just a shim to enable that to work privately in - # the global ColormapRegistry - _colormaps._allow_override_builtin = override_builtin - _colormaps.register(cmap, name=name, force=override_builtin) - _colormaps._allow_override_builtin = False - - -def _get_cmap(name=None, lut=None): - """ - Get a colormap instance, defaulting to rc values if *name* is None. - - Parameters - ---------- - name : `~matplotlib.colors.Colormap` or str or None, default: None - If a `.Colormap` instance, it will be returned. Otherwise, the name of - a colormap known to Matplotlib, which will be resampled by *lut*. The - default, None, means :rc:`image.cmap`. - lut : int or None, default: None - If *name* is not already a Colormap instance and *lut* is not None, the - colormap will be resampled to have *lut* entries in the lookup table. - - Returns - ------- - Colormap - """ - if name is None: - name = mpl.rcParams['image.cmap'] - if isinstance(name, colors.Colormap): - return name - _api.check_in_list(sorted(_colormaps), name=name) - if lut is None: - return _colormaps[name] - else: - return _colormaps[name].resampled(lut) - -# do it in two steps like this so we can have an un-deprecated version in -# pyplot. -get_cmap = _api.deprecated( - '3.7', - name='get_cmap', - alternative=( - "``matplotlib.colormaps[name]`` " + - "or ``matplotlib.colormaps.get_cmap(obj)``" - ) -)(_get_cmap) - - -@_api.deprecated("3.7", - alternative="``matplotlib.colormaps.unregister(name)``") -def unregister_cmap(name): - """ - Remove a colormap recognized by :func:`get_cmap`. - - You may not remove built-in colormaps. - - If the named colormap is not registered, returns with no error, raises - if you try to de-register a default colormap. - - .. warning:: - - Colormap names are currently a shared namespace that may be used - by multiple packages. Use `unregister_cmap` only if you know you - have registered that name before. In particular, do not - unregister just in case to clean the name before registering a - new colormap. - - Parameters - ---------- - name : str - The name of the colormap to be un-registered - - Returns - ------- - ColorMap or None - If the colormap was registered, return it if not return `None` - - Raises - ------ - ValueError - If you try to de-register a default built-in colormap. - """ - cmap = _colormaps.get(name, None) - _colormaps.unregister(name) - return cmap - - def _auto_norm_from_scale(scale_cls): """ Automatically generate a norm class from *scale_cls*. diff --git a/lib/matplotlib/cm.pyi b/lib/matplotlib/cm.pyi index be8f10b39cb6..da78d940ba4a 100644 --- a/lib/matplotlib/cm.pyi +++ b/lib/matplotlib/cm.pyi @@ -19,8 +19,6 @@ class ColormapRegistry(Mapping[str, colors.Colormap]): _colormaps: ColormapRegistry = ... -def get_cmap(name: str | colors.Colormap | None = ..., lut: int | None = ...) -> colors.Colormap: ... - class ScalarMappable: cmap: colors.Colormap | None colorbar: Colorbar | None diff --git a/lib/matplotlib/pyplot.py b/lib/matplotlib/pyplot.py index dc59bc809638..53da581fb3bb 100644 --- a/lib/matplotlib/pyplot.py +++ b/lib/matplotlib/pyplot.py @@ -74,7 +74,6 @@ from matplotlib.scale import get_scale_names # noqa: F401 from matplotlib.cm import _colormaps -from matplotlib.cm import register_cmap # type: ignore # noqa: F401 from matplotlib.colors import _color_sequences import numpy as np @@ -143,6 +142,10 @@ _log = logging.getLogger(__name__) +_colormaps = ColormapRegistry(_gen_cmap_registry()) +globals().update(_colormaps) + + # Explicit rename instead of import-as for typing's sake. colormaps = _colormaps color_sequences = _color_sequences @@ -2368,8 +2371,32 @@ def get_cmap( name: Colormap | str | None = None, lut: int | None = None ) -> Colormap: - return cm._get_cmap(name=name, lut=lut) # type: ignore -get_cmap.__doc__ = cm._get_cmap.__doc__ # type: ignore + """ + Get a colormap instance, defaulting to rc values if *name* is None. + + Parameters + ---------- + name : `~matplotlib.colors.Colormap` or str or None, default: None + If a `.Colormap` instance, it will be returned. Otherwise, the name of + a colormap known to Matplotlib, which will be resampled by *lut*. The + default, None, means :rc:`image.cmap`. + lut : int or None, default: None + If *name* is not already a Colormap instance and *lut* is not None, the + colormap will be resampled to have *lut* entries in the lookup table. + + Returns + ------- + Colormap + """ + if name is None: + name = mpl.rcParams['image.cmap'] + if isinstance(name, colors.Colormap): + return name + _api.check_in_list(sorted(_colormaps), name=name) + if lut is None: + return _colormaps[name] + else: + return _colormaps[name].resampled(lut) def set_cmap(cmap: Colormap | str) -> None: diff --git a/lib/matplotlib/tests/test_colors.py b/lib/matplotlib/tests/test_colors.py index 139efbe17407..312a324e400f 100644 --- a/lib/matplotlib/tests/test_colors.py +++ b/lib/matplotlib/tests/test_colors.py @@ -72,51 +72,6 @@ def test_resampled(): assert_array_almost_equal(lc(np.nan), lc3(np.nan)) -def test_register_cmap(): - new_cm = mpl.colormaps["viridis"] - target = "viridis2" - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap(target, new_cm) - assert mpl.colormaps[target] == new_cm - - with pytest.raises(ValueError, - match="Arguments must include a name or a Colormap"): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap() - - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.unregister\(name\)" - ): - cm.unregister_cmap(target) - with pytest.raises(ValueError, - match=f'{target!r} is not a valid value for name;'): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\[name\]" - ): - cm.get_cmap(target) - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.unregister\(name\)" - ): - # test that second time is error free - cm.unregister_cmap(target) - - with pytest.raises(TypeError, match="'cmap' must be"): - with pytest.warns( - mpl.MatplotlibDeprecationWarning, - match=r"matplotlib\.colormaps\.register\(name\)" - ): - cm.register_cmap('nome', cmap='not a cmap') - - def test_colormaps_get_cmap(): cr = mpl.colormaps @@ -144,20 +99,6 @@ def test_double_register_builtin_cmap(): matplotlib.colormaps.register( mpl.colormaps[name], name=name, force=True ) - with pytest.raises(ValueError, match='A colormap named "viridis"'): - with pytest.warns(mpl.MatplotlibDeprecationWarning): - cm.register_cmap(name, mpl.colormaps[name]) - with pytest.warns(UserWarning): - # TODO is warning more than once! - cm.register_cmap(name, mpl.colormaps[name], override_builtin=True) - - -def test_unregister_builtin_cmap(): - name = "viridis" - match = f'cannot unregister {name!r} which is a builtin colormap.' - with pytest.raises(ValueError, match=match): - with pytest.warns(mpl.MatplotlibDeprecationWarning): - cm.unregister_cmap(name) def test_colormap_copy():