https://docs.xarray.dev/en/stable/_static/dataset-diagram-logo.png

Interactive plots using hvplot#

Xarray’s builtin plotting functionality wraps matplotlib.

The holoviews ecosystem provides the hvplot package to allow easy visualization of xarray (and other) objects. These plots build on Bokeh.

hvplot makes uses of xarray’s accessor interface. This means that all xarray objects gain a .hvplot attribute that lets you access .hvplot functionality as easily as you would use .plot. All you need to do is import hvplot.xarray

For more, see hvplot’s documentation

import cartopy.crs as ccrs
import hvplot.xarray
import xarray as xr
ds = xr.tutorial.open_dataset("air_temperature.nc").rename({"air": "Tair"})

Basics#

hvplot makes the same default choices as DataArray.plot

ds.Tair.hvplot()
# 2D array yields a quadmesh plot
ds.Tair.isel(time=1).hvplot(cmap="fire")
# 1D array yields a line plot
ds.Tair.isel(time=1, lon=1).hvplot()

Interactivity#

But hvplot shines when interactivity is used. Here we can give it all the data and ask it to create a nice slider to control the time slice using the groupby kwarg.

ds.Tair.hvplot(
    groupby="time",
    clim=(250, 295),  # adds a widget for time  # sets colorbar limits
)

Animations#

are easy.

# set constant colorbar limits
ds.Tair.hvplot(
    groupby="time",  # adds a widget for time
    clim=(250, 295),  # sets colormap limits
    widget_type="scrubber",
    widget_location="bottom",
)

Geography#

ds.Tair.isel(time=1).hvplot(
    projection=ccrs.Orthographic(-90, 30),
    coastline=True,
)
---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/IPython/core/formatters.py:974, in MimeBundleFormatter.__call__(self, obj, include, exclude)
    971     method = get_real_method(obj, self.print_method)
    973     if method is not None:
--> 974         return method(include=include, exclude=exclude)
    975     return None
    976 else:

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/dimension.py:1290, in Dimensioned._repr_mimebundle_(self, include, exclude)
   1283 def _repr_mimebundle_(self, include=None, exclude=None):
   1284     """
   1285     Resolves the class hierarchy for the class rendering the
   1286     object using any display hooks registered on Store.display
   1287     hooks.  The output of all registered display_hooks is then
   1288     combined and returned.
   1289     """
-> 1290     return Store.render(self)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/options.py:1425, in Store.render(cls, obj)
   1423 data, metadata = {}, {}
   1424 for hook in hooks:
-> 1425     ret = hook(obj)
   1426     if ret is None:
   1427         continue

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:280, in pprint_display(obj)
    278 if not ip.display_formatter.formatters['text/plain'].pprint:
    279     return None
--> 280 return display(obj, raw_output=True)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:248, in display(obj, raw_output, **kwargs)
    246 elif isinstance(obj, (CompositeOverlay, ViewableElement)):
    247     with option_state(obj):
--> 248         output = element_display(obj)
    249 elif isinstance(obj, (Layout, NdLayout, AdjointLayout)):
    250     with option_state(obj):

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:142, in display_hook.<locals>.wrapped(element)
    140 try:
    141     max_frames = OutputSettings.options['max_frames']
--> 142     mimebundle = fn(element, max_frames=max_frames)
    143     if mimebundle is None:
    144         return {}, {}

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:188, in element_display(element, max_frames)
    185 if type(element) not in Store.registry[backend]:
    186     return None
--> 188 return render(element)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/ipython/display_hooks.py:69, in render(obj, **kwargs)
     66 if renderer.fig == 'pdf':
     67     renderer = renderer.instance(fig='png')
---> 69 return renderer.components(obj, **kwargs)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/renderer.py:399, in Renderer.components(self, obj, fmt, comm, **kwargs)
    396 embed = (not (dynamic or streams or self.widget_mode == 'live') or config.embed)
    398 if embed or config.comms == 'default':
--> 399     return self._render_panel(plot, embed, comm)
    400 return self._render_ipywidget(plot)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/renderer.py:406, in Renderer._render_panel(self, plot, embed, comm)
    404 doc = Document()
    405 with config.set(embed=embed):
--> 406     model = plot.layout._render_model(doc, comm)
    407 if embed:
    408     return render_model(model, comm)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/viewable.py:736, in Viewable._render_model(self, doc, comm)
    734 if comm is None:
    735     comm = state._comm_manager.get_server_comm()
--> 736 model = self.get_root(doc, comm)
    738 if self._design and self._design.theme.bokeh_theme:
    739     doc.theme = self._design.theme.bokeh_theme

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/layout/base.py:305, in Panel.get_root(self, doc, comm, preprocess)
    301 def get_root(
    302     self, doc: Optional[Document] = None, comm: Optional[Comm] = None,
    303     preprocess: bool = True
    304 ) -> Model:
--> 305     root = super().get_root(doc, comm, preprocess)
    306     # ALERT: Find a better way to handle this
    307     if hasattr(root, 'styles') and 'overflow-x' in root.styles:

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/viewable.py:658, in Renderable.get_root(self, doc, comm, preprocess)
    656 wrapper = self._design._wrapper(self)
    657 if wrapper is self:
--> 658     root = self._get_model(doc, comm=comm)
    659     if preprocess:
    660         self._preprocess(root)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/layout/base.py:173, in Panel._get_model(self, doc, root, parent, comm)
    171 root = root or model
    172 self._models[root.ref['id']] = (model, parent)
--> 173 objects, _ = self._get_objects(model, [], doc, root, comm)
    174 props = self._get_properties(doc)
    175 props[self._property_mapping['objects']] = objects

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/layout/base.py:155, in Panel._get_objects(self, model, old_objects, doc, root, comm)
    153 else:
    154     try:
--> 155         child = pane._get_model(doc, root, model, comm)
    156     except RerenderError as e:
    157         if e.layout is not None and e.layout is not self:

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/pane/holoviews.py:411, in HoloViews._get_model(self, doc, root, parent, comm)
    409     plot = self.object
    410 else:
--> 411     plot = self._render(doc, comm, root)
    413 plot.pane = self
    414 backend = plot.renderer.backend

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/panel/pane/holoviews.py:506, in HoloViews._render(self, doc, comm, root)
    503     if comm:
    504         kwargs['comm'] = comm
--> 506 return renderer.get_plot(self.object, **kwargs)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/bokeh/renderer.py:70, in BokehRenderer.get_plot(self_or_cls, obj, doc, renderer, **kwargs)
     63 @bothmethod
     64 def get_plot(self_or_cls, obj, doc=None, renderer=None, **kwargs):
     65     """
     66     Given a HoloViews Viewable return a corresponding plot instance.
     67     Allows supplying a document attach the plot to, useful when
     68     combining the bokeh model with another plot.
     69     """
---> 70     plot = super().get_plot(obj, doc, renderer, **kwargs)
     71     if plot.document is None:
     72         plot.document = Document() if self_or_cls.notebook_context else curdoc()

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/renderer.py:241, in Renderer.get_plot(self_or_cls, obj, doc, renderer, comm, **kwargs)
    238     defaults = [kd.default for kd in plot.dimensions]
    239     init_key = tuple(v if d is None else d for v, d in
    240                      zip(plot.keys[0], defaults))
--> 241     plot.update(init_key)
    242 else:
    243     plot = obj

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/plot.py:936, in DimensionedPlot.update(self, key)
    934 def update(self, key):
    935     if len(self) == 1 and ((key == 0) or (key == self.keys[0])) and not self.drawn:
--> 936         return self.initialize_plot()
    937     item = self.__getitem__(key)
    938     self.traverse(lambda x: setattr(x, '_updated', True))

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/geoviews/plotting/bokeh/plot.py:106, in GeoPlot.initialize_plot(self, ranges, plot, plots, source)
    104 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
    105     opts = {} if isinstance(self, HvOverlayPlot) else {'source': source}
--> 106     fig = super().initialize_plot(ranges, plot, plots, **opts)
    107     if self.geographic and self.show_bounds and not self.overlaid:
    108         from . import GeoShapePlot

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py:2506, in OverlayPlot.initialize_plot(self, ranges, plot, plots)
   2504 if self.tabs:
   2505     subplot.overlaid = False
-> 2506 child = subplot.initialize_plot(ranges, plot, plots)
   2507 if isinstance(element, CompositeOverlay):
   2508     # Ensure that all subplots are in the same state
   2509     frame = element.get(key, None)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/geoviews/plotting/bokeh/plot.py:106, in GeoPlot.initialize_plot(self, ranges, plot, plots, source)
    104 def initialize_plot(self, ranges=None, plot=None, plots=None, source=None):
    105     opts = {} if isinstance(self, HvOverlayPlot) else {'source': source}
--> 106     fig = super().initialize_plot(ranges, plot, plots, **opts)
    107     if self.geographic and self.show_bounds and not self.overlaid:
    108         from . import GeoShapePlot

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py:1527, in ElementPlot.initialize_plot(self, ranges, plot, plots, source)
   1525 if self.autorange:
   1526     self._setup_autorange()
-> 1527 self._init_glyphs(plot, element, ranges, source)
   1528 if not self.overlaid:
   1529     self._update_plot(key, plot, style_element)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/plotting/bokeh/element.py:1469, in ElementPlot._init_glyphs(self, plot, element, ranges, source)
   1467 else:
   1468     style = self.style[self.cyclic_index]
-> 1469     data, mapping, style = self.get_data(element, ranges, style)
   1470     current_id = element._plot_id
   1472 with abbreviated_exception():

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/geoviews/plotting/bokeh/plot.py:162, in GeoPlot.get_data(self, element, ranges, style)
    160 def get_data(self, element, ranges, style):
    161     if self._project_operation and self.geographic:
--> 162         element = self._project_operation(element, projection=self.projection)
    163     return super().get_data(element, ranges, style)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/operation.py:220, in Operation.__call__(self, element, **kwargs)
    218 kwargs['link_dataset'] = self._propagate_dataset
    219 kwargs['link_inputs'] = self.p.link_inputs
--> 220 return element.apply(self, **kwargs)

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/accessors.py:43, in AccessorPipelineMeta.pipelined.<locals>.pipelined_call(*args, **kwargs)
     40     inst._obj._in_method = True
     42 try:
---> 43     result = __call__(*args, **kwargs)
     45     if not in_method:
     46         init_op = factory.instance(
     47             output_type=type(inst),
     48             kwargs={'mode': getattr(inst, 'mode', None)},
     49         )

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/accessors.py:199, in Apply.__call__(self, apply_function, streams, link_inputs, link_dataset, dynamic, per_element, **kwargs)
    197 if hasattr(apply_function, 'dynamic'):
    198     inner_kwargs['dynamic'] = False
--> 199 new_obj = apply_function(self._obj, **inner_kwargs)
    200 if (link_dataset and isinstance(self._obj, Dataset) and
    201     isinstance(new_obj, Dataset) and new_obj._dataset is None):
    202     new_obj._dataset = self._obj.dataset

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/operation.py:214, in Operation.__call__(self, element, **kwargs)
    210         return element.clone([(k, self._apply(el, key=k))
    211                               for k, el in element.items()])
    212     elif ((self._per_element and isinstance(element, Element)) or
    213           (not self._per_element and isinstance(element, ViewableElement))):
--> 214         return self._apply(element)
    215 elif 'streams' not in kwargs:
    216     kwargs['streams'] = self.p.streams

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/holoviews/core/operation.py:141, in Operation._apply(self, element, key)
    139     if not in_method:
    140         element._in_method = True
--> 141 ret = self._process(element, key)
    142 if hasattr(element, '_in_method') and not in_method:
    143     element._in_method = in_method

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/geoviews/operation/projection.py:354, in project_image._process(self, img, key)
    352 arr = img.dimension_values(vd, flat=False)
    353 if arr.size:
--> 354     projected, _ = warp_array(arr, proj, img.crs, (xn, yn),
    355                               src_extent, tgt_extent)
    356 else:
    357     projected = arr

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/cartopy/img_transform.py:192, in warp_array(array, target_proj, source_proj, target_res, source_extent, target_extent, mask_extrapolated)
    186 # XXX Take into account the extents of the original to determine
    187 # target_extents?
    188 target_native_x, target_native_y, extent = mesh_projection(
    189     target_proj, target_res[0], target_res[1],
    190     x_extents=target_x_extents, y_extents=target_y_extents)
--> 192 array = regrid(array, source_native_xy[0], source_native_xy[1],
    193                source_proj, target_proj,
    194                target_native_x, target_native_y,
    195                mask_extrapolated)
    196 return array, extent

File ~/micromamba/envs/xarray-tutorial/lib/python3.11/site-packages/cartopy/img_transform.py:278, in regrid(array, source_x_coords, source_y_coords, source_proj, target_proj, target_x_points, target_y_points, mask_extrapolated)
    274 else:
    275     # Versions of scipy >= v0.16 added the balanced_tree argument,
    276     # which caused the KDTree to hang with this input.
    277     kdtree = scipy.spatial.cKDTree(xyz, balanced_tree=False)
--> 278     _, indices = kdtree.query(target_xyz, k=1)
    279 mask = indices >= len(xyz)
    280 indices[mask] = 0

File _ckdtree.pyx:795, in scipy.spatial._ckdtree.cKDTree.query()

ValueError: 'x' must be finite, check for nan or inf values
:Overlay
   .Image.I     :Image   [lon,lat]   (Tair)
   .Coastline.I :Feature   [Longitude,Latitude]