正确地为动态下拉菜单设置回调,如破折号

时间:2020-09-28 18:26:36

标签: python plotly plotly-dash

我正在尝试创建一个Dash仪表板,其中一个框中的下拉选项取决于先前的下拉选择。

数据由两个字典组成,每个字典有两个键。每个键包含一个带有两列的数据框。确切数据:

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

df_vals_prod={'corn':pd.DataFrame({'time': {1: Timestamp('2020-09-23 06:00:00'),
  2: Timestamp('2020-09-23 12:00:00'),
  3: Timestamp('2020-09-23 18:00:00'),
  4: Timestamp('2020-09-24 00:00:00')},
 '2m_temp_prod': {1: 0.020584322444347606,
  2: 0.08973907730395358,
  3: 2.3866310395722463,
  4: 3.065472457668321},
 'total_precip_prod': {1: 1.372708470272411,
  2: 2.135683294556938,
  3: 1.9811172016307312,
  4: 2.1082116841869323}}),
'soybeans':pd.DataFrame({'time': {1: Timestamp('2020-09-23 06:00:00'),
  2: Timestamp('2020-09-23 12:00:00'),
  3: Timestamp('2020-09-23 18:00:00'),
  4: Timestamp('2020-09-24 00:00:00')},
 '2m_temp_prod': {1: 0.6989001827317545,
  2: -0.8699121426411993,
  3: -0.9484359259520706,
  4: 0.7391299158393124},
 'total_precip_prod': {1: -0.07639291299336869,
  2: 0.19182892415959496,
  3: 0.8719339093510236,
  4: 0.90586956349059}})}

df_vals_area={'corn':pd.DataFrame({'time': {1: Timestamp('2020-09-23 06:00:00'),
  2: Timestamp('2020-09-23 12:00:00'),
  3: Timestamp('2020-09-23 18:00:00'),
  4: Timestamp('2020-09-24 00:00:00')},
 '2m_temp_area': {1: -1.6820417878457192,
  2: -0.2856437053872421,
  3: 0.3864022581278122,
  4: 0.5873739667356371},
 'total_precip_area': {1: 1.3703311242708185,
  2: 0.25528434511264525,
  3: 0.5007488191835624,
  4: -0.16292114222272375}}),
'soybeans':pd.DataFrame({'time': {1: Timestamp('2020-09-23 06:00:00'),
  2: Timestamp('2020-09-23 12:00:00'),
  3: Timestamp('2020-09-23 18:00:00'),
  4: Timestamp('2020-09-24 00:00:00')},
 '2m_temp_area': {1: 1.3789989862086967,
  2: -0.7797086923820608,
  3: 1.0695635889750523,
  4: 1.136561500804678},
 'total_precip_area': {1: -0.6035111830104833,
  2: -0.18237330469451313,
  3: -0.7820158376898607,
  4: -0.6117188028872137}})}

app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])
weight_opts=['Production','Area']

controls = dbc.Card(
    [    dbc.FormGroup(
            [
                dbc.Label("Crop"),
                dcc.Dropdown(
                    id="Crop",
                    options=[
                        {"label": col, "value": col} for col in list(df_vals_prod.keys())
                    ],
                    value=list(df_vals_prod.keys())[0],
                    clearable=False,
                ),
            ]
        ),    
        
        
        
        dbc.FormGroup(
            [
                dbc.Label("Weighting"),
                dcc.Dropdown(
                    id="weights",
                    options=[
                        {"label": i, "value": i} for i in weight_opts
                    ],
                    value=weight_opts[0],
                    clearable=False,
                ),
            ]
        ),
        dbc.FormGroup(
            [
                dbc.Label("Forecast Variable"),
                dcc.Dropdown(
                    id="forecast_v",
                ),
            ]
        ),

    ],
    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,
)

@app.callback(
    Output('forecast_v','options'),
    [Input('weights', 'value')]
)

def update_var_dropdown(weight):
    if weight=='Production':
        return [{'label': i, 'value': i} for i in df_vals_prod['corn'].columns[1:]]
    elif weight=='Area':
        return [{'label': i, 'value': i} for i in df_vals_area['corn'].columns[1:]]


@app.callback(
    Output("crop-graph", "figure"),
    [   Input("Crop", "value"),
        Input("weights", "value"),
        Input("forecast_v", "value"),

    ],
)

def crop_graph(Crop, val, weight):

    # plotly figure setup
    fig = make_subplots(specs=[[{"secondary_y": True}]])
    
    if weight:
        fig.add_trace(go.Scatter(name=val, x=df_vals_prod[Crop]['time'], y=((df_vals_prod[Crop][val]-273)*(9/5))+32, 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.update_yaxes(title_text="<b>Temp (F)<b>", color='red', secondary_y=False,)
        fig.update_yaxes(title_text="<b>24hr Forecast Change (F)</b>", secondary_y=True)

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

如您所见,这6个小时的数据将被绘制为时间序列。现在,我想添加几个下拉菜单。第一个下拉菜单(作物)选择要选择的作物(玉米或大豆),这是每个词典中的两个键。

第二个下拉列表(权重)现在选择我们要使用的数据框。用户在第二个下拉菜单中选择的内容将确定在第三个下拉菜单中选择的选项。

第三个下拉列表将选择实际变量(预测变量),该变量是每个数据帧中可用的两列之一。因此,如果在下拉列表2中选择了“生产”,则下拉列表3的选项将由“ 2m_temp_prod”或“ total_precip_prod”组成。对于下拉菜单2中的“区域”,下拉菜单3中的选项为“ 2m_temp_area”或“ total_precip_area”。

这是我到目前为止的代码。我能够为下拉菜单正确设置回调,但是我认为第二个回调不能正常工作。我了解如何创建动态下拉菜单,但是我不确定如何将其转换为实际绘制数据。

将产生此情节。请注意,下拉列表是我想要的,但它不会显示。如何添加“权重”以绘制所需的图?我期望的只是一个简单的折线图,其中的数据取决于所选的所有下拉菜单。

enter image description here

编辑:正如《战地》所建议的那样,我包括的数据样本要小得多。在这种情况下,具体值无关紧要,仅取决于数据的结构。有关更简洁的数据,请参见上文。

1 个答案:

答案 0 :(得分:1)

我无法弄清楚为什么您的代码失败。但是我一直在整理一个例子,我认为会接近您在这里寻找的内容。它建立在example from the plotly docs之上,因此布局与您的问题有所不同。主要要点是,三组单选按钮将使您:

  1. 选择权重:['prod', 'area']
  2. 依次定义另一个回调中的选项:['2m_temp_prod', 'total_precip_prod'] ['2m_temp_area', 'total_precip_area']
  3. 您还可以选择农产品['corn', 'soybeans']

我很可能误解了您想要在此处实现的逻辑。但只要在此过程中给我一些反馈,我们就可以得出详细信息。

用于选择DF: prod | Crops: corn | Column: 2m_temp_prod的短跑应用

enter image description here

用于选择DF: area | Crops: soybeans | Column: total_precip_area的短跑应用

enter image description here

完整代码:

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.RadioItems(
        id='produce-radio',
        options=[{'label': k, 'value': k} for k in all_options.keys()],
        value='prod'
    ),

    html.Hr(),
    
    dcc.RadioItems(
        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.RadioItems(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):
    
    # data source / weighting
    if selected_produce == 'prod':
        dfd = df_vals_prod
    if selected_produce == 'area':
        dfd = df_vals_area
    
    # plotly figure
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=dfd[available_crops].index, y=dfd[available_crops][selected_column]))
    fig.update_layout(title=dict(text='DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ selected_column))
    return(fig)

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

编辑1-下拉菜单。

要获得所需的下拉按钮,您要做的就是更改每个按钮

dcc.RadioItems()

 dcc.Dropdown()

现在您将获得:

enter image description here

完整代码:

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):
    
    # data source / weighting
    if selected_produce == 'prod':
        dfd = df_vals_prod
    if selected_produce == 'area':
        dfd = df_vals_area
    
    # plotly figure
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=dfd[available_crops].index, y=dfd[available_crops][selected_column]))
    fig.update_layout(title=dict(text='DF: ' + selected_produce +' | Crops: ' + available_crops + ' | Column: '+ selected_column))
    return(fig)

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