đź“Ł Dash AG Grid 2.2.0 Released

:rocket: We’re excited to announce that Dash AG Grid v2.2.0 has been released, with a few features and fixes. Thank you @jinnyzor and @AnnMarieW and @Skiks for your ongoing support maintaining this project and improving the docs. And thank you to our Plotly staff member, Mario Rodríguez Ibáñez for the scrollToRow PR.

pip install dash-ag-grid==2.2.0

Added:

  • #199 Add scrollTo prop which allows scrolling to rows and columns.
  • #209 Add getApi and getColumnApi to dash_ag_grid namespace to allow for JS functions to call the grid’s API directly

Fixed

  • 210 Fix issue with columnState and React 18

Updated

  • 210 Migrate props that use setProps from the render() to componentDidUpdate
6 Likes

Good day, All, @alistair.welch,

Let me expound a little on what the version brings to the table:

add your own event listeners -

This gives you the ability to trigger your own JS functions from any event the grid can spit out listed here, Grid Events.

interact with the grid via JS functions / clientside callbacks

This gives the ability to take this: Drag & Drop Example

And turn it into this, 3 callbacks to 1:

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

app = Dash(__name__)

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

columnDefs = [
    {"field": "country", "dndSource": True},
    {"field": "year"},
    {"field": "sport"},
    {"field": "gold"},
    {"field": "silver"},
    {"field": "bronze"},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            columnDefs=columnDefs,
            rowData=df.to_dict("records")[:5],
            columnSize="sizeToFit",
            defaultColDef={"resizable": True, "sortable": True, "filter": True},
            dashGridOptions={"rowDragManaged": True, "animateRows": True},
            style={'width': '45%'},
            id='grid1'
        ),
        dag.AgGrid(
            columnDefs=columnDefs,
            rowData=df.to_dict("records")[6:10],
            columnSize="sizeToFit",
            defaultColDef={"resizable": True, "sortable": True, "filter": True},
            dashGridOptions={"rowDragManaged": True},
            style={'width': '45%'},
            id='grid2'
        ),
        html.Button(id='drop1', style={'display': 'none'}, n_clicks=0),
        html.Button(id='drop2', style={'display': 'none'}, n_clicks=0),
        dcc.Store(id='dropStore', storage_type='session')
    ],
    style={"margin": 20, "display": "flex"},
)

## enable drag-n-drop
app.clientside_callback(
    """
    function (p) {
        const mouseClickEvents = ['mousedown', 'click', 'mouseup'];
        function simulateMouseClick(element, args) {
            mouseClickEvents.forEach((mouseEventType) =>
                element.dispatchEvent(
                    new MouseEvent(mouseEventType, {
                        view: window,
                        bubbles: true,
                        cancelable: true,
                        buttons: 1,
                        target: element,
                        ...args,
                    })
                )
            );
            mouseClickEvents.forEach((mouseEventType) =>
                element.dispatchEvent(
                    new PointerEvent(mouseEventType, {
                        view: window,
                        bubbles: true,
                        cancelable: true,
                        buttons: 1,
                        target: element,
                        ...args,
                    })
                )
            );
        }
        var gridOrigin;
        var gridDragOver = (event) => {
            const dragSupported = event.dataTransfer.types.length;

            if (dragSupported) {
              event.dataTransfer.dropEffect = 'copy';
              event.preventDefault();
            }
          };

        var gridDragStart = (origin, event) => {
            gridOrigin = origin
        }

        var gridDrop = (target, event) => {
            event.preventDefault();

            const jsonData = event.dataTransfer.getData('application/json');
            const data = JSON.parse(jsonData);

            // if data missing or the drop target is the same as the origin, do nothing
            if (!data || target == gridOrigin) {
              return;
            }

            if (target == 'grid2') {
                dash_ag_grid.getApi('grid2').applyTransactionAsync({'add': [data]})
            } else {
                dash_ag_grid.getApi('grid1').applyTransactionAsync({'add': [data]})
            }

          };
        setTimeout(() => {
        document.querySelector('#grid1').addEventListener('dragstart', (event)=>gridDragStart('grid1', event))
        document.querySelector('#grid2').addEventListener('dragstart', (event)=>gridDragStart('grid2', event))

        document.querySelector('#grid1').addEventListener('dragover', gridDragOver)
        document.querySelector('#grid2').addEventListener('dragover', gridDragOver)
        document.querySelector('#grid2').addEventListener('drop', (event)=>gridDrop('grid2', event))
        document.querySelector('#grid1').addEventListener('drop', (event)=>gridDrop('grid1', event))
        }, 500)
        return window.dash_clientside.no_update
    }
    """,
    Output('grid2', 'id'),
    Input('grid2', 'id')
)

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

Scroll To

This gives you the ability to scroll to any visible row, column, or both with the ability to pass rowIndex, rowId (when using getRowId, or data for a row, and then the column. Very useful for bringing your users to a specific cell for them to view.

Use this to test is out:

from dash import Dash, html, Input, Output, no_update
from dash_ag_grid import AgGrid
import pandas as pd
import json


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

scroll_to_inputs = [
        {"rowIndex": 100, "rowPosition": "bottom",},
        {"column": "bronze", "columnPosition": "end"},
        {"rowId": "Elizabeth Beisel12/8/2012", "rowPosition": "top"},
        {"rowIndex": 300, "rowId": "500"},
        {
            "rowIndex": 400,
            "rowId": "Ryan Bayley29/08/2004",
            "column": "athlete",
            "rowPosition": "bottom",
            "columnPosition": "start",
        },
        {
            "data": {
                "athlete": "Sabine Völker",
                "age": 28,
                "country": "Germany",
                "year": 2002,
                "date": "24/02/2002",
                "sport": "Speed Skating",
                "gold": 0,
                "silver": 2,
                "bronze": 1,
                "total": 3,
            },
            "column": "bronze",
            "columnPosition": "end",
        },
        {
            "rowIndex": 2000,
            "rowId": "Elizabeth Beisel12/8/2012",
            "data": {
                "athlete": "Elizabeth Beisel",
                "age": 19,
                "country": "United States",
                "year": 2012,
                "date": "12/8/2012",
                "sport": "Swimming",
                "gold": 0,
                "silver": 1,
                "bronze": 1,
                "total": 2,
            },
            "column": "age",
            "columnPosition": "start",
        },
    ]

app = Dash()

# basic columns definition with column defaults
columnDefs = [{"field": c} for c in df.columns]

app.layout = html.Div(
    [
        AgGrid(
            id="grid",
            columnDefs=columnDefs,
            rowData=df.to_dict("records"),
            defaultColDef={"resizable": True, "sortable": True, "filter": True},
            getRowId="params.data.athlete+params.data.date",
        ),
        html.Button(id="btn", children="Scroll To Next Position"),
        html.Div(id="scrollTo-output"),
        html.Div(id="scrollTo-input"),
    ]
)

@app.callback(
    Output("grid", "scrollTo"),
    Input("btn", "n_clicks"),
    prevent_initial_call=True,
)
def update_scrollTo(n_clicks):
    if len(scroll_to_inputs) + 1 > n_clicks:
        return scroll_to_inputs[n_clicks - 1]
    return no_update

@app.callback(
    Output("scrollTo-output", "children"),
    Input("grid", "scrollTo"),
)
def display_scrollTo(scroll_to):
    return json.dumps(scroll_to)

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

Thanks Mario Rodríguez Ibáñez!

2 Likes

Thank you for adding the examples, @jinnyzor .

2 Likes

I found another great usage of API!

Current limitation of columnSize=“autoSize” was that sizes of the columns were calculated only when the table was initialized. When updating the data with callback columns do not change its width. You can now easily fix this using this clientside callback:

app.clientside_callback(
    """function (u) {
        dash_ag_grid.getColumnApi("grid").autoSizeAllColumns()
        return dash_clientside.no_update
    }""",
    Output("dummy_out", "children"),
    Input("grid", "getRowsResponse"), # use rowData when using Client-Side row model, I use getRowsResponse for infinite row model
    prevent_initial_call=True
)

The point is that it gets called everytime you update the data and it auto sizes all the columns!

4 Likes