Panel/Hvplot 变量变化时的交互

Panel/Hvplot interaction when variable is changing

我正在尝试创建一个包含两个全息视图对象的仪表板:一个 panel pn.widgets.Select object that contains a list of xarray variables, and a hvplot 对象,它在输入时采用 selected 变量,如下所示:

def hvmesh(var=None):
    mesh = ds[var].hvplot.quadmesh(x='x', y='y', rasterize=True, crs=crs, 
       width=600, height=400, groupby=list(ds[var].dims[:-2]), cmap='jet')
    return mesh

这是特定变量(同时具有时间和高度维度)的示例网格的样子:

我想在 select 来自面板小部件的变量时更新地图: 我试着把它做成动态地图,像这样:

from holoviews.streams import Params
import holoviews as hv

var_stream = Params(var_select, ['value'], rename={'value': 'var'})

mesh = hv.DynamicMap(hvmesh, streams=[var_stream])

但是当我尝试显示地图时,我得到:

Exception: Nesting a DynamicMap inside a DynamicMap is not supported.

select 面板小部件中的 hvplot 变量似乎很常见。使用 pyviz 完成此操作的最佳方法是什么?

如果它有用,这是我的full attempt Jupyter Notebook

所以这里一长一短。让我们从简短的答案开始,即不需要为数据变量创建自定义 select 小部件,因为 hvPlot 允许自动在多个数据变量之间进行 selecting,因此如果将其更改为:

rasterized_mesh = ds[time_vars].hvplot.quadmesh(
    x='x', y='y', z=time_vars[::-1], crs=crs, width=600, height=400, 
    groupby=list(ds[var].dims[:-2]), rasterize=True, cmap='jet')

您将获得一个 DynamicMap,它允许您 select 非空间维度和数据变量,您现在可以将其嵌入到您的面板中,无需额外的工作。如果这就是您关心的全部内容停在这里,因为我们即将进入一些内部结构,希望能提供更好的理解。

让我们暂时假设 hvPlot 不允许在数据变量之间 selecting,那么我们会怎么做?所以你必须知道的主要事情是 HoloViews 允许链接 DynamicMaps 但不允许嵌套它们。这可能有点难以理解,但我们会将问题分解为多个步骤,然后看看我们如何才能实现我们想要的。那么给我们情节的事件链是什么?

  1. Select一个数据变量
  2. 在非空间维度上应用 groupby
  3. 对每个 QuadMesh 应用光栅化

如您所知,hvPlot 为我们处理了第 2 步和第 3 步,那么我们如何在第 2 步和第 3 步之前注入第 1 步。将来我们计划添加对将面板小部件直接传递到 hvPlot 的支持,这意味着您只需一步即可完成所有操作。由于面板仍然是一个非常新的项目,我将一路指出我们的 API 如何最终使这个过程变得微不足道,但现在我们必须坚持相对冗长的解决方法。在这种情况下,我们必须重新排列操作顺序:

  1. 在非空间维度上应用 groupby
  2. Select一个数据变量
  3. 对每个 QuadMesh 应用光栅化

因此我们首先 select 所有 数据变量并跳过光栅化:

meshes = ds[time_vars].hvplot.quadmesh(
    x='x', y='y', z=time_vars, crs=crs, width=600, height=400, 
    groupby=list(ds[var].dims[:-2]))

现在我们有了一个 DynamicMap,其中包含我们可能想要显示的所有数据,我们可以应用下一个操作。在这里,我们将使用 hv.util.Dynamic 实用程序,该实用程序可用于在注入流值时对 DynamicMap 进行链接操作。特别是在这一步中,我们从 var_select 小部件创建一个流,它将用于重新索引我们网格中的 QuadMesh DynamicMap:

def select_var(obj, var):
    return obj.clone(vdims=[var])

var_stream = Params(var_select, ['value'], rename={'value': 'var'})
var_mesh = hv.util.Dynamic(meshes, operation=select_var, streams=[var_select])

# Note starting in hv 1.12 you'll be able to replace this with
# var_mesh = meshes.map(select_var, streams=[var_select])

# And once param 2.0 is out we intend to support
# var_mesh = meshes.map(select_var, var=var_select.param.value)

现在我们有了一个 DynamicMap,它可以响应小部件中的变化,但还没有对其进行栅格化,因此我们可以手动应用 rasterize 操作:

rasterized_mesh = rasterize(var_mesh).opts(cmap='jet', width=600, height=400)

现在我们有一个 DynamicMap,它 linked 到 Selection 小部件,应用 groupby 并栅格化,我们现在可以将其嵌入面板中。上面@jbednar 暗示的另一种方法是通过使 hvPlot 调用不是动态的并手动执行时间和高度级别 selection 来一步完成所有操作。我不会在这里讨论它,但它也是一种有效的(如果效率较低)方法。

正如我在上面暗示的那样,最终我们还打算让所有 hvPlot 参数都变成动态的,这意味着您将能够对 link hvPlot 关键字参数的小部件值:

ds[time_vars].hvplot.quadmesh(
    x='x', y='y', z=var_select.param.value, rasterize=True, crs=crs,
    width=600, height=400, groupby=list(ds[var].dims[:-2]), cmap='jet')

因为 groupby 随着每个变量的选择而变化,变量列表不能传递给 hvplot。因此,一种解决方案是每次选择新变量时重新创建绘图。这有效:

import holoviews as hv
from holoviews.streams import Params

def plot(var=None, tiles=None):
    var = var or var_select.value
    tiles = tiles or map_select.value
    mesh = ds[var].hvplot.quadmesh(x='x', y='y', rasterize=True, crs=crs, title=var,
                                   width=600, height=400, groupby=list(ds[var].dims[:-2]), 
                                   cmap='jet')
    return mesh.opts(alpha=0.7) * tiles

def on_var_select(event):
    var = event.obj.value
    col[-1] = plot(var=var)

def on_map_select(event):
    tiles = event.obj.value
    col[-1] = plot(tiles=tiles)

var_select.param.watch(on_var_select, parameter_names=['value']);
map_select.param.watch(on_map_select, parameter_names=['value']);

col = pn.Column(var_select, map_select, plot(var_select.value) * tiles)

生产:

这里是full notebook