
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]