How to synchronize some properties of two tables?

Dear all,

I’m new to Dash Plotly as well as python.
I have a question regarding place to a dataframe into two tables but the properties such as names and positions must be synchronized.

For example, in below figure, I have Tables 1 and 2. When I change the column names in Table 1 and they will immediately rename the respective rows in Table 2 and more importantly VICE VERSA.
Is there any clever solution to deal with this problem in Dash?

Infact, I can rename columns/rows in one table and the other will follow what have changed. However, I can have both directions.
Cheers!

Hi @addsc6
Can you show us the code you have so far with the data, so we can run it on our computer?

Here I prepared an example with sample dataframe and the result below.

from dash import Input, Output, State, Dash, dash_table, html
import pandas as pd

app = Dash(__name__)

# Example of a dataset below
df = pd.DataFrame({'A': [1, 2, 3, 12, 29],
                   'B': [4, 5, 6, 13, 21],
                   'C': [7, 8, 9, 14, 23],
                   'D': [4, 3, 1, 11, 13],
                   })
# The columns and data below are used for Table 1
columns = [
    {"name": col, "id": col, "deletable": True, "renamable": True}
    for col in df.columns
]
data = df.to_dict('records')

app.layout = html.Div(
    style={"width": "400px", "height": "300px"},
    children=[
        html.H3('Table 1'),
        dash_table.DataTable(
            id='table_1',
            editable=True,
            columns=columns,
            data=data,
            row_deletable=True,
            column_selectable="multi",
            selected_columns=[],
            style_cell={'textAlign': 'center'},
        ),
        html.Hr(),
        html.H3('Table 2'),
        dash_table.DataTable(
            id="table_2",
            selected_cells=[],
            editable=True,
            row_selectable="multi",
            selected_rows=[],
        ),
    ]
)
# A callback function to list column names of Table 1 in Table 2
@app.callback(
    Output("table_2", "columns"),
    Output("table_2", "data"),
    Input("table_1", "columns"),
)
def update_table2(table1_columns):
    table1_col_names = [col["name"] for col in table1_columns]
    data_for_table2 = [
        {"sequence": i+1,
         "column_names": col_name}
        for i, col_name in enumerate(table1_col_names[0:])
    ]
    columns_for_table2 = [
        {"name": "Sequence", "id": "sequence"},
        {"name": "Name", "id": "column_names"}
    ]

    return columns_for_table2, data_for_table2

if __name__ == "__main__":
    app.run_server(debug=True)


I don’t know why the code above doesn’t look well.
Here is the link to download it too:
Python code

Hi, I chnaged the code formatting for you. I think you tried to use Blockquote, I changed to Preformatted text, the button right next to the Blockquote button.

1 Like

Got it! It’s hidden in the setting button :grin:
Many thanks!

1 Like

HI @addsc6
We’re very close. I have to jump off for meetings, but we just have to figure out lines 104 to 106.

Right now the solution is static and only applicable for one example. Try to update the first row of table 2 to “AA” and you’ll see the first table change accordingly.

We have to make this dynamic. We have to identify which table1 col name to update, based on the row id that we updated in table 2.

from dash import Input, Output, State, Dash, dash_table, html, no_update, ctx
import pandas as pd

app = Dash(__name__)

# Example of a dataset below
df = pd.DataFrame({'A': [1, 2, 3, 12, 29],
                   'B': [4, 5, 6, 13, 21],
                   'C': [7, 8, 9, 14, 23],
                   'D': [4, 3, 1, 11, 13],
                   })
# The columns and data below are used for Table 1
columns = [
    {"name": col, "id": col, "deletable": True, "renamable": True}
    for col in df.columns
]
data = df.to_dict('records')

# data, cols for table 2
table1_cols = [col["name"] for col in columns]
data_table2 = [
    {"sequence": i + 1,
     "column_names": col_name}
    for i, col_name in enumerate(table1_cols[0:])
]

columns_table2 = [
    {"name": "Sequence", "id": "sequence"},
    {"name": "Name", "id": "column_names"}
]

app.layout = html.Div(
    style={"width": "400px", "height": "300px"},
    children=[
        html.H3('Table 1'),
        dash_table.DataTable(
            id='table_1',
            editable=True,
            columns=columns,
            data=data,
            row_deletable=True,
            column_selectable="multi",
            selected_columns=[],
            style_cell={'textAlign': 'center'},
        ),
        html.Hr(),
        html.H3('Table 2'),
        dash_table.DataTable(
            id="table_2",
            data=data_table2,
            columns=columns_table2,
            selected_cells=[],
            editable=True,
            row_selectable="multi",
            selected_rows=[],
        ),
    ]
)
# A callback function to list column names of Table 1 in Table 2
@app.callback(
    Output("table_2", "columns"),
    Output("table_2", "data"),
    Output("table_1", "columns"),
    Output("table_1", "data"),
    Input("table_1", "columns"),
    Input("table_2", "data"),
    Input("table_1", "data"),
)
def update_table2(table1_columns, table2_data, table1_data):
    table_modified = ctx.triggered_id
    if table_modified == "table_1":
        table1_col_names = [col["name"] for col in table1_columns]
        data_for_table2 = [
            {"sequence": i + 1,
             "column_names": col_name}
            for i, col_name in enumerate(table1_col_names[0:])
        ]
        columns_for_table2 = [
            {"name": "Sequence", "id": "sequence"},
            {"name": "Name", "id": "column_names"}
        ]

        return columns_for_table2, data_for_table2, no_update, no_update

    elif table_modified == "table_2":
        table2_col_names = [d['column_names'] for d in table2_data]
        # print(table2_col_names)

        columns_for_table1 = [
            {"name": col, "id": col, "deletable": True, "renamable": True}
            for col in table2_col_names
        ]
        # print(columns_for_table1)

        # write code here
        # we need to figure out how to take the data modified in table 2 and
        # update the column names of table1 as well as its data

        df_table1_data = pd.DataFrame(table1_data)          # Convert the data of table 1 back to DataFrame
        table1_old_names = df_table1_data.columns           # Get column names of table1 data
        print(columns_for_table1[0])
        print(table1_old_names)
        df_table1_data.rename(columns={"A": "AA"}, inplace=True)
        # print(df_table1_data)

        return no_update, no_update, columns_for_table1, df_table1_data.to_dict('records')

    else:
        return no_update

if __name__ == "__main__":
    app.run_server(debug=True)

Many thanks!
Based on your idea, I have added some more lines that we can rename some other column names.
However, I think the data and column names of Table 1 aren’t actually changed due to the fact that this Table is always fed by the initial data (now I have changed to global data). I think we need a shared virtual table where its data will be pumped only once from the beginning and will be updated whatever we change data/column names in Table 1 or 2.
Ps: it’s mid night now, I’ll check it tomorrow.

from dash import Input, Output, State, Dash, dash_table, html, no_update, ctx
import pandas as pd

app = Dash(__name__)

# Example of a dataset below
global_df = pd.DataFrame({'A': [1, 2, 3, 12, 29],
                   'B': [4, 5, 6, 13, 21],
                   'C': [7, 8, 9, 14, 23],
                   'D': [4, 3, 1, 11, 13],
                   })
# The columns and data below are used for Table 1
global_columns = [
    {"name": col, "id": col, "deletable": True, "renamable": True}
    for col in global_df.columns
]
data = global_df.to_dict('records')

# data, cols for table 2
table1_cols = [col["name"] for col in global_columns]
data_table2 = [
    {"sequence": i + 1,
     "column_names": col_name}
    for i, col_name in enumerate(table1_cols[0:])
]

columns_table2 = [
    {"name": "Sequence", "id": "sequence"},
    {"name": "Name", "id": "column_names"}
]

app.layout = html.Div(
    style={"width": "400px", "height": "300px"},
    children=[
        html.H3('Table 1'),
        dash_table.DataTable(
            id='table_1',
            editable=True,
            columns=global_columns,
            data=data,
            row_deletable=True,
            column_selectable="multi",
            selected_columns=[],
            style_cell={'textAlign': 'center'},
        ),
        html.Hr(),
        html.H3('Table 2'),
        dash_table.DataTable(
            id="table_2",
            data=data_table2,
            columns=columns_table2,
            selected_cells=[],
            editable=True,
            row_selectable="multi",
            selected_rows=[],
        ),
    ]
)
# A callback function to list column names of Table 1 in Table 2
@app.callback(
    Output("table_2", "columns"),
    Output("table_2", "data"),
    Output("table_1", "columns"),
    Output("table_1", "data"),
    Input("table_1", "columns"),
    Input("table_2", "data"),
    Input("table_1", "data"),
)
def update_table2(table1_columns, table2_data, table1_data):
    table_modified = ctx.triggered_id
    if table_modified == "table_1":
        table1_col_names = [col["name"] for col in table1_columns]
        data_for_table2 = [
            {"sequence": i + 1,
             "column_names": col_name}
            for i, col_name in enumerate(table1_col_names[0:])
        ]
        columns_for_table2 = [
            {"name": "Sequence", "id": "sequence"},
            {"name": "Name", "id": "column_names"}
        ]

        return columns_for_table2, data_for_table2, no_update, no_update

    elif table_modified == "table_2":
        table2_col_names = [d['column_names'] for d in table2_data]
        # print(table2_col_names)

        columns_for_table1 = [
            {"name": col, "id": col, "deletable": True, "renamable": True}
            for col in table2_col_names
        ]
        # print(columns_for_table1)

        # write code here
        # we need to figure out how to take the data modified in table 2 and
        # update the column names of table1 as well as its data

        df_table1_data = pd.DataFrame(table1_data)          # Convert the data of table 1 back to DataFrame
        table1_old_names = df_table1_data.columns           # Get column names of table1 data
        total_length = len(table1_old_names)
        # print("New Name: ", "'" + columns_for_table1[0]['name'] + "'")
        # print("Old Name: ", "'" + table1_old_names[0] + "'")
        # print("length: ", total_length)
        print("Before:", df_table1_data.head())
        for i in range(total_length):
            new_name = "'" + columns_for_table1[i]['name'] + "'"
            old_name = "'" + table1_old_names[i] + "'"

            df_table1_data.rename(columns={old_name: new_name}, inplace=True)

        print("After:", df_table1_data.head())
        return no_update, no_update, columns_for_table1, df_table1_data.to_dict('records')

    else:
        return no_update

if __name__ == "__main__":
    app.run_server(debug=True)
1 Like

you got this, @addsc6 . You just had to remove the wrapping quotation marks you added in lines 107-108.

This is your code:

new_name = "'" + columns_for_table1[i]['name'] + "'"
old_name = "'" + table1_old_names[i] + "'"

The new code should be:

new_name = columns_for_table1[i]['name']
old_name = table1_old_names[i]
1 Like

Indeed, it worked! Thanks alot Adam!!

Here is a final version that migh be interesting for other people.
Note: If we rename two columns with a similar name, the data of the old name will be erased!!

from dash import Input, Output, State, Dash, dash_table, html, no_update, ctx
import pandas as pd

app = Dash(__name__)

# Example of a dataset below
global_df = pd.DataFrame({'A': [1, 2, 3, 12, 29],
                          'B': [4, 5, 6, 13, 21],
                          'C': [7, 8, 9, 14, 23],
                          'D': [4, 3, 1, 11, 13],
                          })
# The columns and data below are used for Table 1
global_columns = [
    {"name": col, "id": col, "deletable": True, "renamable": True}
    for col in global_df.columns
]
data = global_df.to_dict('records')

# data, cols for table 2
table1_cols = [col["name"] for col in global_columns]
data_table2 = [
    {"sequence": i + 1,
     "column_names": col_name}
    for i, col_name in enumerate(table1_cols[0:])
]

columns_table2 = [
    {"name": "Sequence", "id": "sequence"},
    {"name": "Name", "id": "column_names"}
]

app.layout = html.Div(
    style={"width": "400px", "height": "300px"},
    children=[
        html.H3('Table 1'),
        dash_table.DataTable(
            id='table_1',
            editable=True,
            columns=global_columns,
            data=data,
            row_deletable=True,
            column_selectable="multi",
            selected_columns=[],
            style_cell={'textAlign': 'center'},
        ),
        html.Hr(),
        html.H3('Table 2'),
        dash_table.DataTable(
            id="table_2",
            data=data_table2,
            columns=columns_table2,
            selected_cells=[],
            editable=True,
            row_selectable="multi",
            selected_rows=[],
        ),
    ]
)
# A callback function to list column names of Table 1 in Table 2
@app.callback(
    Output("table_2", "columns"),
    Output("table_2", "data"),
    Output("table_1", "columns"),
    Output("table_1", "data"),
    Input("table_1", "columns"),
    Input("table_2", "data"),
    Input("table_1", "data"),
)
def update_table2(table1_columns, table2_data, table1_data):
    table_modified = ctx.triggered_id
    print(table_modified)
    if table_modified == "table_1":
        table1_col_names = [col["name"] for col in table1_columns]
        data_for_table2 = [
            {"sequence": i + 1,
             "column_names": col_name}
            for i, col_name in enumerate(table1_col_names[0:])
        ]
        columns_for_table2 = [
            {"name": "Sequence", "id": "sequence"},
            {"name": "Name", "id": "column_names"}
        ]

        return columns_for_table2, data_for_table2, no_update, no_update

    elif table_modified == "table_2":
        table2_col_names = [d['column_names'] for d in table2_data]

        columns_for_table1 = [
            {"name": col, "id": col, "deletable": True, "renamable": True}
            for col in table2_col_names
        ]
        df_table1_data = pd.DataFrame(table1_data)          # Convert the data of table 1 back to DataFrame
        table1_old_names = df_table1_data.columns           # Get column names of table1 data
        total_length = len(table1_old_names)                # Get the total number of columns
        for i in range(total_length):
            new_name = columns_for_table1[i]["name"]
            old_name = table1_old_names[i]
            df_table1_data.rename(columns={old_name: new_name}, inplace=True)   # Rename the columns of data

        return no_update, no_update, columns_for_table1, df_table1_data.to_dict('records')

    else:
        return no_update

if __name__ == "__main__":
    app.run_server(debug=True)
2 Likes