首页 > 解决方案 > Plotly-Dash:更新没有自己下拉列表的图表上的跟踪

问题描述

这个问题基本上是对这个先前提出的问题的补充:

正确设置动态下拉列表的回调 plotly dash

现在,我想在我的绘图中添加第二条轨迹,它位于辅助 y 轴上。辅助 y 轴上的绘图数据将来自类似结构的字典和数据框,也具有类似的命名约定。这就是我所拥有的。

app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

index1= [1,2,3,4]
columns1 =['time', '2m_temp_prod' , 'total_precip_prod']

index2= [1,2,3,4]
columns2 = ['time', '2m_temp_area', 'total_precip_area']

df_vals_prod = {'corn': pd.DataFrame(index=index1, columns = columns1,
                                data= np.random.randn(len(index1),len(columns1))).cumsum(),
                'soybeans' : pd.DataFrame(index=index1, columns = columns1,
                                     data= np.random.randn(len(index1),len(columns1))).cumsum()}

df_vals_area= {'corn': pd.DataFrame(index=index2, columns = columns2,
                                data= np.random.randn(len(index2),len(columns2))).cumsum(),
               'soybeans' : pd.DataFrame(index=index2, columns = columns2,
                                     data= np.random.randn(len(index2),len(columns2))).cumsum()}


# mimic data properties of your real world data
df_vals_prod['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'), 
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['corn'].set_index('time', inplace = True)
df_vals_prod['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['soybeans'].set_index('time', inplace = True)

df_vals_area['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['corn'].set_index('time', inplace = True)
df_vals_area['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['soybeans'].set_index('time', inplace = True)

index3= [1,2,3,4]
columns3 =['time', '2m_temp_24hdelta_prod' , 'total_precip_24hdelta_prod']

index4= [1,2,3,4]
columns4 = ['time', '2m_temp_24hdelta_area', 'total_precip_24hdelta_area']

df_deltas_prod = {'corn': pd.DataFrame(index=index3, columns = columns3,
                                data= np.random.randn(len(index3),len(columns3))).cumsum(),
                'soybeans' : pd.DataFrame(index=index3, columns = columns3,
                                     data= np.random.randn(len(index3),len(columns3))).cumsum()}

df_deltas_area= {'corn': pd.DataFrame(index=index4, columns = columns4,
                                data= np.random.randn(len(index4),len(columns4))).cumsum(),
               'soybeans' : pd.DataFrame(index=index4, columns = columns4,
                                     data= np.random.randn(len(index4),len(columns4))).cumsum()}

# mimic data properties of your real world data
df_deltas_prod['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'), 
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_prod['corn'].set_index('time', inplace = True)
df_deltas_prod['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_prod['soybeans'].set_index('time', inplace = True)

df_deltas_area['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_area['corn'].set_index('time', inplace = True)
df_deltas_area['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_deltas_area['soybeans'].set_index('time', inplace = True)

# weighting
all_options = {
    'Production': list(df_vals_prod[list(df_vals_prod.keys())[0]].columns[1:]),
    'Area': list(df_vals_area[list(df_vals_prod.keys())[0]].columns[1:])
}


controls = dbc.Card(
    [    dbc.FormGroup(
            [
                dbc.Label("Crop"),
                dcc.Dropdown(
                    id='crop_dd',
                    options=[{'label': k.title(), 'value': k} for k in list(df_vals_prod.keys())],
                    value=list(df_vals_prod.keys())[0],
                    clearable=False,
                ),
            ]
        ),    
        
        
        
        dbc.FormGroup(
            [
                dbc.Label("Weighting"),
                dcc.Dropdown(
                    id='weight_dd',
                    options=[{'label': k, 'value': k} for k in all_options.keys()],
                    value='Area',
                    clearable=False,
                ),
            ]
        ),
        dbc.FormGroup(
            [
                dbc.Label("Forecast Variable"),
                dcc.Dropdown(
                    id='columns_dd',
                    clearable=False,
                ),
            ]
        ),

    ],
    body=True,
)


app.layout = dbc.Container(
    [
        html.Hr(),
        dbc.Row([
            dbc.Col([
                dbc.Row([
                    dbc.Col(controls)
                ],  align="start"), 
                dbc.Row([
                    dbc.Col([
                        html.Br(),
                        dbc.Row([
                            dbc.Col([html.Div(id = 'txt1')
                            ])
                        ]),
                        html.Br(),
                        dbc.Row([
                            dbc.Col([html.Div(id = 'txt2')])
                        ])
                    ])
                ])
            ],xs = 2)
            ,
            dbc.Col([
                dbc.Row([
                    dbc.Col([html.Div(id = 'plot_title')],)
                ]),
                dbc.Row([
                    dbc.Col(dcc.Graph(id="crop-graph")),
                    #dbc.Col(dcc.Graph(id="cluster-graph"))
                ])
            ])
        ],), 
    ],
    fluid=True,
)
    



# Callbacks #####################################################################

# Weighting selection.
@app.callback( # Dataframe PROD or AREA
    Output('columns_dd', 'options'),
    # layout element: dcc.RadioItems(id='weight_dd'...)
    [Input('weight_dd', 'value')])
def set_columns_options(weight):
    varz =  [{'label': i, 'value': i} for i in all_options[weight]]
    return [{'label': i, 'value': i} for i in all_options[weight]]

# Columns selection
@app.callback( 
    Output('columns_dd', 'value'),
    [Input('columns_dd', 'options')])
def set_columns(available_options):
    return available_options[1]['value']

# Crop selection
@app.callback( 
    Output('crop_dd', 'value'),
    [Input('crop_dd', 'options')])
def set_crops(available_crops):
    return available_crops[0]['value']




# Make a figure based on the selections
@app.callback( # Columns 2m_temp_prod, or....
    Output('crop-graph', 'figure'),
    [Input('weight_dd', 'value'),
     Input('crop_dd', 'value'),
     Input('columns_dd', 'value')])
def make_graph(weight, available_crops, vals):
    
    # data source / weighting
    if weight == 'Production':
        dfv = df_vals_prod
        #dfd = df_deltas_prod
    if weight == 'Area':
        dfv = df_vals_area
        #dfd= df_deltas_area
    
    # plotly figure
    fig = make_subplots(specs=[[{"secondary_y": True}]])
    if 'precip' in vals:
        fig.add_trace(go.Scatter(x=df_vals_prod[available_crops]['time'], y=round((dfv[available_crops][vals].cumsum()/25.4),2),
                                mode = 'lines', line=dict(color='lime', width=4),
                                hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Precip: %{y:.2f} in<extra></extra>'), secondary_y=False)
    else:
        fig.add_trace(go.Scatter(x=df_vals_prod[available_crops]['time'], y=round(((dfv[available_crops][vals]-273.15)*(9/5))+32,2),
                                mode = 'lines', line=dict(color='red', width=4),
                                hovertemplate='Date: %{x|%d %b %H%M} UTC<br>Temp: %{y:.2f} F<extra></extra>'), secondary_y=False)
    #fig.add_trace(go.Bar(x=dfd[available_crops].index, y=dfd[available_crops][deltas]), secondary_y=True)
    fig.update_layout(title=dict(text='Crop: ' + available_crops.title() + ', Weight: ' +weight+ ', Variable: '+ vals))
    fig.update_layout(yaxis2_showgrid=False,showlegend=False, 
                      width=1500,height=800,yaxis_zeroline=False, yaxis2_zeroline=False)
    fig.update_layout(template="plotly_dark", plot_bgcolor='#272B30', paper_bgcolor='#272B30')
    fig.update_yaxes(automargin=True)
    return(fig)

app.run_server(mode='external', port = 8099)

请注意字典名称和字典中的数据框列如何具有相似的名称。我希望他们在情节上保持在一起。

例如,用户选择加权:生产、作物:玉米、预测变量:2m_temp_prod。这应该绘制一个线图。现在,我想添加一个辅助 y 轴,其中 2m_temp_24hdelta_prod 被绘制(来自 df_deltas_prod['corn']['2m_temp_24hdelta_prod']。请注意,虽然我不想要一个下拉菜单,但我只是希望它是根据其他下拉选择绘制。最后,如果用户切换到权重:面积,作物:玉米,预测变量:2m_temp_area,次要 y 轴将绘制 df_deltas_area['corn']['2m_temp_24hdelta_area']。希望这个清楚了。

标签: pythonplotlyplotly-dashplotly-python

解决方案


只是为了看看我是否在这里正确理解了您的逻辑,我将在链接问题的答案中构建代码段,而不是您在此处提供的代码段。如果事实证明由下面的代码片段生成的应用程序的结构实际上是您正在寻找的,我会看看我是否可以在这个问题的代码片段中使用它。(但首先,请提供一个包含必要导入的完整工作片段)。

逻辑:

正如您通过研究下面的代码片段所看到的那样,此建议仅限于在另一个 dict的辅助 y 轴上显示相关列( '2m_temp_prod'vs ) ,但对于相同的类别,如or 。因此,如果主轴在主轴上显示for from ,则辅助 yaxis 显示for from 。'2m_temp_area''corn''soybeans''2m_temp_prod'''corn'df_vals_prod''2m_temp_area''corn''df_vals_prod'

为了遵循所描述的逻辑,更新的代码片段中的一个关键附录是:

# secondary yaxis
column_implied_lst = [e for e in dfd2[available_crops].columns if e[:4]==selected_column[:4]]
column_implied = column_implied_lst[0]

fig.add_trace(go.Bar(x=dfd2[available_crops].index,
                     y=dfd2[available_crops][column_implied],
                     marker_color = "rgba(255,0,0,0.4)"),
              secondary_y=True)
fig.update_layout(yaxis2=dict(title=dict(text='DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ column_implied)))

编辑:字符串匹配

我使用了一种非常基本的字符串匹配方法column_implied_lst = [e for e in dfd2[available_crops].columns if e[:4]==selected_column[:4]]。在为您的答案构建解决方案时,我提出了如何从字符串列表中检索部分匹配项的问题,结果得到了一些非常好的答案。如果我提供的方法不适合您的需求,请查看那些。

Dash 应用程序:

在此处输入图像描述

完整代码(第一个音高)

from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output

# data
from jupyter_dash import JupyterDash
import dash_core_components as dcc
import dash_html_components as html
from dash.dependencies import Input, Output, State, ClientsideFunction
import dash_core_components as dcc
import dash_html_components as html
import pandas as pd
import plotly.graph_objs as go
from dash.dependencies import Input, Output
import dash_bootstrap_components as dbc
import numpy as np
from plotly.subplots import make_subplots
import plotly.express as px
import pandas as pd
from pandas import Timestamp
import numpy as np

# data ##########################################################################
index1= [1,2,3,4]
columns1 =['time', '2m_temp_prod' , 'total_precip_prod']

index2= [1,2,3,4]
columns2 = ['time', '2m_temp_area', 'total_precip_area']

df_vals_prod = {'corn': pd.DataFrame(index=index1, columns = columns1,
                                data= np.random.randn(len(index1),len(columns1))).cumsum(),
                'soybeans' : pd.DataFrame(index=index1, columns = columns1,
                                     data= np.random.randn(len(index1),len(columns1))).cumsum()}

df_vals_area= {'corn': pd.DataFrame(index=index2, columns = columns2,
                                data= np.random.randn(len(index2),len(columns2))).cumsum(),
               'soybeans' : pd.DataFrame(index=index2, columns = columns2,
                                     data= np.random.randn(len(index2),len(columns2))).cumsum()}

# mimic data properties of your real world data
df_vals_prod['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'), 
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['corn'].set_index('time', inplace = True)
df_vals_prod['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_prod['soybeans'].set_index('time', inplace = True)

df_vals_area['corn']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                  Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['corn'].set_index('time', inplace = True)
df_vals_area['soybeans']['time'] =   [Timestamp('2020-09-23 06:00:00'), Timestamp('2020-09-23 12:00:00'),
                                      Timestamp('2020-09-23 18:00:00'), Timestamp('2020-09-24 00:00:00')]
df_vals_area['soybeans'].set_index('time', inplace = True)

# dash ##########################################################################
app = JupyterDash(__name__)

# weighting
all_options = {
    'prod': list(df_vals_prod[list(df_vals_prod.keys())[0]].columns),
    'area': list(df_vals_area[list(df_vals_prod.keys())[0]].columns)
}

app.layout = html.Div([
    dcc.Dropdown(
        id='produce-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='area'
    ),
#     dcc.Dropdown(
#     id='produce-radio',
#     options=[
#         {'label': k, 'value': k} for k in all_options.keys()
#     ],
#     value='prod',
#     clearable=False),
    

    html.Hr(),
    
    dcc.Dropdown(
        id='crop-radio',
        options=[{'label': k, 'value': k} for k in list(df_vals_prod.keys())],
        value=list(df_vals_prod.keys())[0]
    ),

    html.Hr(),

    dcc.Dropdown(id='columns-radio'),

    html.Hr(),

    html.Div(id='display-selected-values'),
    
    dcc.Graph(id="crop-graph")
])

# Callbacks #####################################################################

# Weighting selection.
@app.callback( # Dataframe PROD or AREA
    Output('columns-radio', 'options'),
    # layout element: dcc.RadioItems(id='produce-radio'...)
    [Input('produce-radio', 'value')])
def set_columns_options(selected_produce):
    varz =  [{'label': i, 'value': i} for i in all_options[selected_produce]]
    print('cb1 output: ')
    print(varz)
    return [{'label': i, 'value': i} for i in all_options[selected_produce]]

# Columns selection
@app.callback( 
    Output('columns-radio', 'value'),
    # layout element: dcc.RadioItems(id='columns-radio'...)
    [Input('columns-radio', 'options')])
def set_columns(available_options):
    return available_options[0]['value']

# Crop selection
@app.callback( 
    Output('crop-radio', 'value'),
    # layout element: dcc.RadioItems(id='columns-radio'...)
    [Input('crop-radio', 'options')])
def set_crops(available_crops):
    return available_crops[0]['value']

# Display selections in its own div
@app.callback( # Columns 2m_temp_prod, or....
    Output('display-selected-values', 'children'),
    [Input('produce-radio', 'value'),
     Input('crop-radio', 'value'),
     Input('columns-radio', 'value')])
def set_display_children(selected_produce, available_crops, selected_column):
    return('DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ selected_column)

# Make a figure based on the selections
@app.callback( # Columns 2m_temp_prod, or....
    Output('crop-graph', 'figure'),
    [Input('produce-radio', 'value'),
     Input('crop-radio', 'value'),
     Input('columns-radio', 'value')])
def make_graph(selected_produce, available_crops, selected_column):
    #global selected_column
    
    # data source / weighting
    if selected_produce == 'prod':
        dfd = df_vals_prod
        dfd2 = df_vals_area
    if selected_produce == 'area':
        dfd = df_vals_area
        dfd2 = df_vals_prod
    
    # plotly figure
    
    # primary yaxis
    fig = make_subplots(specs=[[{"secondary_y": True}]])
    fig.add_trace(go.Scatter(x=dfd[available_crops].index, y=dfd[available_crops][selected_column]), secondary_y=False)
    fig.update_layout(yaxis1=dict(title=dict(text='DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ selected_column)))
    
    # secondary yaxis
    
    column_implied_lst = [e for e in dfd2[available_crops].columns if e[:4]==selected_column[:4]]
    column_implied = column_implied_lst[0]
    
    fig.add_trace(go.Bar(x=dfd2[available_crops].index,
                         y=dfd2[available_crops][column_implied],
                         marker_color = "rgba(255,0,0,0.4)"),
                  secondary_y=True)
    fig.update_layout(yaxis2=dict(title=dict(text='DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ column_implied)))
    
    # layout makeover
    fig.update_layout(title=dict(text='Column to match: '+ selected_column + '| Implied match: ' +column_implied))
    fig['layout']['yaxis2']['showgrid'] = False
    
    return(fig)

app.run_server(mode='inline', port = 8077, dev_tools_ui=True,
          dev_tools_hot_reload =True, threaded=True)

推荐阅读