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()
关键说明
- 轴范围捕获:通过
RangeXY流监听Points图的轴范围变化,实时保存到current_ranges字典中 - 复用组件:Points图和时序DynamicMap只初始化一次,切换时仅更新数据源,避免重建组件导致轴重置
- 轴范围恢复:切换数据集后,若存在保存的轴范围,手动设置
xlim和ylim恢复之前的缩放状态
内容的提问来源于stack exchange,提问作者hm8
相关产品推荐
相关产品推荐

