Repository: holoviz/holoviews

Issue:
Matplotlib error on colorbar
Issue first observed when making a doc build of hvPlot (https://pyviz-dev.github.io/hvplot/user_guide/Plotting_with_Matplotlib.html#scatter). Making a Matplotlib plot with a colorbar raises an error on display. This is due to the latest version of Matplotlib, i.e. 3.5.3. There's no error with Matplotlib 3.5.2.


```python
import holoviews as hv
import numpy as np

hv.extension('matplotlib')

data = np.random.rand(100,4)

scatter = hv.Scatter(data, vdims=['y', 'z'])
scatter = scatter.opts(color='z', colorbar=True)

scatter
```

Traceback:
```
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/IPython/core/formatters.py:973, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    970     method = get_real_method(obj, self.print_method)
    972     if method is not None:
--> 973         return method(include=include, exclude=exclude)
    974     return None
    975 else:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/core/dimension.py:1317, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1310 def _repr_mimebundle_(self, include=None, exclude=None):
   1311     """
   1312     Resolves the class hierarchy for the class rendering the
   1313     object using any display hooks registered on Store.display
   1314     hooks.  The output of all registered display_hooks is then
   1315     combined and returned.
   1316     """
-> 1317     return Store.render(self)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/core/options.py:1405, in Store.render(cls, obj)
   1403 data, metadata = {}, {}
   1404 for hook in hooks:
-> 1405     ret = hook(obj)
   1406     if ret is None:
   1407         continue

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py:282, in pprint_display(obj)
    280 if not ip.display_formatter.formatters['text/plain'].pprint:
    281     return None
--> 282 return display(obj, raw_output=True)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py:252, in display(obj, raw_output, **kwargs)
    250 elif isinstance(obj, (CompositeOverlay, ViewableElement)):
    251     with option_state(obj):
--> 252         output = element_display(obj)
    253 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    254     with option_state(obj):

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py:146, in display_hook.<locals>.wrapped(element)
    144 try:
    145     max_frames = OutputSettings.options['max_frames']
--> 146     mimebundle = fn(element, max_frames=max_frames)
    147     if mimebundle is None:
    148         return {}, {}

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py:192, in element_display(element, max_frames)
    189 if type(element) not in Store.registry[backend]:
    190     return None
--> 192 return render(element)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/ipython/display_hooks.py:68, in render(obj, **kwargs)
     65 if renderer.fig == 'pdf':
     66     renderer = renderer.instance(fig='png')
---> 68 return renderer.components(obj, **kwargs)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/plotting/renderer.py:450, in Renderer.components(self, obj, fmt, comm, **kwargs)
    448     return data, {}
    449 else:
--> 450     html = self._figure_data(plot, fmt, as_script=True, **kwargs)
    451 data['text/html'] = html
    453 return (data, {MIME_TYPES['jlab-hv-exec']: metadata})

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/holoviews/plotting/mpl/renderer.py:175, in MPLRenderer._figure_data(self, plot, fmt, bbox_inches, as_script, **kwargs)
    173         pass
    174     bytes_io = BytesIO()
--> 175     fig.canvas.print_figure(bytes_io, **kw)
    176     data = bytes_io.getvalue()
    178 if as_script:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/backend_bases.py:2319, in FigureCanvasBase.print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, bbox_inches, pad_inches, bbox_extra_artists, backend, **kwargs)
   2315 try:
   2316     # _get_renderer may change the figure dpi (as vector formats
   2317     # force the figure dpi to 72), so we need to set it again here.
   2318     with cbook._setattr_cm(self.figure, dpi=dpi):
-> 2319         result = print_method(
   2320             filename,
   2321             facecolor=facecolor,
   2322             edgecolor=edgecolor,
   2323             orientation=orientation,
   2324             bbox_inches_restore=_bbox_inches_restore,
   2325             **kwargs)
   2326 finally:
   2327     if bbox_inches and restore_bbox:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/backend_bases.py:1648, in _check_savefig_extra_args.<locals>.wrapper(*args, **kwargs)
   1640     _api.warn_deprecated(
   1641         '3.3', name=name, removal='3.6',
   1642         message='%(name)s() got unexpected keyword argument "'
   1643                 + arg + '" which is no longer supported as of '
   1644                 '%(since)s and will become an error '
   1645                 '%(removal)s')
   1646     kwargs.pop(arg)
-> 1648 return func(*args, **kwargs)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/_api/deprecation.py:415, in delete_parameter.<locals>.wrapper(*inner_args, **inner_kwargs)
    405     deprecation_addendum = (
    406         f"If any parameter follows {name!r}, they should be passed as "
    407         f"keyword, not positionally.")
    408     warn_deprecated(
    409         since,
    410         name=repr(name),
   (...)
    413                  else deprecation_addendum,
    414         **kwargs)
--> 415 return func(*inner_args, **inner_kwargs)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:540, in FigureCanvasAgg.print_png(self, filename_or_obj, metadata, pil_kwargs, *args)
    490 @_check_savefig_extra_args
    491 @_api.delete_parameter("3.5", "args")
    492 def print_png(self, filename_or_obj, *args,
    493               metadata=None, pil_kwargs=None):
    494     """
    495     Write the figure to a PNG file.
    496 
   (...)
    538         *metadata*, including the default 'Software' key.
    539     """
--> 540     FigureCanvasAgg.draw(self)
    541     mpl.image.imsave(
    542         filename_or_obj, self.buffer_rgba(), format="png", origin="upper",
    543         dpi=self.figure.dpi, metadata=metadata, pil_kwargs=pil_kwargs)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/backends/backend_agg.py:436, in FigureCanvasAgg.draw(self)
    432 # Acquire a lock on the shared font cache.
    433 with RendererAgg.lock, \
    434      (self.toolbar._wait_cursor_for_draw_cm() if self.toolbar
    435       else nullcontext()):
--> 436     self.figure.draw(self.renderer)
    437     # A GUI class may be need to update a window using this draw, so
    438     # don't forget to call the superclass.
    439     super().draw()

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/artist.py:74, in _finalize_rasterization.<locals>.draw_wrapper(artist, renderer, *args, **kwargs)
     72 @wraps(draw)
     73 def draw_wrapper(artist, renderer, *args, **kwargs):
---> 74     result = draw(artist, renderer, *args, **kwargs)
     75     if renderer._rasterizing:
     76         renderer.stop_rasterizing()

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/figure.py:2845, in Figure.draw(self, renderer)
   2842         # ValueError can occur when resizing a window.
   2844 self.patch.draw(renderer)
-> 2845 mimage._draw_list_compositing_images(
   2846     renderer, self, artists, self.suppressComposite)
   2848 for sfig in self.subfigs:
   2849     sfig.draw(renderer)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/image.py:132, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    130 if not_composite or not has_images:
    131     for a in artists:
--> 132         a.draw(renderer)
    133 else:
    134     # Composite any adjacent images together
    135     image_group = []

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/axes/_base.py:3091, in _AxesBase.draw(self, renderer)
   3088         a.draw(renderer)
   3089     renderer.stop_rasterizing()
-> 3091 mimage._draw_list_compositing_images(
   3092     renderer, self, artists, self.figure.suppressComposite)
   3094 renderer.close_group('axes')
   3095 self.stale = False

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/image.py:132, in _draw_list_compositing_images(renderer, parent, artists, suppress_composite)
    130 if not_composite or not has_images:
    131     for a in artists:
--> 132         a.draw(renderer)
    133 else:
    134     # Composite any adjacent images together
    135     image_group = []

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/artist.py:51, in allow_rasterization.<locals>.draw_wrapper(artist, renderer)
     48     if artist.get_agg_filter() is not None:
     49         renderer.start_filter()
---> 51     return draw(artist, renderer)
     52 finally:
     53     if artist.get_agg_filter() is not None:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/axis.py:1158, in Axis.draw(self, renderer, *args, **kwargs)
   1155     return
   1156 renderer.open_group(__name__, gid=self.get_gid())
-> 1158 ticks_to_draw = self._update_ticks()
   1159 ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
   1160                                                         renderer)
   1162 for tick in ticks_to_draw:

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/axis.py:1045, in Axis._update_ticks(self)
   1040 def _update_ticks(self):
   1041     """
   1042     Update ticks (position and labels) using the current data interval of
   1043     the axes.  Return the list of ticks that will be drawn.
   1044     """
-> 1045     major_locs = self.get_majorticklocs()
   1046     major_labels = self.major.formatter.format_ticks(major_locs)
   1047     major_ticks = self.get_major_ticks(len(major_locs))

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/axis.py:1277, in Axis.get_majorticklocs(self)
   1275 def get_majorticklocs(self):
   1276     """Return this Axis' major tick locations in data coordinates."""
-> 1277     return self.major.locator()

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/ticker.py:2115, in MaxNLocator.__call__(self)
   2113 def __call__(self):
   2114     vmin, vmax = self.axis.get_view_interval()
-> 2115     return self.tick_values(vmin, vmax)

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/ticker.py:2123, in MaxNLocator.tick_values(self, vmin, vmax)
   2120     vmin = -vmax
   2121 vmin, vmax = mtransforms.nonsingular(
   2122     vmin, vmax, expander=1e-13, tiny=1e-14)
-> 2123 locs = self._raw_ticks(vmin, vmax)
   2125 prune = self._prune
   2126 if prune == 'lower':

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/ticker.py:2062, in MaxNLocator._raw_ticks(self, vmin, vmax)
   2060 if self._nbins == 'auto':
   2061     if self.axis is not None:
-> 2062         nbins = np.clip(self.axis.get_tick_space(),
   2063                         max(1, self._min_n_ticks - 1), 9)
   2064     else:
   2065         nbins = 9

File ~/miniconda3/envs/hvplot-docs38/lib/python3.8/site-packages/matplotlib/axis.py:2267, in XAxis.get_tick_space(self)
   2265 size = self._get_tick_label_size('x') * 3
   2266 if size > 0:
-> 2267     return int(np.floor(length / size))
   2268 else:
   2269     return 2**31 - 1

ValueError: cannot convert float NaN to integer
```


Discussion:
Thanks for catching this.

And here I was thinking matplotlib's API was stable ;-p

Resolving patch:
diff --git a/holoviews/plotting/mpl/util.py b/holoviews/plotting/mpl/util.py
--- a/holoviews/plotting/mpl/util.py
+++ b/holoviews/plotting/mpl/util.py
@@ -308,8 +308,11 @@ def get_tight_bbox(fig, bbox_extra_artists=[], pad=None):
                 clip_path = clip_path.get_fully_transformed_path()
                 bbox = Bbox.intersection(bbox,
                                          clip_path.get_extents())
-        if bbox is not None and (bbox.width != 0 or
-                                 bbox.height != 0):
+        if (
+            bbox is not None and
+            (bbox.width != 0 or bbox.height != 0) and
+            np.isfinite(bbox).all()
+        ):
             bbox_filtered.append(bbox)
     if bbox_filtered:
         _bbox = Bbox.union(bbox_filtered)


Test patch: