diff --git a/doc/api/next_api_changes/deprecations/25138-AL.rst b/doc/api/next_api_changes/deprecations/25138-AL.rst new file mode 100644 index 000000000000..8fa510fc4c90 --- /dev/null +++ b/doc/api/next_api_changes/deprecations/25138-AL.rst @@ -0,0 +1,5 @@ +``allsegs``, ``allkinds``, ``tcolors`` and ``tlinewidths`` attributes of `.ContourSet` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +These attributes are deprecated; if required, directly retrieve the vertices +and codes of the Path objects in ``QuadContourSet.collections`` and the colors +and the linewidths of these collections. diff --git a/lib/matplotlib/colorbar.py b/lib/matplotlib/colorbar.py index 14c7c1e58b9a..d3d53e3d98f8 100644 --- a/lib/matplotlib/colorbar.py +++ b/lib/matplotlib/colorbar.py @@ -774,8 +774,8 @@ def add_lines(self, *args, **kwargs): # TODO: Make colorbar lines auto-follow changes in contour lines. return self.add_lines( CS.levels, - [c[0] for c in CS.tcolors], - [t[0] for t in CS.tlinewidths], + CS.to_rgba(CS.cvalues, CS.alpha), + [coll.get_linewidths()[0] for coll in CS.collections], erase=erase) else: self, levels, colors, linewidths, erase = params.values() diff --git a/lib/matplotlib/contour.py b/lib/matplotlib/contour.py index 86009bee63a7..208c426ba68e 100644 --- a/lib/matplotlib/contour.py +++ b/lib/matplotlib/contour.py @@ -3,7 +3,6 @@ """ import functools -import itertools from numbers import Integral import numpy as np @@ -767,6 +766,10 @@ def __init__(self, ax, *args, self.negative_linestyles = \ mpl.rcParams['contour.negative_linestyle'] + # The base class _process_args will update _allpaths, which gets picked + # up by _get_allpaths below. OTOH the _process_args of subclasses + # leave _allpaths as None and instead set _contour_generator. + self._allpaths = None kwargs = self._process_args(*args, **kwargs) self._process_levels() @@ -820,23 +823,7 @@ def __init__(self, ax, *args, self.norm.vmax = vmax self._process_colors() - if getattr(self, 'allsegs', None) is None: - self.allsegs, self.allkinds = self._get_allsegs_and_allkinds() - elif self.allkinds is None: - # allsegs specified in constructor may or may not have allkinds as - # well. Must ensure allkinds can be zipped below. - self.allkinds = [None] * len(self.allsegs) - - # Each entry in (allsegs, allkinds) is a list of (segs, kinds) which - # specifies a list of Paths: segs is a list of (N, 2) arrays of xy - # coordinates, kinds is a list of arrays of corresponding pathcodes. - # However, kinds can also be None; in which case all paths in that list - # are codeless. - allpaths = [ - [*map(mpath.Path, - segs, - kinds if kinds is not None else itertools.repeat(None))] - for segs, kinds in zip(self.allsegs, self.allkinds)] + allpaths = self._get_allpaths() if self.filled: if self.linewidths is not None: @@ -857,7 +844,7 @@ def __init__(self, ax, *args, for level, level_upper, paths in zip(lowers, uppers, allpaths)] else: - self.tlinewidths = tlinewidths = self._process_linewidths() + tlinewidths = self._process_linewidths() tlinestyles = self._process_linestyles() aa = self.antialiased if aa is not None: @@ -895,6 +882,15 @@ def __init__(self, ax, *args, ", ".join(map(repr, kwargs)) ) + allsegs = _api.deprecated("3.8", pending=True)(property(lambda self: [ + p.vertices for c in self.collections for p in c.get_paths()])) + allkinds = _api.deprecated("3.8", pending=True)(property(lambda self: [ + p.codes for c in self.collections for p in c.get_paths()])) + tcolors = _api.deprecated("3.8")(property(lambda self: [ + (tuple(rgba),) for rgba in self.to_rgba(self.cvalues, self.alpha)])) + tlinewidths = _api.deprecated("3.8")( + property(lambda self: self._process_linewidths())) + def get_transform(self): """Return the `.Transform` instance used by this ContourSet.""" if self._transform is None: @@ -979,51 +975,60 @@ def _process_args(self, *args, **kwargs): Must set self.levels, self.zmin and self.zmax, and update axes limits. """ self.levels = args[0] - self.allsegs = args[1] - self.allkinds = args[2] if len(args) > 2 else None + allsegs = args[1] + allkinds = args[2] if len(args) > 2 else None self.zmax = np.max(self.levels) self.zmin = np.min(self.levels) + if allkinds is None: + allkinds = [[None] * len(segs) for segs in allsegs] + # Check lengths of levels and allsegs. if self.filled: - if len(self.allsegs) != len(self.levels) - 1: + if len(allsegs) != len(self.levels) - 1: raise ValueError('must be one less number of segments as ' 'levels') else: - if len(self.allsegs) != len(self.levels): + if len(allsegs) != len(self.levels): raise ValueError('must be same number of segments as levels') # Check length of allkinds. - if (self.allkinds is not None and - len(self.allkinds) != len(self.allsegs)): + if len(allkinds) != len(allsegs): raise ValueError('allkinds has different length to allsegs') # Determine x, y bounds and update axes data limits. - flatseglist = [s for seg in self.allsegs for s in seg] + flatseglist = [s for seg in allsegs for s in seg] points = np.concatenate(flatseglist, axis=0) self._mins = points.min(axis=0) self._maxs = points.max(axis=0) + # Each entry in (allsegs, allkinds) is a list of (segs, kinds) which + # specifies a list of Paths: segs is a list of (N, 2) arrays of xy + # coordinates, kinds is a list of arrays of corresponding pathcodes. + # However, kinds can also be None; in which case all paths in that list + # are codeless (this case is normalized above). + self._allpaths = [[*map(mpath.Path, segs, kinds)] + for segs, kinds in zip(allsegs, allkinds)] + return kwargs - def _get_allsegs_and_allkinds(self): - """Compute ``allsegs`` and ``allkinds`` using C extension.""" - allsegs = [] - allkinds = [] + def _get_allpaths(self): + """Compute ``allpaths`` using C extension.""" + if self._allpaths is not None: + return self._allpaths + allpaths = [] if self.filled: lowers, uppers = self._get_lowers_and_uppers() for level, level_upper in zip(lowers, uppers): vertices, kinds = \ self._contour_generator.create_filled_contour( level, level_upper) - allsegs.append(vertices) - allkinds.append(kinds) + allpaths.append([*map(mpath.Path, vertices, kinds)]) else: for level in self.levels: vertices, kinds = self._contour_generator.create_contour(level) - allsegs.append(vertices) - allkinds.append(kinds) - return allsegs, allkinds + allpaths.append([*map(mpath.Path, vertices, kinds)]) + return allpaths def _get_lowers_and_uppers(self): """ @@ -1052,7 +1057,6 @@ def changed(self): self.norm.autoscale_None(self.levels) tcolors = [(tuple(rgba),) for rgba in self.to_rgba(self.cvalues, alpha=self.alpha)] - self.tcolors = tcolors hatches = self.hatches * len(tcolors) for color, hatch, collection in zip(tcolors, hatches, self.collections): diff --git a/lib/matplotlib/tests/test_contour.py b/lib/matplotlib/tests/test_contour.py index d56a4c9a972a..8c38554ed45c 100644 --- a/lib/matplotlib/tests/test_contour.py +++ b/lib/matplotlib/tests/test_contour.py @@ -5,11 +5,12 @@ import contourpy import numpy as np from numpy.testing import ( - assert_array_almost_equal, assert_array_almost_equal_nulp) + assert_array_almost_equal, assert_array_almost_equal_nulp, assert_array_equal) import matplotlib as mpl -from matplotlib.testing.decorators import image_comparison from matplotlib import pyplot as plt, rc_context, ticker +from matplotlib._api import MatplotlibDeprecationWarning from matplotlib.colors import LogNorm, same_color +from matplotlib.testing.decorators import image_comparison import pytest @@ -365,7 +366,9 @@ def test_contour_linewidth( fig, ax = plt.subplots() X = np.arange(4*3).reshape(4, 3) cs = ax.contour(X, linewidths=call_linewidths) - assert cs.tlinewidths[0][0] == expected + assert cs.collections[0].get_linewidths()[0] == expected + with pytest.warns(MatplotlibDeprecationWarning, match="tlinewidths"): + assert cs.tlinewidths[0][0] == expected @pytest.mark.backend("pdf") @@ -722,3 +725,16 @@ def test_all_nan(): assert_array_almost_equal(plt.contour(x).levels, [-1e-13, -7.5e-14, -5e-14, -2.4e-14, 0.0, 2.4e-14, 5e-14, 7.5e-14, 1e-13]) + + +def test_deprecated_apis(): + cs = plt.contour(np.arange(16).reshape((4, 4))) + colls = cs.collections + with pytest.warns(PendingDeprecationWarning, match="allsegs"): + assert cs.allsegs == [p.vertices for c in colls for p in c.get_paths()] + with pytest.warns(PendingDeprecationWarning, match="allkinds"): + assert cs.allkinds == [p.codes for c in colls for p in c.get_paths()] + with pytest.warns(MatplotlibDeprecationWarning, match="tcolors"): + assert_array_equal(cs.tcolors, [c.get_edgecolor() for c in colls]) + with pytest.warns(MatplotlibDeprecationWarning, match="tlinewidths"): + assert cs.tlinewidths == [c.get_linewidth() for c in colls]