From 51c1bf66537f2389cd7ffc7c215cdeb556cf8446 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 4 Jul 2024 08:33:59 +0200 Subject: [PATCH 1/5] Rename labels to tick_labels --- holoviews/plotting/mpl/stats.py | 18 ++++++++++++++---- holoviews/plotting/mpl/util.py | 1 + .../plotting/matplotlib/test_boxwhisker.py | 6 +++++- .../plotting/matplotlib/test_violinplot.py | 11 +++++++++-- 4 files changed, 29 insertions(+), 7 deletions(-) diff --git a/holoviews/plotting/mpl/stats.py b/holoviews/plotting/mpl/stats.py index f728404c3b..9764de4e11 100644 --- a/holoviews/plotting/mpl/stats.py +++ b/holoviews/plotting/mpl/stats.py @@ -6,6 +6,7 @@ from .chart import AreaPlot, ChartPlot from .path import PolygonPlot from .plot import AdjoinedPlot +from .util import MPL_GE_3_9 class DistributionPlot(AreaPlot): @@ -77,7 +78,10 @@ def get_data(self, element, ranges, style): d = group[group.vdims[0]] data.append(d[np.isfinite(d)]) labels.append(label) - style['labels'] = labels + if MPL_GE_3_9: + style['tick_labels'] = labels + else: + style['labels'] = labels style = {k: v for k, v in style.items() if k not in ['zorder', 'label']} style['vert'] = not self.invert_axes @@ -157,7 +161,10 @@ def init_artists(self, ax, plot_args, plot_kwargs): stats_color = plot_kwargs.pop('stats_color', 'black') facecolors = plot_kwargs.pop('facecolors', []) edgecolors = plot_kwargs.pop('edgecolors', 'black') - labels = plot_kwargs.pop('labels') + if MPL_GE_3_9: + labels = {'tick_labels': plot_kwargs.pop('tick_labels')} + else: + labels = {'labels': plot_kwargs.pop('labels')} alpha = plot_kwargs.pop('alpha', 1.) showmedians = self.inner == 'medians' bw_method = self.bandwidth or 'scott' @@ -168,7 +175,7 @@ def init_artists(self, ax, plot_args, plot_kwargs): showfliers=False, showcaps=False, patch_artist=True, boxprops={'facecolor': box_color}, medianprops={'color': 'white'}, widths=0.1, - labels=labels) + **labels) artists.update(box) for body, color in zip(artists['bodies'], facecolors): body.set_facecolors(color) @@ -199,7 +206,10 @@ def get_data(self, element, ranges, style): labels.append(label) colors.append(elstyle[i].get('facecolors', 'blue')) style['positions'] = list(range(len(data))) - style['labels'] = labels + if MPL_GE_3_9: + style['tick_labels'] = labels + else: + style['labels'] = labels style['facecolors'] = colors if element.ndims > 0: diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py index 4699d85907..c102d3003d 100644 --- a/holoviews/plotting/mpl/util.py +++ b/holoviews/plotting/mpl/util.py @@ -38,6 +38,7 @@ mpl_version = Version(mpl.__version__) +MPL_GE_3_9 = mpl_version >= Version('3.9') def is_color(color): """ diff --git a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py index cf28d04abb..2c2ec926b9 100644 --- a/holoviews/tests/plotting/matplotlib/test_boxwhisker.py +++ b/holoviews/tests/plotting/matplotlib/test_boxwhisker.py @@ -1,6 +1,7 @@ import numpy as np from holoviews.element import BoxWhisker +from holoviews.plotting.mpl.util import MPL_GE_3_9 from .test_plot import TestMPLPlot, mpl_renderer @@ -13,7 +14,10 @@ def test_boxwhisker_simple(self): plot = mpl_renderer.get_plot(boxwhisker) data, style, axis_opts = plot.get_data(boxwhisker, {}, {}) self.assertEqual(data[0][0], values) - self.assertEqual(style['labels'], ['']) + if MPL_GE_3_9: + self.assertEqual(style['tick_labels'], ['']) + else: + self.assertEqual(style['labels'], ['']) def test_boxwhisker_simple_overlay(self): values = np.random.rand(100) diff --git a/holoviews/tests/plotting/matplotlib/test_violinplot.py b/holoviews/tests/plotting/matplotlib/test_violinplot.py index 8a1addf252..e87fdb5395 100644 --- a/holoviews/tests/plotting/matplotlib/test_violinplot.py +++ b/holoviews/tests/plotting/matplotlib/test_violinplot.py @@ -1,6 +1,7 @@ import numpy as np from holoviews.element import Violin +from holoviews.plotting.mpl.util import MPL_GE_3_9 from .test_plot import TestMPLPlot, mpl_renderer @@ -14,7 +15,10 @@ def test_violin_simple(self): data, style, axis_opts = plot.get_data(violin, {}, {}) self.assertEqual(data[0][0], values) self.assertEqual(style['positions'], [0]) - self.assertEqual(style['labels'], ['']) + if MPL_GE_3_9: + self.assertEqual(style['tick_labels'], ['']) + else: + self.assertEqual(style['labels'], ['']) def test_violin_simple_overlay(self): values = np.random.rand(100) @@ -34,4 +38,7 @@ def test_violin_multi(self): self.assertEqual(data[0][0], violin.select(A=0).dimension_values(1)) self.assertEqual(data[0][1], violin.select(A=1).dimension_values(1)) self.assertEqual(style['positions'], [0, 1]) - self.assertEqual(style['labels'], ['0', '1']) + if MPL_GE_3_9: + self.assertEqual(style['tick_labels'], ['0', '1']) + else: + self.assertEqual(style['labels'], ['0', '1']) From 8cf95da965ef7b4c8894eb232a9c16fe4f19a08a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 4 Jul 2024 08:40:57 +0200 Subject: [PATCH 2/5] ax.plot -> ax.plot_date --- holoviews/plotting/mpl/chart.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 879fd260e6..24dd63a556 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -27,7 +27,7 @@ from .element import ColorbarPlot, ElementPlot, LegendPlot from .path import PathPlot from .plot import AdjoinedPlot, mpl_rc_context -from .util import mpl_version +from .util import MPL_GE_3_9, mpl_version class ChartPlot(ElementPlot): @@ -95,7 +95,10 @@ def get_data(self, element, ranges, style): def init_artists(self, ax, plot_args, plot_kwargs): xs, ys = plot_args if isdatetime(xs): - artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0] + if MPL_GE_3_9: + artist = ax.plot(xs, ys, '-', **plot_kwargs)[0] + else: + artist = ax.plot_date(xs, ys, '-', **plot_kwargs)[0] else: artist = ax.plot(xs, ys, **plot_kwargs)[0] return {'artist': artist} From 6ea4ea204d46a2ae25939f0086ebfd85d617b147 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 4 Jul 2024 09:30:12 +0200 Subject: [PATCH 3/5] Update test for rectangles --- .../matplotlib/test_annotationplot.py | 60 +++++++++++++------ 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/holoviews/tests/plotting/matplotlib/test_annotationplot.py b/holoviews/tests/plotting/matplotlib/test_annotationplot.py index d1c1884497..acd7959941 100644 --- a/holoviews/tests/plotting/matplotlib/test_annotationplot.py +++ b/holoviews/tests/plotting/matplotlib/test_annotationplot.py @@ -2,6 +2,7 @@ import holoviews as hv from holoviews.element import HLines, HSpans, VLines, VSpans +from holoviews.plotting.mpl.util import MPL_GE_3_9 from .test_plot import TestMPLPlot, mpl_renderer @@ -153,6 +154,25 @@ def test_vlines_hlines_overlay(self): class TestHVSpansPlot(TestMPLPlot): + + def _hspans_check(self, source, v0, v1): + # Matplotlib 3.9+ uses a rectangle instead of polygon + if MPL_GE_3_9: + rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()] + assert np.allclose(rect, [0, v0, 1, v1 - v0]) + else: + assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0]) + assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0]) + + def _vspans_check(self, source, v0, v1): + # Matplotlib 3.9+ uses a rectangle instead of polygon + if MPL_GE_3_9: + rect = [source.get_x(), source.get_y(), source.get_width(), source.get_height()] + assert np.allclose(rect, [v0, 0, v1 - v0, 1]) + else: + assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0]) + assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0]) + def test_hspans_plot(self): hspans = HSpans( {"y0": [0, 3, 5.5], "y1": [1, 4, 6.5], "extra": [-1, -2, -3]}, @@ -164,14 +184,17 @@ def test_hspans_plot(self): xlim = plot.handles["fig"].axes[0].get_xlim() ylim = plot.handles["fig"].axes[0].get_ylim() - assert np.allclose(xlim, (-0.055, 0.055)) + if MPL_GE_3_9: + assert np.allclose(xlim, (-0.05, 1.05)) + else: + assert np.allclose(xlim, (-0.055, 0.055)) + assert np.allclose(ylim, (0, 6.5)) sources = plot.handles["annotations"] assert len(sources) == 3 for source, v0, v1 in zip(sources, hspans.data["y0"], hspans.data["y1"]): - assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0]) - assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0]) + self._hspans_check(source, v0, v1) def test_hspans_inverse_plot(self): hspans = HSpans( @@ -190,8 +213,7 @@ def test_hspans_inverse_plot(self): sources = plot.handles["annotations"] assert len(sources) == 3 for source, v0, v1 in zip(sources, hspans.data["y0"], hspans.data["y1"]): - assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0]) - assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0]) + self._vspans_check(source, v0, v1) def test_dynamicmap_overlay_hspans(self): el = HSpans(data=[[1, 3], [2, 4]]) @@ -218,7 +240,10 @@ def test_hspans_nondefault_kdim(self): xlim = plot.handles["fig"].axes[0].get_xlim() ylim = plot.handles["fig"].axes[0].get_ylim() - assert np.allclose(xlim, (-0.055, 0.055)) + if MPL_GE_3_9: + assert np.allclose(xlim, (-0.05, 1.05)) + else: + assert np.allclose(xlim, (-0.055, 0.055)) assert np.allclose(ylim, (0, 6.5)) sources = plot.handles["annotations"] @@ -226,8 +251,7 @@ def test_hspans_nondefault_kdim(self): for source, v0, v1 in zip( sources, hspans.data["other0"], hspans.data["other1"] ): - assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0]) - assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0]) + self._hspans_check(source, v0, v1) def test_vspans_plot(self): vspans = VSpans( @@ -246,8 +270,7 @@ def test_vspans_plot(self): sources = plot.handles["annotations"] assert len(sources) == 3 for source, v0, v1 in zip(sources, vspans.data["x0"], vspans.data["x1"]): - assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0]) - assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0]) + self._vspans_check(source, v0, v1) def test_vspans_inverse_plot(self): vspans = VSpans( @@ -260,14 +283,16 @@ def test_vspans_inverse_plot(self): xlim = plot.handles["fig"].axes[0].get_xlim() ylim = plot.handles["fig"].axes[0].get_ylim() - assert np.allclose(xlim, (-0.055, 0.055)) + if MPL_GE_3_9: + assert np.allclose(xlim, (-0.05, 1.05)) + else: + assert np.allclose(xlim, (-0.055, 0.055)) assert np.allclose(ylim, (0, 6.5)) sources = plot.handles["annotations"] assert len(sources) == 3 for source, v0, v1 in zip(sources, vspans.data["x0"], vspans.data["x1"]): - assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0]) - assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0]) + self._hspans_check(source, v0, v1) def test_vspans_nondefault_kdims(self): vspans = VSpans( @@ -287,8 +312,7 @@ def test_vspans_nondefault_kdims(self): for source, v0, v1 in zip( sources, vspans.data["other0"], vspans.data["other1"] ): - assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0]) - assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0]) + self._vspans_check(source, v0, v1) def test_vspans_hspans_overlay(self): hspans = HSpans( @@ -310,12 +334,10 @@ def test_vspans_hspans_overlay(self): sources = plot.handles["fig"].axes[0].get_children() for source, v0, v1 in zip(sources[:3], hspans.data["y0"], hspans.data["y1"]): - assert np.allclose(source.xy[:, 0], [0, 0, 1, 1, 0]) - assert np.allclose(source.xy[:, 1], [v0, v1, v1, v0, v0]) + self._hspans_check(source, v0, v1) for source, v0, v1 in zip(sources[3:6], vspans.data["x0"], vspans.data["x1"]): - assert np.allclose(source.xy[:, 1], [0, 1, 1, 0, 0]) - assert np.allclose(source.xy[:, 0], [v0, v0, v1, v1, v0]) + self._vspans_check(source, v0, v1) def test_dynamicmap_overlay_vspans(self): el = VSpans(data=[[1, 3], [2, 4]]) From 654175ac41c1f2ccb5670fe72d8dec131a9a6d5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 4 Jul 2024 09:30:55 +0200 Subject: [PATCH 4/5] Ignore warning in debug mode --- holoviews/plotting/mpl/util.py | 2 +- pyproject.toml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py index c102d3003d..e6de4865f5 100644 --- a/holoviews/plotting/mpl/util.py +++ b/holoviews/plotting/mpl/util.py @@ -37,9 +37,9 @@ from ..util import COLOR_ALIASES, RGB_HEX_REGEX mpl_version = Version(mpl.__version__) - MPL_GE_3_9 = mpl_version >= Version('3.9') + def is_color(color): """ Checks if supplied object is a valid color spec. diff --git a/pyproject.toml b/pyproject.toml index 4ff028092b..a95d6e4d5a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -119,6 +119,8 @@ filterwarnings = [ # 2024-06 "ignore:\\s*Dask dataframe query planning is disabled because dask-expr is not installed:FutureWarning", # OK "ignore:unclosed file <_io.TextIOWrapper name='/dev/null' mode='w':ResourceWarning", # OK + # 2024-07 + "ignore:The (non_)?interactive_bk attribute was deprecated in Matplotlib 3.9", # OK - Only happening in debug mode ] [tool.coverage] From e3e951d3165e842eb8c5f0ffe2db913a9fa1b0e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20H=C3=B8xbro=20Hansen?= Date: Thu, 4 Jul 2024 09:58:22 +0200 Subject: [PATCH 5/5] Use non-deprecated get_cmap --- holoviews/plotting/mpl/chart.py | 10 +++++++--- holoviews/plotting/mpl/util.py | 1 + 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/holoviews/plotting/mpl/chart.py b/holoviews/plotting/mpl/chart.py index 24dd63a556..5da620b859 100644 --- a/holoviews/plotting/mpl/chart.py +++ b/holoviews/plotting/mpl/chart.py @@ -1,7 +1,6 @@ import matplotlib as mpl import numpy as np import param -from matplotlib import cm from matplotlib.collections import LineCollection from matplotlib.dates import DateFormatter, date2num from packaging.version import Version @@ -27,7 +26,7 @@ from .element import ColorbarPlot, ElementPlot, LegendPlot from .path import PathPlot from .plot import AdjoinedPlot, mpl_rc_context -from .util import MPL_GE_3_9, mpl_version +from .util import MPL_GE_3_7, MPL_GE_3_9, mpl_version class ChartPlot(ElementPlot): @@ -504,7 +503,12 @@ def _update_plot(self, key, element, bars, lims, ranges): # Get colormapping options if isinstance(range_item, (HeatMap, Raster)) or (cdim and cdim in element): style = self.lookup_options(range_item, 'style')[self.cyclic_index] - cmap = cm.get_cmap(style.get('cmap')) + if MPL_GE_3_7: + # https://github.com/matplotlib/matplotlib/pull/28355 + cmap = mpl.colormaps.get_cmap(style.get('cmap')) + else: + from matplotlib import cm + cmap = cm.get_cmap(style.get('cmap')) main_range = style.get('clims', main_range) else: cmap = None diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py index e6de4865f5..99c5a9b37c 100644 --- a/holoviews/plotting/mpl/util.py +++ b/holoviews/plotting/mpl/util.py @@ -37,6 +37,7 @@ from ..util import COLOR_ALIASES, RGB_HEX_REGEX mpl_version = Version(mpl.__version__) +MPL_GE_3_7 = mpl_version >= Version('3.7') MPL_GE_3_9 = mpl_version >= Version('3.9')