Restoring AG Grid columns after dragging from view

I’m following the Suppress Hide Leave and Lock Visible docs which has an example of restoring hidden columns by modifying AG Grid’s columnState. In the docs, the example callback returns [{'colId': col['colId'], 'hide': False} for col in column_state] on button click.

When I do this, the hide prop doesn’t change and the hidden columns that have been dragged away aren’t restored to the grid. Is there anything I might be missing? Thanks!

@app.callback(
    Output("my-grid", "columnState"),
    Input("show-all-cols-btn", "n_clicks"),
    State("my-grid", "columnState"),
    prevent_initial_call=True,
)
def col_moving_pin_set(_, column_state):
    return [{'colId': col['colId'], 'hide': False} for col in column_state]

Hello @nlyle,

What version of Dash AG Grid are you using?

We are on 2.3 now.

Hm, I was using 2.1.0 but I just tried 2.3 without any luck.

Can you possibly provide more of your info?

This should still be working, haha.

Sure :smile: I have a working AG Grid where I’ve set suppressDragLeaveHidesColumns=False, so users can drag columns out of the grid. To restore those, I’m attempting to follow this example.

Here’s my exact callback. On click, the grid doesn’t change.

@callback(
    Output("data-grid", "columnState"),
    Input("all-columns-button", "n_clicks"),
    State("data-grid", "columnState"),
    prevent_initial_call=True,
)
def show_all_columns(_, column_state):
    return [{'colId': col['colId'], 'hide': False} for col in column_state]

My grid looks like this:

dag.AgGrid(
    id="data-grid",
    columnDefs=columnDefs,
    defaultColDef=defaultColDef,
    dashGridOptions={
        "skipHeaderOnAutoSize": True,
        "rowSelection": "multiple",
        "pagination": True,
        "paginationPageSize": 20,
        "enableCellTextSelection": True,
        "ensureDomOrder": True,
        "tooltipShowDelay": 100,
    },
    rowData=df,
    columnSize="responsiveSizeToFit",
    suppressDragLeaveHidesColumns=False,
    style={"height": "530px", "width": "100%"}
)

I initialize the columnDefs on some preset values, where some of the columns are "hide": True, but I don’t think that overrides the callback.

My default column defs are:

defaultColDef = {
    "filter": True,
    "resizable": True,
    "sortable": True,
    "editable": False,
    "enableRowGroup": True,
}

I’m thinking about having a separate callback that modifies my DataFrame before returning it to the grid, in which case I could set which columns are returned based on some UI input component. So that might work as an alternative (for column selection).

Hmm, maybe try doing a uninstall and then reinstall it.

1 Like

I’m taking a different approach now, but running into an issue where I’m not able to iterate through columnState.

I have a group of checkboxes whose values I can grab as a list for a callback input. For the values selected, I want to return "hide": False in the columnState.

Here’s essentially the callback I’m attempting:

@callback(
    Output('data-grid', 'columnState'),
    State('data-grid', 'columnState'),
    Input('columns-checkbox-grp', 'value'),
)
def display_selected_columns(col_state, col_selections):
    for col in col_state:
        if col['colId'] in col_selections:
            col['hide'] = False
        else:
            col['hide'] = True
    
    return col_state

It doesn’t like the for loop on columnState because “‘NoneType’ object is not iterable”. I’m using Dash AG Grid 2.3.0. I’m looking at these docs as a guide for interacting with the column state in a callback.

I’m able to print the columnState (json.dumps(col_state)) and it looks valid. So I’m not sure if I’m missing something small here (which is… highly likely :smile:).

cc @jinnyzor @AnnMarieW

@nlyle

Try changing the columnSize to:

columnSize="sizeToFit",

1 Like

Hi @AnnMarieW, I’m trying to iterate through columnState so that I can adjust the hide property based on a input component’s values, but columnState is null when being evaluated in my callback.

It appears that when I want the State of column state, like State('data-grid', 'columnState'), I get a NULL column state. But when I want Input like Input('data-grid', 'columnState'), I’m able to display the column state via json.dumps().

In either case, I’m not able to iterate through the columns in column state for some reason because the column state is coming through as a NoneType object.

I’m using dash-ag-grid==2.3.0 with dash==2.9.3, so maybe I need to update my dash installation.

Hi @nlyle

I can’t duplicate the problem - Try running this example - it works fine for me:

ag-grid-forum


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

app = Dash(__name__)

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

columnDefs = [
    {"field": "athlete", "hide":True},
    {"field": "age"},
    {"field": "country"},
    {"field": "year"},
    {"field": "sport"},
    {"field": "total"},
]

grid=dag.AgGrid(
    id="data-grid",
    columnDefs=columnDefs,
    dashGridOptions={
        "skipHeaderOnAutoSize": True,
        "rowSelection": "multiple",
        "pagination": True,
         "paginationPageSize": 20,
        "enableCellTextSelection": True,
        "ensureDomOrder": True,
        "tooltipShowDelay": 100,
    },
  
    rowData=df.to_dict("records"),
   # columnSize="responsiveSizeToFit",
    columnSize="sizeToFit",
    suppressDragLeaveHidesColumns=False,
    style={"height": "530px", "width": "100%"}
 )

app.layout = html.Div(
    [
        html.Button("Unhide", id="btn-unhide"),
        grid,
    ]
)

@callback(
    Output("data-grid", "columnState"),
    Input("btn-unhide", "n_clicks"),
    State("data-grid", "columnState"),
    prevent_initial_call=True,
)
def show_all_columns(_, column_state):
    return [{'colId': col['colId'], 'hide': False} for col in column_state]

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



2 Likes

Here, give this a try:

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

app = Dash(__name__)

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

columnDefs = [
    {"field": "athlete"},
    {"field": "age", "initialWidth": 100},
    {"field": "country"},
    {"field": "year", "initialWidth": 100},
    {"field": "sport"},
    {"field": "total", "initialWidth": 100},
]

app.layout = html.Div(
    [
        dag.AgGrid(id='showColumns', rowData=[{'column': i['field'] or i['headerName']} for i in columnDefs],
                   columnDefs=[{'field': 'column', 'checkboxSelection': True, 'rowDrag': True}],
                   dashGridOptions={'rowSelection': 'multiple', "rowDragManaged": True},
                   getRowId='params.data.column'),
        dag.AgGrid(
            id="grid",
            rowData=df.to_dict("records"),
            defaultColDef={"sortable": True, "resizable": True, "filter": True, 'initialWidth': 130},
            suppressDragLeaveHidesColumns=False,
            columnDefs=columnDefs,
            style={'padding': '20px'}
        ),
    ]
)

@app.callback(
    Output("grid", "columnState"),
    Output("showColumns", "selectedRows"),
    Output("showColumns", "rowData"),
    Input("showColumns", "selectedRows"),
    Input("grid", "columnState"),
    Input("showColumns", "virtualRowData"),
)
def showColumns(selections, col_state, vData):
    if ctx.triggered_id == 'showColumns':
        if 'selectedRows' in ctx.triggered[0]['prop_id']:
            if selections:
                v = [i['column'] for i in selections]
                for sel in v:
                    add = True
                    for col in col_state:
                        if col['colId'] == sel:
                            add = False
                            break
                    if add:
                        col_state.append({'colId': v})
                for col in col_state:
                    if col['colId'] in v:
                        col['hide'] = False
                    else:
                        col['hide'] = True

                return col_state, no_update, no_update
        else:
            if vData and col_state:
                new_col_state = []
                for row in vData:
                    for col in col_state:
                        if col['colId'] == row['column']:
                            new_col_state.append(col)
                            break
                if col_state != new_col_state:
                    return new_col_state, no_update, no_update
            return no_update, no_update, no_update
    elif ctx.triggered_id == 'grid':
        rowData = [{'column': col['colId']} for col in col_state]
        if rowData == vData:
            rowData = no_update
        return no_update, {'ids': [col['colId'] for col in col_state if not col['hide']]}, rowData
    return no_update, no_update, no_update


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

Thank you both for sticking with me, I’ll try your examples. I seem to get Type Errors when interacting with my column state. When I try something really simple (below, the input columns-checkbox-grp is a DMC checkbox group):

# Adjust column display
@callback(
    Output('data-grid', 'columnState'),
    Input('columns-checkbox-grp', 'value'),
    prevent_initial_call=True,
)
def display_selected_columns(col_selections):
    return [{'colId': col['colId'], 'hide': False} for col in col_selections]

… I get a TypeError on the indices as col['coldID'] is not valid :thinking:

    return [{'colId': col['colId'], 'hide': False} for col in col_selections]
TypeError: string indices must be integers

This makes me think something else needs to be resolved first

Assuming that your value is just a list, then you just need to use col instead of col['colId'] as you are not using the columnState

Oh right – that’s correct. If I adjust to the following, it still doesn’t change my column state.

return [{'colId': col, 'hide': False} for col in col_selections]

And if I rewrite the callback to use State to loop through the columns in columnState, I get “TypeError: ‘NoneType’ object is not iterable” as if the columnState is null.

@callback(
    Output('data-grid', 'columnState'),
    Input('columns-checkbox-grp', 'value'),
    State('data-grid', 'columnState'),
)
def display_selected_columns(col_selections, col_state):
    for c in col_state:
        if c['colId'] in col_selections:
            c['hide'] = False
        else:
            c['hide'] = True
    return col_state

I’ll spend some time trying both of your examples and will circle back!

You have to wait for the grid to populate it initially. If not provided, it is completely empty. Thats where the error is coming from.

@jinnyzor Ah ok – In other words do I need prevent_initial_call=True? Or how do I ensure it’s populated so I can get the Sate of columnState?

Yes, prevent the initial call, you can also take a closer look at how @AnnMarieW and myself avoided this.

Hi both, I finally had a moment to put together a MRE (FYI @AnnMarieW). This is what I’d like to do, but the checkbox inputs don’t affect the column state.

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

# Random dataframe
d = {'col1': [1, 2, 3], 'col2': ['A', 'B', 'C'], 'col3': ['X', 'Y', 'Z']}
df = pd.DataFrame(data=d)
grid_data = df.to_dict('records')

# AG Grid column defs
columnDefs = [
    {
        "headerName": "Column 1",
        "field": "col1",
    },
    {
        "headerName": "Column 2",
        "field": "col2",
    },
    {
        "headerName": "Column 3",
        "field": "col3",
    },
]

defaultColDef = {
    "filter": True,
    "resizable": True,
    "sortable": True,
    "editable": False,
    "enableRowGroup": True,
}

app = Dash(__name__)

app.layout = dmc.MantineProvider(
    id="mantine-theme",
    withGlobalStyles=True,
    withNormalizeCSS=True,
    children=[
        dmc.Container(
            size="sm",
            children=[

				# Checkboxes
                dmc.CheckboxGroup(
                    id="checkbox-grp",
                    label="Select the columns you want to see",
                    orientation="horizontal",
                    offset="md",
                    persistence=True,
                    persistence_type="session",
                    persisted_props=["value"],
                    children=[
                        dmc.Checkbox(label="Column 1", value="col1"),
                        dmc.Checkbox(label="Column 2", value="col2"),
                        dmc.Checkbox(label="Column 3", value="col3"),
                    ],
                    value=[
                        "col1",
                        "col2",
                        "col3",
                    ]
                ),

                dmc.Space(h=30),

                # AG Grid
                dag.AgGrid(
                    id="data-grid",
                    columnDefs=columnDefs,
                    defaultColDef=defaultColDef,
                    dashGridOptions={
                        "maintainColumnOrder": True,
                        "skipHeaderOnAutoSize": True,
                        "pagination": True,
                        "enableCellTextSelection": True,
                        "ensureDomOrder": True,
                    },
                    rowData=grid_data,
                    columnSize="responsiveSizeToFit",
                    suppressDragLeaveHidesColumns=False,
                    # style={"height": "100px", "width": "100%"}
                )
            ],
            mt=100,
        )
    ],
)

# Adjust column display
@app.callback(
    Output('data-grid', 'columnState'),
    Input('checkbox-grp', 'value'),
    State('data-grid', 'columnState'),
    prevent_initial_call=True,
)
def display_selected_columns(col_selections, col_state):
    for c in col_state:
        if c['colId'] in col_selections:
            c['hide'] = False
        else:
            c['hide'] = True
    return col_state


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

@nlyle,

Did you try my example?

Hi @nlyle

Like I mentioned above:

if I change :

    columnSize="responsiveSizeToFit",

To:

    columnSize="sizeToFit",

Then your example worked for me. I’m not sure why the responsiveSizeToFit makes it fail. @jinnyzor any ideas?

2 Likes