pythoncallbackplotlyplotly-dash

Dash Python: Linking tabs through @callback


This question is an extension of the question linked below:

Dash Python: @Callback in Layout function not being called

I have a simple dataframe:

import pandas as pd
df = pd.DataFrame({'Class1': [1, 2, 3, 4, 5],
                   'Class2': [6, 7, 8, 9, 10]}
)  

I have created a data extraction function that splits the data based on the position of the column.

data_extraction.py

def dataExtraction(arg1):

    df = pd.DataFrame({
        'Class1': [1, 2, 3, 4, 5],
        'Class2': [6, 7, 8, 9, 10]})  # <-- or Import df from somewhere
    df = df[[f'Class{arg1}']]

    return df   

tab_page.py

import layout as lay
import data_extraction as de
import dash_bootstrap_components as dbc
from dash import html, Dash
import dash

app = Dash()

dash.register_page(__name__, path='/tabs') 

def get_layout(position):
    df = de.dataExtraction(position)
    layout = lay.update_page(position, df)
    return layout

tab1_content = dbc.Card(
    dbc.CardBody([get_layout(1)]),
    className="mt-1")

tab2_content = dbc.Card(
    dbc.CardBody([get_layout(2)]),
    className="mt-2")

layout = html.Div(children = [dbc.Tabs([
    dbc.Tab(tab1_content, label="1",activeLabelClassName="text-success"),
    dbc.Tab(tab2_content, label="2",activeLabelClassName="text-success")])])
    
app.layout = [layout]

lay.create_callback(1)
lay.create_callback(2)  

if __name__ == '__main__':
    app.run(debug=True)

layout.py

from dash import dcc, html, Input, Output, callback
from dash import Dash, dash_table, State
import dash_daq as daq
import pandas as pd

def update_page_test(arg1, arg2):
    layout = html.Div(children=[
        html.H1(f'Class {arg1}'),
        daq.NumericInput(
            id=f'numericinput{arg1}',
            min=0,
            max=100,
            value=0, ), 
        html.Br(),
        dash_table.DataTable(
            id=f'tableTest{arg1}',
            data=arg2.to_dict('records'),
            columns=[{"name": i, "id": i} for i in arg2.columns]),

        dcc.Store(id=f'{arg1}mystore', data=arg2.to_dict('records'),
                  storage_type='memory'),
    ])

    return layout

def create_callback(i):

    @callback(
        Output(f'tableTest{i}', 'data'),
        Input(f'numericinput{i}', 'value'),
        State(f'tableTest{i}', 'data'),
        State(f'{i}mystore', 'data'),
    )
    def updateTableTest(x,data, Orig):
    
        data = pd.DataFrame(data)
        Orig = pd.DataFrame(Orig)

        if x > 0:

            print(f'Callback for class {i} works')

            data.iloc[:,1:] = Orig.iloc[:,1:]*x

            return data.to_dict('records')
        return Orig.to_dict('records')

    return updateTableTest

I want to construct a tab page that consists of a page with 3 tabs. Something like this:

...

total_content = dbc.Card(
    dbc.CardBody(
        [
            html.Div([
                html.Div(id='table-placeholder', children=[])
            ], className='row')
        ]
    ),
    className="mt-3",
)


layout = html.Div(children = [

    dbc.Tabs(
    [
        dbc.Tab(tab1_content, label="1",activeLabelClassName="text-success"),
        dbc.Tab(tab2_content, label="2",activeLabelClassName="text-success"),
        dbc.Tab(total_content, label="total",activeLabelClassName="text-success"),

    ]
),

])

Question: How would I be able to update the layout.py section so that the total tab updates with data from the sum of the other tabs and when the Numeric Input boxes are adjusted the total also updates?

For example if Tab 1: [1,2,3] Tab 2: [4,5,6] -> Total: [5,7,9]. If the numeric input box was set at 2 in Tab 1 the result will be Tab 1: [2,4,6] Tab 2: [4,5,6] -> Total: [6,9,12]

I was thinking of using another callback option that updates the Stored data in each tab and then outputting it in to the total tab but I am not sure how that would work. I have tried this and I have got stuck:

def update_store(i):
    @callback(
        Output(f'{i}mystore', 'data'),
        Input(f'tableTest{i}', 'data'),
    )
    def Update(data):
        print(f'Callback update store {i} works')
        print(data)
        return data
    return Update

def output_total(i):
    @callback(
        Output('table-placeholder', 'children'),
        State(f'{i}mystore', 'data'),
    )
    def create_total_table(data):
        print(f'Callback of create Total {i} works')
        return data
    return create_total_table

Solution

  • As mentioned above ensure that the store in the callback is updated when ever the numeric input is updated:

    def update_store(i):
        @callback(
            Output(f'{i}mystore', 'data'),
            Input(f'tableTest{i}', 'data'),
        )
        def Update(data):
            print(f'Callback update store {i} works')
            return data
        return Update
    

    I made some slight tweaks to the above. it is the stores that need to be inputted and the output id needs to be unique.

    def update_total(i):
        @callback(
            Output(f'{i}table-placeholder', 'children'),
           [Input('1mystore', 'data')],
            [Input('2mystore', 'data')]
        )
        def update_total_table(tab1_data, tab2_data):
            print("Callback works")
            df_tab1 = pd.DataFrame(tab1_data)
    
            df_tab2 = pd.DataFrame(tab2_data)
    
            total_df = df_tab1.add(df_tab2, fill_value=0) # Sum columns from both tabs
    
            total_table = dash_table.DataTable(
                id='total-table',
                data=total_df.to_dict('records'),
                columns=[{"name": i, "id": i} for i in total_df.columns]
            )
    
            return total_table
        return update_total_table
    

    In tab_page.py

    ...
    
    total_content = dbc.Card(
        dbc.CardBody(
            [
                html.Div([
                    html.Div(id='1table-placeholder', children=[])
                ], className='row')
            ]
        ),
        className="mt-3",
    )
    
    
    layout = html.Div(children = [
    
        dbc.Tabs(
        [
            dbc.Tab(tab1_content, label="1",activeLabelClassName="text-success"),
            dbc.Tab(tab2_content, label="2",activeLabelClassName="text-success"),
            dbc.Tab(total_content, label="total",activeLabelClassName="text-success"),
        ]
    ),
    ])
    
    lay.update_total(1)