如何实现Plotly图表中X轴范围变化时自动调整Y轴?
问题:Plotly Range Selector点击后自动调整Y轴范围
我有一个使用range selector的Plotly图表,代码如下:
import datetime import dash from dash import dcc, html import plotly from dash.dependencies import Input, Output from pyorbital.orbital import Orbital satellite = Orbital('TERRA') app = dash.Dash(__name__) app.layout = html.Div( html.Div([ html.H4('TERRA Satellite Live Feed'), html.Div(id='live-update-text'), dcc.Graph(id='live-update-graph'), dcc.Interval( id='interval-component', interval=1*1000, # in milliseconds n_intervals=0 ) ]) ) @app.callback(Output('live-update-text', 'children'), Input('interval-component', 'n_intervals')) def update_metrics(n): lon, lat, alt = satellite.get_lonlatalt(datetime.datetime.now()) style = {'padding': '5px', 'fontSize': '16px'} return [ html.Span('Longitude: {0:.2f}'.format(lon), style=style), html.Span('Latitude: {0:.2f}'.format(lat), style=style), html.Span('Altitude: {0:0.2f}'.format(alt), style=style) ] # Multiple components can update everytime interval gets fired. @app.callback(Output('live-update-graph', 'figure'), Input('interval-component', 'n_intervals')) def update_graph_live(n): satellite = Orbital('TERRA') data = { 'time': [], 'Latitude': [], 'Longitude': [], 'Altitude': [] } # Collect some data for i in range(180): time = datetime.datetime.now() - datetime.timedelta(seconds=i*20) lon, lat, alt = satellite.get_lonlatalt( time ) data['Longitude'].append(lon) data['Latitude'].append(lat) data['Altitude'].append(alt) data['time'].append(time) # Create the graph with subplots fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2) fig['layout']['margin'] = { 'l': 30, 'r': 10, 'b': 30, 't': 10 } fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'} fig.append_trace({ 'x': data['time'], 'y': data['Altitude'], 'name': 'Altitude', 'mode': 'lines+markers', 'type': 'scatter' }, 1, 1) fig.append_trace({ 'x': data['Longitude'], 'y': data['Latitude'], 'text': data['time'], 'name': 'Longitude vs Latitude', 'mode': 'lines+markers', 'type': 'scatter' }, 2, 1) fig.update_layout( xaxis=dict( rangeselector=dict( buttons=list([ dict(count=1, label="1d", step="day", stepmode="backward") ]) ), type="date" ) ) return fig if __name__ == '__main__': app.run_server(debug=True)
我希望当用户点击筛选最近1天数据的按钮时,Y轴能自动调整范围,例如从默认的90-130调整为120-130,请问如何实现这一功能?
解决方案
要实现点击range selector按钮后自动调整Y轴范围,需要捕获图表的布局变更事件(即range selector的点击操作),然后根据选中的X轴时间范围筛选对应的数据,计算Y轴的合理范围并应用到图表上。具体修改步骤如下:
- 新增回调输入:将
dcc.Graph的relayoutData作为额外输入,用于捕获range selector的操作。 - 数据持久化:使用
dcc.Store组件存储卫星数据,避免每次回调重复获取,同时解决多用户环境下的数据冲突问题。 - 筛选数据并调整Y轴:当检测到range selector操作时,提取选中的时间范围,筛选对应数据后计算Y轴的最小/最大值,以此设置轴范围。
修改后的完整代码如下:
import datetime import dash from dash import dcc, html, Input, Output, State import plotly from pyorbital.orbital import Orbital import pandas as pd satellite = Orbital('TERRA') app = dash.Dash(__name__) app.layout = html.Div( html.Div([ html.H4('TERRA Satellite Live Feed'), html.Div(id='live-update-text'), dcc.Graph(id='live-update-graph'), dcc.Interval( id='interval-component', interval=1*1000, # in milliseconds n_intervals=0 ), # 存储卫星数据,避免重复计算 dcc.Store(id='satellite-data', data={}) ]) ) @app.callback( [Output('live-update-text', 'children'), Output('satellite-data', 'data')], Input('interval-component', 'n_intervals') ) def update_metrics_and_data(n): # 获取当前卫星位置 lon, lat, alt = satellite.get_lonlatalt(datetime.datetime.now()) style = {'padding': '5px', 'fontSize': '16px'} text_children = [ html.Span('Longitude: {0:.2f}'.format(lon), style=style), html.Span('Latitude: {0:.2f}'.format(lat), style=style), html.Span('Altitude: {0:0.2f}'.format(alt), style=style) ] # 收集历史数据 data = { 'time': [], 'Latitude': [], 'Longitude': [], 'Altitude': [] } for i in range(180): time = datetime.datetime.now() - datetime.timedelta(seconds=i*20) lon, lat, alt = satellite.get_lonlatalt(time) data['Longitude'].append(lon) data['Latitude'].append(lat) data['Altitude'].append(alt) data['time'].append(time.isoformat()) # 转换为ISO字符串方便存储 df = pd.DataFrame(data) return text_children, df.to_dict('records') @app.callback( Output('live-update-graph', 'figure'), [Input('interval-component', 'n_intervals'), Input('live-update-graph', 'relayoutData')], State('satellite-data', 'data') ) def update_graph_live(n, relayout_data, stored_data): if not stored_data: return plotly.graph_objects.Figure() df = pd.DataFrame(stored_data) df['time'] = pd.to_datetime(df['time']) # 创建基础图表 fig = plotly.tools.make_subplots(rows=2, cols=1, vertical_spacing=0.2) fig['layout']['margin'] = {'l': 30, 'r': 10, 'b': 30, 't': 10} fig['layout']['legend'] = {'x': 0, 'y': 1, 'xanchor': 'left'} fig.append_trace({ 'x': df['time'], 'y': df['Altitude'], 'name': 'Altitude', 'mode': 'lines+markers', 'type': 'scatter' }, 1, 1) fig.append_trace({ 'x': df['Longitude'], 'y': df['Latitude'], 'text': df['time'], 'name': 'Longitude vs Latitude', 'mode': 'lines+markers', 'type': 'scatter' }, 2, 1) fig.update_layout( xaxis=dict( rangeselector=dict( buttons=list([ dict(count=1, label="1d", step="day", stepmode="backward") ]) ), type="date" ) ) # 检测range selector操作,调整Y轴范围 if relayout_data and 'xaxis.range[0]' in relayout_data: # 获取选中的时间范围 start_time = pd.to_datetime(relayout_data['xaxis.range[0]']) end_time = pd.to_datetime(relayout_data['xaxis.range[1]']) # 筛选该时间范围内的海拔数据 filtered_alt = df[(df['time'] >= start_time) & (df['time'] <= end_time)]['Altitude'] if not filtered_alt.empty: # 设置Y轴范围(添加±1的缓冲,避免数据贴边) y_min = filtered_alt.min() - 1 y_max = filtered_alt.max() + 1 fig.update_yaxes(range=[y_min, y_max], row=1, col=1) return fig if __name__ == '__main__': app.run_server(debug=True)
关键说明
relayoutData的作用:这个参数会捕获用户对图表布局的所有修改操作,包括range selector的点击,我们可以从中提取选中的X轴时间范围。- 数据筛选逻辑:根据选中的时间范围过滤数据,只保留该区间内的海拔值,计算其极值后设置Y轴范围,实现自动适配。
dcc.Store的优势:相比全局变量,它更适合Dash的多用户场景,能确保每个用户的数据独立不冲突。
内容的提问来源于stack exchange,提问作者Cauder
相关产品推荐
相关产品推荐

