Change column order with a callback in Dash Ag Grid (dag, columnDefs and columnState)

Hi all! This is going to be more of a comment than a question but I wanted to share it in case it’s useful for somebody else :slight_smile:

Use case: changing grid columns and their order programatically, that is, with a callback instead of with user interaction.

Environment: dash-ag-grid==2.0.0rc2

Main takeaways:

  • It’s necessary to update both columnDefs and columnState for the change in columnDefs to be effective; otherwise we need to trigger the callback twice:
# this works
  @callback(
      Output("grid4", 'columnDefs'),
      Output("grid4", "columnState"),
      Input('b2', 'n_clicks'),
      prevent_initial_call=True
  )
  def update_grids_cols_4(_) :
      return columnDefs2, []
  • We could also target resetColumnState as Output, but in that case updating it at the same time/in the same callback as columnDefs doesn’t work, we would need to use a chained callback:
# this works too, but it's less efficient
@callback(
    Output("grid3", 'columnDefs'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grids_cols(_) :
    return columnDefs2

@callback(
    Output("grid3", "resetColumnState"),
    Input("grid3", 'columnDefs'),
    prevent_initial_call=True
)
def update_grids_cols(_) :
    return True
  • If you want to learn more about columnState: Dash

Full code to replicate the issue and test things!:

import dash_ag_grid as dag
from dash import Dash, html, dcc, callback, Input, Output
import pandas as pd

df = pd.read_csv("https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv")

app = Dash(__name__)

columnDefs1 = [
    {"field": "athlete"},
    {"field": "age"},
    {"field": "country"},
    {"field": "sport"},
]

columnDefs2 = [
    {'field':'year'},
    {"field": "age"},
    {"field": "sport"},
    {"field": "athlete"},
    {"field": "country"},
]

def grid(colDefs, idGrid=''):
    return dag.AgGrid(
        id=idGrid,
        columnDefs=colDefs,
        rowData=df.to_dict("records"),
        style={'height':'200px'}
    )

grid1 = grid(columnDefs1)
grid2 = grid(columnDefs2)
grid3 = grid(columnDefs1,"grid3")
grid4 = grid(columnDefs1,"grid4")

app.layout = html.Div(
    [
        html.Div("Start state"),
        grid1,
        html.Div("Desired end state"),
        grid2,
        html.Br(),
        dcc.Markdown("""
        **Actual behaviour:** 
        If we only use `columnDefs` as Output, we need two clicks to completely overwrite previous `columnDefs`, with only one click they are combined
        ```
        @callback(
            Output("grid3", 'columnDefs'),
            Input('b', 'n_clicks'),
            prevent_initial_call=True
        )
        def update_grids_cols(_) :
            return columnDefs2
        ```
        """),
        html.Button(id='b1', children='Change colDefs in 2 clicks!'),
        grid3,
        dcc.Markdown("""
        **Corrected behaviour:** 
        Using both `columnDefs` and `columnState`.
        ```
        @callback(
            Output("grid4", 'columnDefs'),
            Output("grid4", "columnState"),
            Input('b2', 'n_clicks'),
            prevent_initial_call=True
            )
        def update_grids_cols_4(_) :
               return columnDefs2, []
        ```
        """),
        html.Button(id='b2', children='Change colDefs in 1 click!'),
        grid4,
    ],
    style={"margin": 20},
)

@callback(
    Output("grid3", 'columnDefs'),
    Input('b1', 'n_clicks'),
    prevent_initial_call=True
)
def update_grids_cols_3(_) :
    return columnDefs2

@callback(
    Output("grid4", 'columnDefs'),
    Output("grid4", "columnState"),
    Input('b2', 'n_clicks'),
    prevent_initial_call=True
)
def update_grids_cols_4(_) :
    return columnDefs2, []

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