How does columnSize="autoSize" work when AgGrid/dag values are set with a callback?

Hi everyone! I’m using dash-ag-grid==2.0.0rc2 and I have an app where:

  • In the initial layout there’s an empty grid
  • I want users to be able to populate the grid through a callback
  • I want the grid’s columns to autosize based on the new data

However, I noticed that columnSize="autoSize" only works properly when both rowData and columnDefs are specified from the start. I include a sample app where different combinations are tested:

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"
)

rowData = df.to_dict("records")

app = Dash(__name__)

columnDefs = [
    {"field": "athlete"},
    {"field": "age"},
    {"field": "country"},
    {"field": "year"},
    {"field": "date"},
    {"field": "sport"},
    {
        "headerName": "Medals",
        "children": [
            {
                "columnGroupShow": "closed",
                "field": "total",
                "valueGetter": "data.gold + data.silver + data.bronze",
            },
            {"columnGroupShow": "open", "field": "gold"},
            {"columnGroupShow": "open", "field": "silver"},
            {"columnGroupShow": "open", "field": "bronze"},
        ],
    },
]

# all values set from the start
grid1 = dag.AgGrid(
    id='grid1',
    columnDefs=columnDefs,
    rowData=rowData,
    columnSize="autoSize",
    )

# no rowData
grid2 = dag.AgGrid(
    id='grid2',
    columnDefs=columnDefs,
    rowData=[],
    columnSize="autoSize",
    )

# no rowData nor columnDefs
grid3 = dag.AgGrid(
    id='grid3',
    columnDefs=[],
    rowData=[],
    columnSize="autoSize",
    )

# no rowData nor columnDefs nor columnSize
grid4 = dag.AgGrid(
    id='grid4',
    columnDefs=[],
    rowData=[],
    )

# rowData and columnDefs but no columnSize
grid5 = dag.AgGrid(
    id='grid5',
    columnDefs=columnDefs,
    rowData=rowData,
    )

# no rowData nor columnDefs nor columnSize, but with chained callbacks
grid6 = dag.AgGrid(
    id='grid6',
    columnDefs=[],
    rowData=[],
    )

app.layout = html.Div(
    [
        html.Button(id='b', children='Update grids!'),
        html.Div("Grid 1: All values set from the start - This output is ok"),
        grid1, html.Br(),
        html.Div("Grid 2: no rowData - Columns are not autosized, the first col is not wide enough"),
        grid2, html.Br(),
        html.Div("Grid 3: no rowData nor columnDefs - Columns are not autosized, the Age col is too wide"),
        grid3, html.Br(),
        html.Div("Grid 4: no rowData nor columnDefs nor columnSize - Columns are not autosized, the Age col is too wide"),
        grid4, html.Br(),
        html.Div("Grid 5: rowData and columnDefs but no columnSize - This output is ok"), 
        grid5, html.Br(),
        html.Div("Grid 6: like grid 4 (no values by default) but with chained callbacks - This output is ok"), 
        grid6
    ],
    style={"margin": 20, "width":"600px"},
)

@callback(
    Output("grid2", 'rowData'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid2(_) :
    return rowData

@callback(
    Output("grid3", 'rowData'),
    Output("grid3", 'columnDefs'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid3(_) :
    return rowData, columnDefs

@callback(
    Output("grid4", 'rowData'),
    Output("grid4", 'columnDefs'),
    Output("grid4", 'columnSize'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid4(_) :
    return rowData, columnDefs, "autoSize"

@callback(
    Output("grid5", 'columnSize'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid5(_) :
    return "autoSize"

@callback(
    Output("grid6", 'rowData'),
    Output("grid6", 'columnDefs'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid6_step1(_) :
    return rowData, columnDefs

@callback(
    Output("grid6", 'columnSize'),
    Input("grid6", 'rowData'),
    prevent_initial_call=True
)
def update_grid6_step2(_) :
    return "autoSize"

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

As you will see if you run the example, if I want to do that I explain at the beginning of the post, I need chained callbacks (Grid 6), setting the three properties (rowData, columnDefs and columnSize) at the same time/in the same callback doesn’t work.

Is this the expected behaviour? Thanks in advance!

4 Likes

Hello @celia,

In AG Grid, there is no columnSize attribute the way that we have it. For ease of use, we allow you to pass it as a prop.

What this does is to call the api and set the column size on the grid.

sizeToFit
autoSize

Which the exception of responsiveSizeToFit, once the columnSize is applied, we clear it out. This aids performance to keep the grid from constantly resizing. (cell value changes, column reordering/resizing would retrigger the API)

This works initially when the grid is first loaded (no chained callbacks) because there is a delay in which the columnSize prop and others are applied, this event is gridReady (because we cant apply things to the grid until it is ready). Since you are loading the grids before applying rowData and columnDefs, the grid is ready and has already applied the default columnSize (autoSize).

The assessment of using a chained callback is correct. I recommend using columnDefs rather than rowData as rowData can be changed through interaction with the grid, and columnDefs would be for the most part static.

Another thing you can do, is disable the button during loading and then use it as a confirmation that you are actually loading the info from the button click:

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


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

rowData = df.to_dict("records")

app = Dash(__name__)

columnDefs = [
    {"field": "athlete"},
    {"field": "age"},
    {"field": "country"},
    {"field": "year"},
    {"field": "date"},
    {"field": "sport"},
    {
        "headerName": "Medals",
        "children": [
            {
                "columnGroupShow": "closed",
                "field": "total",
                "valueGetter": "data.gold + data.silver + data.bronze",
            },
            {"columnGroupShow": "open", "field": "gold"},
            {"columnGroupShow": "open", "field": "silver"},
            {"columnGroupShow": "open", "field": "bronze"},
        ],
    },
]

defaultColDef = {'editable': True, 'resizable': True}

# all values set from the start
grid1 = dag.AgGrid(
    id='grid1',
    columnDefs=columnDefs,
    rowData=rowData,
    columnSize="autoSize",
    defaultColDef=defaultColDef
    )

# no rowData
grid2 = dag.AgGrid(
    id='grid2',
    columnDefs=columnDefs,
    rowData=[],
    columnSize="autoSize",
defaultColDef=defaultColDef
    )

# no rowData nor columnDefs
grid3 = dag.AgGrid(
    id='grid3',
    columnDefs=[],
    rowData=[],
    columnSize="autoSize",
defaultColDef=defaultColDef
    )

# no rowData nor columnDefs nor columnSize
grid4 = dag.AgGrid(
    id='grid4',
    columnDefs=[],
    rowData=[],
defaultColDef=defaultColDef
    )

# rowData and columnDefs but no columnSize
grid5 = dag.AgGrid(
    id='grid5',
    columnDefs=columnDefs,
    rowData=rowData,
    defaultColDef=defaultColDef
    )

# no rowData nor columnDefs nor columnSize, but with chained callbacks
grid6 = dag.AgGrid(
    id='grid6',
    columnDefs=[],
    rowData=[],
    defaultColDef=defaultColDef
    )

app.layout = html.Div(
    [
        html.Button(id='b', children='Update grids!'),
        html.Div("Grid 1: All values set from the start - This output is ok"),
        grid1, html.Br(),
        html.Div("Grid 2: no rowData - Columns are not autosized, the first col is not wide enough"),
        grid2, html.Br(),
        html.Div("Grid 3: no rowData nor columnDefs - Columns are not autosized, the Age col is too wide"),
        grid3, html.Br(),
        html.Div("Grid 4: no rowData nor columnDefs nor columnSize - Columns are not autosized, the Age col is too wide"),
        grid4, html.Br(),
        html.Div("Grid 5: rowData and columnDefs but no columnSize - This output is ok"),
        grid5, html.Br(),
        html.Div("Grid 6: like grid 4 (no values by default) but with chained callbacks - This output is ok"),
        grid6
    ],
    style={"margin": 20, "width":"600px"},
)

@callback(
    Output('b', 'disabled'),
    Input('b', 'n_clicks')
)
def setDisable(n):
    if n:
        return True
    return False

@callback(
    Output("grid2", 'rowData'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid2(_) :
    return rowData

@callback(
    Output("grid3", 'rowData'),
    Output("grid3", 'columnDefs'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid3(_) :
    return rowData, columnDefs

@callback(
    Output("grid4", 'rowData'),
    Output("grid4", 'columnDefs'),
    Output("grid4", 'columnSize'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid4(_) :
    return rowData, columnDefs, "autoSize"

@callback(
    Output("grid5", 'columnSize'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid5(_) :
    return "autoSize"

@callback(
    Output("grid6", 'rowData'),
    Output("grid6", 'columnDefs'),
    Input('b', 'n_clicks'),
    prevent_initial_call=True
)
def update_grid6_step1(_) :
    return rowData, columnDefs

@callback(
    Output("grid6", 'columnSize'),
    Output('b', 'disabled', allow_duplicate=True),
    Input("grid6", 'columnDefs'),
    State('b', 'disabled'),
    prevent_initial_call=True
)
def update_grid6_step2(_, d) :
    if d:
        return "autoSize", False

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

@celia @ @jinnyzor

Great question with excellent minimal example. And great answer! I’ll update the docs with this info too.

Our objective is to keep the dash-ag–grid API as close as possible to the upstream AG Grid component’s API. But there are some differences that are necessary to make things work with Dash or to improve performance when AG Grid is in a Dash app. This background information on the differences is helpful.

3 Likes