You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python中Holoviews/Bokeh切换Points图时保留轴范围/缩放级别

问题

通过RadioButton切换两个Holoviews Points图时,用户设置的轴缩放/范围会被重置,需要实现切换时保留当前的轴限设置。

解决方案

核心思路是避免每次切换都重新生成整个绘图组件,而是复用已有组件并更新数据源,同时通过RangeXY流捕获和恢复轴范围:

  • 初始化时创建一次Points图和时序DynamicMap,而非每次切换都重建
  • 用hv.streams.RangeXY实时捕获Points图的当前x、y轴范围
  • 切换数据集时,更新Points的数据源,并手动恢复之前保存的轴范围
  • 时序图的数据源通过动态切换对应数据集来更新
修改后完整代码
import pandas as pd 
import holoviews as hv 
import panel as pn 
import numpy as np

hv.extension('bokeh')

#### 创建示例数据 ####
df = pd.DataFrame(data = {'id':['w', 'x', 'y', 'z'], 'value':[1,2,3,4], 'x':range(4), 'y':range(4)})
df_qc = df.copy()
df_qc['value'] = df_qc['value'].to_list()[::-1]

# 创建时序数据
df_w = pd.DataFrame(data = {'id':['w']*5, 'hour':range(5), 'value':np.random.random(5)})
df_x = pd.DataFrame(data = {'id':['x']*5, 'hour':range(5), 'value':np.random.random(5)})
df_y = pd.DataFrame(data = {'id':['y']*5, 'hour':range(5), 'value':np.random.random(5)})
df_z = pd.DataFrame(data = {'id':['z']*5, 'hour':range(5), 'value':np.random.random(5)})
df_ts = pd.concat([df_w, df_x, df_y, df_z]).set_index(['id', 'hour'])

df_ts_qc = df_ts.copy()
for id in df_ts_qc.index.unique('id'):
    df_ts_qc.loc[id, 'value'] = df_ts_qc.loc[id, 'value'].to_list()[::-1]

# 全局变量存储当前轴范围和选中的数据集
current_ranges = {'x_range': None, 'y_range': None}
current_data = {'df': df, 'df_ts': df_ts}

# 初始化Points图和流
points = hv.Points(data=df, kdims=['x', 'y'], vdims=['id', 'value'])
selection_stream = hv.streams.Selection1D(source=points).rename(index="index")
range_stream = hv.streams.RangeXY(source=points)

# 捕获轴范围变化
def update_ranges(x_range, y_range):
    if x_range:
        current_ranges['x_range'] = x_range
    if y_range:
        current_ranges['y_range'] = y_range

range_stream.add_subscriber(update_ranges)

# 时序图生成函数
empty_curve = hv.Curve(df_ts.loc['w']).opts(visible=False)
def tap_station_curve(index):
    if not index:
        return empty_curve
    id = current_data['df'].iloc[index[0]]['id']
    return hv.Curve(current_data['df_ts'].loc[id], label=str(id))

ts_curve = hv.DynamicMap(tap_station_curve, kdims=[], streams=[selection_stream])

# 切换数据集的回调函数
def update_plot(option):
    global current_data
    # 更新当前数据集
    if option == 'df':
        current_data = {'df': df, 'df_ts': df_ts}
    else:
        current_data = {'df': df_qc, 'df_ts': df_ts_qc}
    # 更新Points数据源
    points.data = current_data['df']
    # 恢复轴范围(如果存在)
    if current_ranges['x_range'] and current_ranges['y_range']:
        points.opts(xlim=current_ranges['x_range'], ylim=current_ranges['y_range'])
    return pn.Row(points.opts(size=10, color='value', tools=['tap']), ts_curve)

radio_button = pn.widgets.RadioButtonGroup(options=['df', 'df_qc'])
# 绑定回调
plot_panel = pn.bind(update_plot, radio_button)

pn.Column(radio_button, plot_panel).servable()
关键说明
  1. 轴范围捕获:通过RangeXY流监听Points图的轴范围变化,实时保存到current_ranges字典中
  2. 复用组件:Points图和时序DynamicMap只初始化一次,切换时仅更新数据源,避免重建组件导致轴重置
  3. 轴范围恢复:切换数据集后,若存在保存的轴范围,手动设置xlim和ylim恢复之前的缩放状态

内容的提问来源于stack exchange,提问作者hm8

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 08:07:45