防止链接的回调将下拉列表切换为原始值破折号

时间:2020-10-08 19:14:58

标签: python plotly plotly-dash

我有一段冗长的代码,用于源自多个嵌套字典的多个链接回调。我有所有想要提供的必要下拉菜单和选项。但是,每当我在此示例中将“ crop”下拉菜单更改为原始选项(即玉米)以外的其他内容时,都会将下面的“ Weighting”下拉菜单重置。同样,如果我更改“加权”下拉菜单,则会将“预测变量”下拉菜单重置为原始选项。我该如何预防?链接回调的目的是使更改一个选项将更改绘制的数据,因为它们都已链接。

我认为这里的数据不重要?但是它的功能是这样的:

final_dict[init_date][model][weight][crop]

然后,上述确切的词典将输出一个数据帧。数据框中的列将成为“预测变量”,并将最终绘制出来。如果我确实需要添加数据,我可以尝试做,但是字典非常大。

这是我到目前为止的代码。请注意,该图是空的,因为我还没走那么远。

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
from pandas import Timestamp
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
import numpy as np
from datetime import timedelta
import glob
import datetime as dt

import xarray as xr
import os 
from PIL import Image
import time
import random

my_dict={}

for i in np.arange(1,17,1):
    n=random.randint(1,10)
    m=random.randint(1,10)
    data=[[pd.Timestamp('2020-10-06'),n,m],[pd.Timestamp('2020-10-07'),m,n],[pd.Timestamp('2020-10-08'),n,m],[pd.Timestamp('2020-10-09'),m,n]]
    my_dict[i]=pd.DataFrame(data=data, columns=['time', 'Temp','Precip'])

final_dict={'day1':{'model1':{'weight1':{'crop1':my_dict[1], 'crop2':my_dict[2]},
                           'weight2':{'crop1':my_dict[3], 'crop2':my_dict[4]}},
                 
                 'model2':{'weight1':{'crop1':my_dict[5], 'crop2':my_dict[6]},
                           'weight2':{'crop1':my_dict[7], 'crop2':my_dict[8]}}},
         
         'day2':{'model1':{'weight1':{'crop1':my_dict[9], 'crop2':my_dict[10]},
                           'weight2':{'crop1':my_dict[11], 'crop2':my_dict[12]}},
                 
                 'model2':{'weight1':{'crop1':my_dict[13], 'crop2':my_dict[14]},
                           'weight2':{'crop1':my_dict[15], 'crop2':my_dict[16]}}}}

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

controls = dbc.Card(
    [   dbc.FormGroup(
            [dbc.Label("Init Date"),
                dcc.Dropdown(
                    id='init_dd',
                    options=[{'label': k, 'value': k} for k in final_dict.keys()],
                    value=list(final_dict.keys())[0],
                    clearable=False,
                ),
            ]
        ),
        dbc.FormGroup(
            [dbc.Label("Model"),
                dcc.Dropdown(
                    id='model_dd',
                    clearable=False,
                ),
            ]
        ), 
        dbc.FormGroup(
            [dbc.Label("Crop"),
                dcc.Dropdown(
                    id='crop_dd',
                    clearable=False,
                ),
            ]
        ),           
        dbc.FormGroup(
            [dbc.Label("Weighting"),
                dcc.Dropdown(
                    id='weight_dd',
                    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"), 
            ],xs = 2)
            ,
            dbc.Col([
                dbc.Row([
                    dbc.Col([html.Div(id = 'plot_title')],)
                ]),
                dbc.Row([
                    dbc.Col(dcc.Graph(id="crop-graph")),
                ])
            ])
        ],), 
    ],
    fluid=True,
)
    
# Callbacks #####################################################################
#set the model
@app.callback(
    Output('model_dd', 'options'),
    [Input('init_dd', 'value')])
def set_model_options(model):
    return [{'label': i.replace('_',' '), 'value': i} for i in final_dict[model]]
 
@app.callback(
    Output('model_dd', 'value'),
    [Input('model_dd', 'options')])
def set_model_options_value(available_model):
    return available_model[0]['value']

#set the weight
@app.callback(
    Output('weight_dd', 'options'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value')])
def set_weight_options(selected_init, selected_model):
    return [{'label': i, 'value': i} for i in final_dict[selected_init][selected_model]]
 
@app.callback(
    Output('weight_dd', 'value'),
    [Input('weight_dd', 'options')])
def set_weight_value(available_weight):
    return available_weight[0]['value']

#set the crop
@app.callback(
    Output('crop_dd', 'options'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value'),
     Input('weight_dd', 'value')])
def set_crop_options(selected_init, selected_model, selected_weight):
    return [{'label': i, 'value': i} for i in final_dict[selected_init][selected_model][selected_weight]]
 
@app.callback(
    Output('crop_dd', 'value'),
    [Input('crop_dd', 'options')])
def set_crop_value(available_crop):
    return available_crop[0]['value']

#set the variable
@app.callback(
    Output('columns_dd', 'options'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value'),
     Input('weight_dd', 'value'),
     Input('crop_dd', 'value')])
def set_column_options(selected_init, selected_model, selected_weight, selected_crop):
    return [{'label': i, 'value': i} for i in final_dict[selected_init][selected_model][selected_weight][selected_crop].columns[1:]]
 
@app.callback(
    Output('columns_dd', 'value'),
    [Input('columns_dd', 'options')])
def set_column_value(available_column):
    return available_column[1]['value']

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

编辑:在示例虚拟数据中添加。请注意,当更改选项的某些组合时,其他选项如何切换回原始值。希望防止这种情况发生。

2 个答案:

答案 0 :(得分:2)

特定数据示例有所帮助。我看到了

  • 数据集存储在嵌套字典中
  • 您要允许用户选择一个特定的数据集(每个用户输入的选项取决于嵌套结构中的先前/上游选择)。
  • 因为这里的嵌套结构意味着对于给定的输入更改,您只想为后续/下游输入更新输入选项

关于更好地控制链接回调的问题,我认为这是在正确的位置使用Input()State()的问题。

尝试一下(我已将您的final_dict重命名,以便更轻松地监视正在发生的事情):

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
from pandas import Timestamp
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State
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
import numpy as np
from datetime import timedelta
import glob
import datetime as dt

import xarray as xr
import os 
from PIL import Image
import time
import random

my_dict={}

for i in np.arange(1,17,1):
    n=random.randint(1,10)
    m=random.randint(1,10)
    data=[[pd.Timestamp('2020-10-06'),n,m],[pd.Timestamp('2020-10-07'),m,n],[pd.Timestamp('2020-10-08'),n,m],[pd.Timestamp('2020-10-09'),m,n]]
    my_dict[i]=pd.DataFrame(data=data, columns=['time', 'Temp','Precip'])

final_dict={'day1':{'model1':{'weight1':{'crop1':my_dict[1], 'cropA':my_dict[2]},
                           'weight2':{'crop2':my_dict[3], 'cropB':my_dict[4]}},
                 
                 'model2':{'weight3':{'crop3':my_dict[5], 'cropC':my_dict[6]},
                           'weight4':{'crop4':my_dict[7], 'cropD':my_dict[8]}}},
         
         'day2':{'model3':{'weight5':{'crop5':my_dict[9], 'cropE':my_dict[10]},
                           'weight6':{'crop6':my_dict[11], 'cropF':my_dict[12]}},
                
                 'model4':{'weight7':{'crop7':my_dict[13], 'cropG':my_dict[14]},
                           'weight8':{'crop8':my_dict[15], 'cropH':my_dict[16]}}}}

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

controls = dbc.Card(
    [   dbc.FormGroup(
            [dbc.Label("Init Date"),
                dcc.Dropdown(
                    id='init_dd',
                    options=[{'label': k, 'value': k} for k in final_dict.keys()],
                    value=list(final_dict.keys())[0],
                    clearable=False,
                ),
            ]
        ),
        dbc.FormGroup(
            [dbc.Label("Model"),
                dcc.Dropdown(
                    id='model_dd',
                    clearable=False,
                ),
            ]
        ), 
        dbc.FormGroup(
            [dbc.Label("Crop"),
                dcc.Dropdown(
                    id='crop_dd',
                    clearable=False,
                ),
            ]
        ),           
        dbc.FormGroup(
            [dbc.Label("Weighting"),
                dcc.Dropdown(
                    id='weight_dd',
                    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"), 
            ],xs = 2)
            ,
            dbc.Col([
                dbc.Row(html.Div(id='selected_data')),
                # dbc.Row([
                #     dbc.Col([html.Div(id = 'plot_title')],)
                # ]),
                dbc.Row([
                    dbc.Col(dcc.Graph(id="crop-graph")),
                ])                
            ])
        ],), 
    ],
    fluid=True,
)
    
# Callbacks #####################################################################
#set the model
@app.callback(
    Output('model_dd', 'options'),
    [Input('init_dd', 'value')])
def set_model_options(model):
    return [{'label': i.replace('_',' '), 'value': i} for i in final_dict.get(model).keys()]
 
@app.callback(
    Output('model_dd', 'value'),
    [Input('model_dd', 'options')])
def set_model_options_value(available_model):
    return available_model[0]['value']

#set the weight
@app.callback(
    Output('weight_dd', 'options'),
    [Input('model_dd', 'value')],
    [State('init_dd', 'value')])
def set_weight_options(selected_model, selected_init):
    if selected_model is None: return None 
    print('selected_model(): ', selected_init, selected_model)
    return [{'label': i, 'value': i} for i in final_dict.get(selected_init).get(selected_model).keys()]
 
@app.callback(
    Output('weight_dd', 'value'),
    [Input('weight_dd', 'options')])
def set_weight_value(available_weight):
    return available_weight[0]['value']

#set the crop
@app.callback(
    Output('crop_dd', 'options'),
     [Input('weight_dd', 'value')],
     [State('init_dd', 'value'),
     State('model_dd', 'value')])
def set_crop_options(selected_weight, selected_init, selected_model):
    if selected_model is None or selected_weight is None: return None 
    print('set_crop_options(): ',selected_init, selected_model, selected_weight)
    return [{'label': i, 'value': i} for i in final_dict.get(selected_init).get(selected_model).get(selected_weight).keys()]

@app.callback(
    Output('crop_dd', 'value'),
    [Input('crop_dd', 'options')])
def set_crop_value(available_crop):
    return available_crop[0]['value']

#set the variable
@app.callback(
    Output('columns_dd', 'options'),
    [Input('crop_dd', 'value')],
    [State('init_dd', 'value'),
     State('model_dd', 'value'),
     State('weight_dd', 'value')])
def set_column_options(selected_crop, selected_init, selected_model, selected_weight):
    if selected_crop is None or selected_weight is None or selected_model is None: return None
    print('set_column_options(): ', selected_init, selected_model, selected_weight, selected_crop)
    return [{'label': i, 'value': i} for i in final_dict.get(selected_init).get(selected_model).get(selected_weight).get(selected_crop).columns[1:]]
 
@app.callback(
    Output('columns_dd', 'value'),
    [Input('columns_dd', 'options')])
def set_column_value(available_column):
    if available_column is None: return None
    return available_column[1]['value']

@app.callback(
    Output('selected_data', 'children'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value'),
     Input('weight_dd', 'value'),
     Input('crop_dd', 'value'),
     Input('columns_dd','value')]
    )
def show_data(init_dd, model_dd, weight_dd, crop_dd, columns_dd):
    if crop_dd is None or weight_dd is None or model_dd is None or columns_dd is None: return None
    print('show_data():', init_dd, model_dd, weight_dd, crop_dd, columns_dd)
    try:
        data = final_dict[init_dd][model_dd][weight_dd][crop_dd][columns_dd].to_json(orient='split')
    except:
        return
    return data


def make_plot(df, var):
    fig = go.Figure(
            data=[go.Scatter(x=df['time'], y=df[var], name=var)],
            layout={
                'yaxis': {'title': f'Plot of <b>{var}</b>'}
            }
        )
    return fig


no_data_fig = {"layout": {
        "xaxis": { "visible": False},
        "yaxis": {"visible": False},
        "annotations": [
            { "text": "",
                "xref": "paper",
                "yref": "paper",
                "showarrow": False,
                "font": {"size": 20 }
            }]
        }
    }

@app.callback(
    Output('crop-graph', 'figure'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value'),
     Input('weight_dd', 'value'),
     Input('crop_dd', 'value'),
     Input('columns_dd','value')]
    )
def plot_data(init_dd, model_dd, weight_dd, crop_dd, columns_dd):
    if crop_dd is None or weight_dd is None or model_dd is None or columns_dd is None: return None
    print('plot_data():', init_dd, model_dd, weight_dd, crop_dd, columns_dd)
    try:
        data = final_dict[init_dd][model_dd][weight_dd][crop_dd]
        data_col = data[columns_dd]
    except:
        return no_data_fig
    return make_plot(data, columns_dd)


app.run_server(mode='external', port = 8098, debug=True)   

答案 1 :(得分:0)

这是另一个版本。

  • 我注意到您想保持数据列column_dd固定而不被更新(可能假设最终数据集中不同版本的数据列相同)。因此,我注释掉了用于更新column_dd的回调。
  • 您还可以将输出合并为列表。
  • 我试图展示一种使用动态输入生成的方法,但最终不需要动态更新它(我不能两次指定相同的output-id,这很不方便。我只是将其保留为一个演示,而您不需要切换到这种样式。)请注意,仍然可以在回调中将其用作State()并覆盖其属性。)
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
from pandas import Timestamp
import plotly.graph_objs as go
from dash.dependencies import Input, Output, State
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
import numpy as np
from datetime import timedelta
import glob
import datetime as dt

import xarray as xr
import os 
from PIL import Image
import time
import random

my_dict={}

for i in np.arange(1,17,1):
    n=random.randint(1,10)
    m=random.randint(1,10)
    data=[[pd.Timestamp('2020-10-06'),n,m],[pd.Timestamp('2020-10-07'),m,n],[pd.Timestamp('2020-10-08'),n,m],[pd.Timestamp('2020-10-09'),m,n]]
    my_dict[i]=pd.DataFrame(data=data, columns=['time', 'Temp','Precip'])

final_dict={'day1':{'model1':{'weight1':{'crop1':my_dict[1], 'cropA':my_dict[2]},
                           'weight2':{'crop2':my_dict[3], 'cropB':my_dict[4]}},
                 
                 'model2':{'weight3':{'crop3':my_dict[5], 'cropC':my_dict[6]},
                           'weight4':{'crop4':my_dict[7], 'cropD':my_dict[8]}}},
         
         'day2':{'model3':{'weight5':{'crop5':my_dict[9], 'cropE':my_dict[10]},
                           'weight6':{'crop6':my_dict[11], 'cropF':my_dict[12]}},
                
                 'model4':{'weight7':{'crop7':my_dict[13], 'cropG':my_dict[14]},
                           'weight8':{'crop8':my_dict[15], 'cropH':my_dict[16]}}}}

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


# Helpers #####################################################################

def get_dict_keys(varlist):
    dic = final_dict
    for var in varlist:
        dic = dic.get(var)
    return dic

def make_options(option_iter):
    return [{'label': i, 'value': i} for i in option_iter]

class InputContainer:
    def __init__(self, input_dd='day1', model_dd='model1', weight_dd='weight1',
        crop_dd='crop1', columns_dd='Precip'):
        
        self.container = [ 
            dbc.FormGroup(
                [dbc.Label("Init Date"),
                    dcc.Dropdown(
                        id='init_dd',
                        options= make_options(final_dict.keys()),
                        value=input_dd,
                        clearable=False,
                    ),
                ]
            ),
            dbc.FormGroup(
                [dbc.Label("Model"),
                    dcc.Dropdown(
                        id='model_dd',
                        value = model_dd,
                        clearable=False,
                    ),
                ]
            ), 
            dbc.FormGroup(
                [dbc.Label("Weighting"),
                    dcc.Dropdown(
                        id='weight_dd',
                        value = weight_dd,
                        clearable=False,
                    ),
                ]
            ),
            dbc.FormGroup(
                [dbc.Label("Crop"),
                    dcc.Dropdown(
                        id='crop_dd',
                        value = crop_dd,
                        clearable=False,
                    ),
                ]
            ),          
            dbc.FormGroup(
                [dbc.Label("Forecast Variable"),
                    dcc.Dropdown(
                        id='columns_dd',
                        value = columns_dd,
                        options = make_options(['time', 'Temp','Precip']),
                        clearable=False,
                    ),
                ]
            ),
        ]
        self.assign_index()

    def assign_index(self): # unused: just for an idea 
        self.idx = {} 
        for i, item in enumerate(self.container): 
            if hasattr(item.children[1], 'id'): # skip the label at 0
                self.idx[item.children[1].id] = i

    def update_options(self, varname, options, selected=0):  # unused: just for an idea 
        pos = self.idx[varname]
        print(self.container[pos].children[1])
        if hasattr(self.container[pos].children[1],'id'):
            setattr(self.container[pos].children[1],'options', options)
            setattr(self.container[pos].children[1],'value', options[selected].get('value'))


# UI #####################################################################
controls = dbc.Card(
    html.Div(
        id='dynamic-input-container',
        children = InputContainer().container),
    body=True,
)


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

#set the model
@app.callback(
    [Output('model_dd', 'options'),
     Output('model_dd', 'value')],
    [Input('init_dd', 'value')],
    )
def update_model_options(input_dd):
    print('update_model_options():')
    options = make_options(get_dict_keys([input_dd]).keys())
    return options, options[0].get('value')

#set the weight
@app.callback(
    [Output('weight_dd', 'options'),
    Output('weight_dd', 'value')],
    [Input('model_dd', 'value')],
    [State('init_dd', 'value')])
def update_weight_options(model_dd, input_dd):
    print('update_weight_options():')
    options = make_options(get_dict_keys([input_dd, model_dd]).keys())
    return options, options[0].get('value')

#set the crop
@app.callback(
    [Output('crop_dd', 'options'),
    Output('crop_dd', 'value')],
    [Input('weight_dd', 'value')],
    [State('init_dd', 'value'),
     State('model_dd', 'value')])
def update_crop_options(weight_dd, input_dd, model_dd):
    print('update_crop_options():')
    options = make_options(get_dict_keys([input_dd, model_dd, weight_dd]).keys())
    return options, options[0].get('value')

# #set the variable
# @app.callback(
#     [Output('columns_dd', 'options'),
#      Output('columns_dd','value')],
#     [Input('crop_dd', 'value')],
#     [State('init_dd', 'value'),
#      State('model_dd', 'value'),
#      State('weight_dd', 'value')])
# def set_column_options(crop_dd, input_dd, model_dd, weight_dd):
#     print('update_column_options():')
#     options = make_options(get_dict_keys([input_dd, model_dd, weight_dd, crop_dd]).columns[1:])
#     return options, options[0].get('value')

def make_plot(df, var):
    fig = go.Figure(
            data=[go.Scatter(x=df['time'], y=df[var], name=var)],
            layout={
                'yaxis': {'title': f'Plot of <b>{var}</b>'}
            }
        )
    return fig


no_data_fig = {"layout": {
        "xaxis": { "visible": False},
        "yaxis": {"visible": False},
        "annotations": [
            { "text": "",
                "xref": "paper",
                "yref": "paper",
                "showarrow": False,
                "font": {"size": 20 }
            }]
        }
    }

@app.callback(
    Output('crop-graph', 'figure'),
    [Input('init_dd', 'value'),
     Input('model_dd', 'value'),
     Input('weight_dd', 'value'),
     Input('crop_dd', 'value'),
     Input('columns_dd','value')]
    )
def plot_data(init_dd, model_dd, weight_dd, crop_dd, columns_dd):
    if crop_dd is None or weight_dd is None or model_dd is None or columns_dd is None: return None
    print('plot_data():', init_dd, model_dd, weight_dd, crop_dd, columns_dd)
    try:
        data = final_dict[init_dd][model_dd][weight_dd][crop_dd]
        data_col = data[columns_dd]
    except:
        return no_data_fig
    return make_plot(data, columns_dd)


app.run_server(mode='external', port = 8098, debug=True)