Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

comp(matplotlib): Add compatibility with Matplotlib 3.9 #6307

Merged
merged 5 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions holoviews/plotting/mpl/chart.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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_version
from .util import MPL_GE_3_7, MPL_GE_3_9, mpl_version


class ChartPlot(ElementPlot):
Expand Down Expand Up @@ -95,7 +94,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}
Expand Down Expand Up @@ -501,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
Expand Down
18 changes: 14 additions & 4 deletions holoviews/plotting/mpl/stats.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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'
Expand All @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 2 additions & 0 deletions holoviews/plotting/mpl/util.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@
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')


def is_color(color):
Expand Down
60 changes: 41 additions & 19 deletions holoviews/tests/plotting/matplotlib/test_annotationplot.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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]},
Expand All @@ -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(
Expand All @@ -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]])
Expand All @@ -218,16 +240,18 @@ 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"]
assert len(sources) == 3
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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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(
Expand All @@ -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]])
Expand Down
6 changes: 5 additions & 1 deletion holoviews/tests/plotting/matplotlib/test_boxwhisker.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand Down
11 changes: 9 additions & 2 deletions holoviews/tests/plotting/matplotlib/test_violinplot.py
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -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)
Expand All @@ -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'])
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down