Обработка обратного вызова тире ввода внутри приложения с несколькими вкладками

Я пытаюсь создать приложение с несколькими вкладками на основе справочного кода из https://dash-bootstrap-components.opensource.faculty.ai/examples/graphs-in-tabs/#sourceCode

Мое требование - иметь кнопку внутри страницы вкладки гистограммы, а не на главной странице. Для этого я изменил ссылочный код. Ниже приведен измененный код.

Закомментировано из app.layout и перемещено dbc.Button внутри вкладки в качестве первого столбца (модификация обратного вызова):

@app.callback(
    Output("tab-content", "children"),
    [Input("tabs", "active_tab"), Input("store", "data")],
)
def render_tab_content(active_tab, data):
    """
    This callback takes the 'active_tab' property as input, as well as the
    stored graphs, and renders the tab content depending on what the value of
    'active_tab' is.
    """
    if active_tab and data is not None:
        if active_tab == "scatter":
            return dcc.Graph(figure=data["scatter"])
        elif active_tab == "histogram":
                return dbc.Row(
                    [
                 #Modified to add the Button inside the Tab
                        dbc.Col(dbc.Button(
                        "Regenerate graphs",
                        color="primary",
                        block=True,
                        id="iButton",
                        className="mb-3",
                        ), width=4
                    ),
                    dbc.Col(dcc.Graph(figure=data["hist_1"]), width=4),
                    dbc.Col(dcc.Graph(figure=data["hist_2"]), width=4),
                ]
            )
    return "No tab selected"

2-е изменение, как показано ниже, обновило iButton в качестве входных данных в обратном вызове:

@app.callback(Output("store", "data"), [Input("iButton", "n_clicks")])
def generate_graphs(n):

С этим изменением возникает исключение при запуске приложения.

ID not found in layout

Attempting to connect a callback Input item to component:
  "iButton"
but no components with that id exist in the layout.

Я не уверен, как обрабатывать поведение кнопки, когда она находится внутри вкладки.


person hamlet    schedule 05.05.2021    source источник


Ответы (1)


Когда dash сначала оценивает приложение, оно пытается разрешить входные данные обратного вызова, используя компоненты макета в àpp.layout. Кнопка не может быть найдена, потому что она добавляется позже в ваш обратный вызов, поэтому Dash еще не знает об этом.

Я бы порекомендовал немного реструктурировать макет и обратные вызовы, чтобы каждый компонент был известен с самого начала.

Макет

Заранее определите каждый компонент макета. Определите каждый Div, но не показывайте их оба в начале

app.layout = dbc.Container(
    [
        html.H1("Dynamically rendered tab content"),
        html.Hr(),
        dbc.Tabs(
            [
                dbc.Tab(label = "Scatter", tab_id = "scatter"),
                dbc.Tab(label = "Histograms", tab_id = "histogram"),
            ],
            id = "tabs",
            active_tab = "scatter",
        ),
        html.Div(id = "hist-tab", className = "p-4", children = [
            dbc.Row(
                [
                    # Modified to add the Button inside the Tab
                    dbc.Col(dbc.Button(
                        "Regenerate graphs",
                        color = "primary",
                        block = True,
                        id = "iButton",
                        className = "mb-3",
                    ), width = 4
                    ),
                    dbc.Col(dcc.Graph(id = 'hist-1', figure = {}), width = 4),
                    dbc.Col(dcc.Graph(id = 'hist-2', figure = {}), width = 4)
                ]
            )
        ]),
        html.Div(id = "scatter-tab", className = "p-4", children = [
            dcc.Graph(figure = {}, id = 'scatter-plot')
        ], style = {'display': 'none'}),
    ])

Установка стиля Div на {'display': 'block'} отобразит Div, установка на {'display': 'none'} пока скроет его.

Обратные вызовы

Первый обратный вызов, прослушивающий Tab, будет отвечать за то, чтобы сделать Divs видимыми и невидимыми:

@app.callback(
    [Output("hist-tab", "style"), Output("scatter-tab", "style")],
    [Input("tabs", "active_tab")],
)
def render_tab_content(active_tab):
    """
    This callback takes the 'active_tab' property as input, as well as the
    stored graphs, and renders the tab content depending on what the value of
    'active_tab' is.
    """
    on = {'display': 'block'}
    off = {'display': 'none'}
    if active_tab is not None:
        if active_tab == "scatter":
            return off, on
        elif active_tab == "histogram":
            return on, off
    return "No tab selected"

Поскольку каждый компонент существует в каждой точке, второй обратный вызов может напрямую записывать данные в Figures

@app.callback([Output("hist-1", "figure"), Output("hist-2", "figure"), Output("scatter-plot", "figure")],
              [Input("iButton", "n_clicks")])
def generate_graphs(n):
    """
    This callback generates three simple graphs from random data.
    """
    if not n:
        # generate empty graphs when app loads
        return {}, {}, {}

    # simulate expensive graph generation process
    time.sleep(2)

    # generate 100 multivariate normal samples
    data = np.random.multivariate_normal([0, 0], [[1, 0.5], [0.5, 1]], 100)

    scatter = go.Figure(
        data = [go.Scatter(x = data[:, 0], y = data[:, 1], mode = "markers")]
    )
    hist_1 = go.Figure(data = [go.Histogram(x = data[:, 0])])
    hist_2 = go.Figure(data = [go.Histogram(x = data[:, 1])])

    # save figures in a dictionary for sending to the dcc.Store
    return hist_1, hist_2, scatter
person ConstantinB    schedule 07.05.2021