Logo
Section 1 / 3

交互属性:悬停与点击

了解可用于基于用户交互触发 Dash 应用变化的交互属性,包括悬停、点击等。

导入

交互属性:悬停与点击

了解可用于基于用户交互触发 Dash 应用变化的交互属性,包括悬停、点击等。

概述

在 Dash 生态系统中,我们可以将 Plotly 图表与 dcc.Graph 组件结合使用,根据用户的交互操作(如悬停、点击和选择)动态改变图表的呈现方式。特别是,我们可以利用 dcc.Graph 中的 hoverData 属性来捕获用户悬停的具体信息。结合 figure 属性,我们可以让用户精准地探索他们感兴趣的内容。

首先,我们来看一个简单的示例:

...

使用简单示例实现悬停功能

在此示例中,我们将探讨 Plotly 内置的悬停功能。通过 Dash,我们展示图表,而所有悬停功能均由 Plotly 自动处理。这就是 Dash 的魅力所在——它将我们在 Plotly 中所学的一切无缝集成到交互式网页应用中,全部用 Python 实现!

本例中,我们将使用广告数据集,绘制销售额与电视广告支出的关系图。

悬停交互与回调函数

还记得我们在数据点上悬停时有多种选项吗?其中一种是通过 fig.update_layout() 设置 hovermode 属性。在此基础上,我们将扩展已有应用,并利用回调函数的知识,让用户选择特定的悬停模式。

我们仍采用标准的应用布局结构,但现在通过回调函数动态调整 dcc.Graph 组件的行为。这是我们首次通过回调函数修改 Plotly 图表,但过程并不复杂。

让我们拆解 @app.callback() 装饰器及其回调函数。我们以如下方式编写函数装饰器:

1@app.callback( 2 Output(component_id='my_fig', component_property='figure'), 3 Input(component_id='my_radio', component_property='value') 4)

其中,Output() 的 component_id='my_fig' 是因为我们希望在布局中显示的 dcc.Graph 组件的 ID 为 'my_fig'。我们设置 component_property='figure',因为我们要修改的是 dcc.Graph 组件中的 figure 属性。至于 Input(),我们设 component_id='my_radio',因为在应用布局中,我们输入来源的组件是 ID 为 'my_radio' 的 dcc.RadioItems 组件。我们设置 component_property='value',因为该组件的 value 属性用于存储用户选中的单选按钮选项。

现在来看函数本身:

1def modify_hovermode(chosen_hovermode): 2 fig = create_fig() 3 fig.update_layout(hovermode=chosen_hovermode) 4 return fig

由于回调装饰器中只有一个 Input(),因此函数只有一个参数,即 chosen_hovermode。这个值由用户从单选按钮中选择。在函数内部,我们调用之前定义的 create_fig() 函数获取原始图表对象,然后使用 fig.update_layout 方法,将 hovermode 属性更新为用户传入的值。最后返回更新后的图表对象,因为 dcc.Graph 组件需要一个 figure 对象作为其 figure 属性(关键字参数)。

使用更复杂的示例实现悬停功能

现在我们考虑一个更复杂的场景:悬停行为不仅涉及单一图表,而是多个图表之间的联动。在此示例中,我们使用 dcc.Graph 组件中的 hoverData 属性(而非 figure 属性)。该属性可以改变图表的状态,特别适用于当用户在图表 B 上悬停时,动态调整图表 A 的显示内容。

本例中,我们展示两个图表:左侧是一个按收入排名的公司柱状图,右侧是一个散点图,展示员工数量与盈利能力的关系。当用户在右侧散点图上悬停某一点时,左侧图表会自动显示对应公司的收入情况。

我们的回调装饰器定义如下:

1@app.callback( 2 Output(component_id='graph-left', component_property='figure'), 3 Input(component_id='graph-right', component_property='hoverData') 4)

Output() 的 component_id='graph-left' 表示我们希望输出的目标是 ID 为 'graph-left' 的 dcc.Graph 组件。我们设置 component_property='figure',因为我们希望调整 Plotly 图表的显示样式。对于 Input(),我们指定 component_id='graph-right',因为用户对右侧图表(ID 为 "graph-right" 的 dcc.Graph 组件)执行的悬停操作,将成为触发左侧图表变化的输入信号。component_property='hoverData' 表明我们关注的是右侧图表的 hoverData 属性。


接下来我们分析以下回调函数:

1def update_left_plot(hoverData): 2 if hoverData is None: 3 # 如果没有悬停任何数据,返回原始图表 4 return create_left_fig() 5 else: 6 # 根据悬停的点进行数据过滤 7 8 print(hoverData) # 用于查看内部结构 9 10 name = hoverData['points'][0]['customdata'][0] 11 df = get_data() 12 filtered_df = df.query('Name==@name') 13 14 # 创建包含筛选后数据的新图表 15 fig = px.bar(filtered_df, 16 x='Name', y='Revenue', 17 color_discrete_sequence=['black']) 18 return fig

该函数仅接收一个参数,因为回调中只有一个 Input()。当用户未在散点图上悬停任何点时,我们直接返回原始的左侧柱状图。进入 else 分支则说明用户已悬停某个点。

我们在函数中打印 hoverData 参数,是为了帮助理解其内部结构。例如,当您悬停散点图最左侧的点时,终端输出如下字典:

1{'points': 2 [ 3 {'curveNumber': 0, 4 'pointNumber': 5, 5 'pointIndex': 5, 6 'x': 68493, 'y': 105369.1, 7 'bbox': { 8 'x0': 747.15, 9 'x1': 753.15, 10 'y0': 76.25, 11 'y1': 82.25 12 }, 13 'customdata': ['Saudi Aramco'] 14 } 15 ] 16}

我们如何提取出被悬停的公司名称?

通过字典语法可知,points 键对应的值是一个列表。我们通过索引 [0] 获取第一个元素,再访问其内部的 customdata 字段。可以看到,customdata 存储的是一个仅包含公司名称的列表。这是因为我们在 create_right_fig() 函数中使用 px.scatter() 创建右侧图表时,只指定了 name 列作为 customdata。

随后,我们使用 pandas 获取一个仅包含该公司名称的数据子集 filtered_df,并将其传递给 px.bar,生成仅显示该公司的柱状图。

点击交互与回调函数

除了悬停,我们还可以在点击事件发生时触发特定行为。实现非常简单,只需将 hoverData 替换为 clickData 即可。在下面的例子中,只有当用户点击散点图上的某一点时,左侧图表才会发生变化。

Dash 回调

从用户交互更新图表