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

如何实现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轴的合理范围并应用到图表上。具体修改步骤如下:

  1. 新增回调输入:将dcc.Graph的relayoutData作为额外输入,用于捕获range selector的操作。
  2. 数据持久化:使用dcc.Store组件存储卫星数据,避免每次回调重复获取,同时解决多用户环境下的数据冲突问题。
  3. 筛选数据并调整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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 14:18:15