python - 防止链式回调将下拉菜单切换为原始值破折号

标签 python plotly plotly-dash

我有一段很长的代码用于来自多个嵌套字典的几个链式回调。我有我想提供的所有必要的下拉菜单和选项。但是,每当我将本示例中的“裁剪”下拉列表更改为原始选项(即 Jade 米)以外的其他选项时,它会重置下方的“权重”下拉列表。同样,如果我更改“权重”下拉列表,它会将“预测变量”下拉列表重置为原始选项。我怎样才能防止这种情况?链式回调的重点是改变一个选项会改变绘制的数据,因为它们都是链接的。
我认为这里的数据不重要?但它的功能是这样的:

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)   
编辑:在示例虚拟数据中添加。请注意在更改某些选项组合时,其他选项如何切换回原始值。想防止这种情况发生。

最佳答案

具体的数据示例有所帮助。我看到

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

  • 关于更好地控制链式回调的问题,我认为这是使用 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)   
    

    关于python - 防止链式回调将下拉菜单切换为原始值破折号,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64269222/

    相关文章:

    r - 如何为 R shiny App 连续旋转 3D Plotly

    r - 在 Shiny 的应用程序上,ggplotly() 渲染的大小是 plot_ly() 的一半。如何解决?

    python - Django where in queryset 在 url 中用逗号分隔 slug

    python pandas dataframe head() 什么都不显示

    python - Plotly:如何使用下拉菜单选择图形源?

    python - 导入 Dash : "ImportError: DLL load failed while importing _brotli: The specified module could not be found." 时出错

    python - 本地 HTML 文件无法正确加载到 Dash 应用程序中

    python - 使用破折号下拉列表过滤 pd.DataFrame

    python - Python 中单个列表上的 n 折笛卡尔积

    python - 如何在 Python 中使用 BeautifulSoup 提取标签内的文本?