serverSide rowModelType in Dash AgGrid

Hi,
There isn’t any way mentioned in the documentation to work with serverSide rowModelType in dash AgGrid. Can any one provide me how to implement it?

I have gone through the official AgGrid documentation, but they are in Js. I want to know how its possible in Dash version

I am using the enterprise version of AgGrid.

Hello @Gangz,

Welcome to the community!

Server side row model is not currently supported, there were some issues when I initially tried to get it working.

With that said, the infinite row model sends its query to the dash server by default. So, this model is similar to how the server side row model works.

Are you missing some functionality in the infinite model that the serverSide would provide?

Infinite model does not support group by and aggregation, pivot features, which I need to implement, that’s why serverside rowmodel seems better approach

Can you give an MRE of something I can work with?

For right now, just work with rowData for the groups.

Suppose this is the code which has infinite row model enabled currently.
I want it to group by age such that it also shows those collapsable arrows in the age column like this:

how can i achieve this?

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

app = Dash(__name__)

raw_data = {"id": [], "name": [], "age":[]}
for i in range(0, 10000):
    raw_data["id"].append(i)
    raw_data["name"].append(f"{i*3%5}-{i*7%15}-{i%8}")
    raw_data["age"].append(random.choice([12,13,14])

df = pd.DataFrame(data=raw_data)

app.layout = html.Div(
    [
        dag.AgGrid(
            id="infinite-grid-2",
            columnSize="sizeToFit",
            columnDefs=[{"field": "id"}, {"field": "name"}, {"field": "age"}],
            defaultColDef={"sortable": True},
            rowModelType="infinite"
        ),
    ],
    style={"margin": 20},
)

@callback(
    Output("infinite-grid-2", "getRowsResponse"),
    Input("infinite-grid-2", "getRowsRequest"),
)
def infinite_scroll(request):
    if request is None:
        return no_update
    partial = df.iloc[request["startRow"] : request["endRow"]]
    return {"rowData": partial.to_dict("records"), "rowCount": len(df.index)}

Hello @Gangz,

Check out my response here, let me know if you need some more guidance.

This uses the flask server route to customize the api for the server data. Basically, you just cater your response to however you want to use it. :slight_smile:

You’ll need to craft your own where, group and select clauses:

1 Like

Here is if you have data as a df:

app.py

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, dcc, State
import requests, json
import flask
import pandas as pd

app = Dash(__name__)

server = app.server

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

rowData = df.to_dict('records')

columnDefs = [
    # Row group by country and by year is enabled.
    {"field": "country", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "year", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "athlete", "sortable": True, "filter": True},
    {"field": "age", "sortable": True, "filter": True},
    {"field": "date", "sortable": True, "filter": True},
    {"field": "sport", "sortable": True, "filter": True},
    {"field": "total", "sortable": True, "filter": True, "aggFunc": "sum"},
]

def extractRowsFromData(request, df):
    response = []
    dff = df.copy()
    groupBy = [i['id'] for i in request['rowGroupCols']]
    agg = {i['id']: i['aggFunc'] for i in request['valueCols']}
    if not request['groupKeys']:
        dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
    else:
        for i in range(len(request['groupKeys'])):
            dff = dff[dff[request['rowGroupCols'][i]['id']] == request['groupKeys'][i]]
        if len(request['groupKeys']) != len(groupBy):
            dff = dff.groupby(groupBy[:len(request['groupKeys'])+1]).agg(agg).reset_index()
    return {'rowData': dff.to_dict('records')[request['startRow']: request['endRow']], 'rowCount': len(dff)}

@server.route('/api/serverData', methods=['POST'])
def serverData():
    response = extractRowsFromData(flask.request.json, df)
    return json.dumps(response)


grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            dashGridOptions={"rowSelection": "multiple"},
            defaultColDef=dict(
                resizable=True,
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
        grid,
    ]
)

app.clientside_callback(
    """async function (id) {
        const delay = ms => new Promise(res => setTimeout(res, ms));
        const updateData = (grid) => {
          var datasource = createServerSideDatasource();
          grid.setServerSideDatasource(datasource);
        };
        var grid;
            try {
                grid = dash_ag_grid.getApi(id)
            } catch {}
            count = 0
            while (!grid) {
                await delay(200)
                try {
                    grid = dash_ag_grid.getApi(id)
                } catch {}
                count++
                if (count > 20) {
                    break;
                }
            }
            if (grid) {
                updateData(grid)
            }
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'), Input('grid', 'id')
)


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

js.file:

async function getServerData(request) {
    response = await fetch('./api/serverData', {'method': 'POST', 'body': JSON.stringify(request),
      'headers': {'content-type': 'application/json'}})
    return response.json()
}

function createServerSideDatasource() {
  const dataSource = {
    getRows: async (params) => {
      console.log('ServerSideDatasource.getRows: params = ', params);
      var result = await getServerData(params.request)
      console.log('getRows: result = ', result);
      setTimeout(function () {
        params.success(result);
      }, 200);
    },
  };
  return dataSource;
}

with support for sorting the columns:

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, dcc, State
import requests, json
import flask
import pandas as pd

app = Dash(__name__)

server = app.server

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

rowData = df.to_dict('records')

columnDefs = [
    # Row group by country and by year is enabled.
    {"field": "country", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "year", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "athlete", "sortable": True, "filter": True},
    {"field": "age", "sortable": True, "filter": True},
    {"field": "date", "sortable": True, "filter": True},
    {"field": "sport", "sortable": True, "filter": True},
    {"field": "total", "sortable": True, "filter": True, "aggFunc": "sum"},
]

def extractRowsFromData(request, df):
    response = []
    print(request)
    dff = df.copy()
    groupBy = [i['id'] for i in request['rowGroupCols']]
    agg = {i['id']: i['aggFunc'] for i in request['valueCols']}
    if not request['groupKeys']:
        dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
    else:
        for i in range(len(request['groupKeys'])):
            dff = dff[dff[request['rowGroupCols'][i]['id']] == request['groupKeys'][i]]
        if len(request['groupKeys']) != len(groupBy):
            dff = dff.groupby(groupBy[:len(request['groupKeys'])+1]).agg(agg).reset_index()
    dff = dff.sort_values(by=[i['colId'] for i in request['sortModel']], ascending=[i['sort'] == 'asc' for i in request['sortModel']])
    return {'rowData': dff.to_dict('records')[request['startRow']: request['endRow']], 'rowCount': len(dff)}

@server.route('/api/serverData', methods=['POST'])
def serverData():
    response = extractRowsFromData(flask.request.json, df)
    return json.dumps(response)


grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            dashGridOptions={"rowSelection": "multiple"},
            defaultColDef=dict(
                resizable=True,
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
        grid,
    ]
)

app.clientside_callback(
    """async function (id) {
        const delay = ms => new Promise(res => setTimeout(res, ms));
        const updateData = (grid) => {
          var datasource = createServerSideDatasource();
          grid.setServerSideDatasource(datasource);
        };
        var grid;
            try {
                grid = dash_ag_grid.getApi(id)
            } catch {}
            count = 0
            while (!grid) {
                await delay(200)
                try {
                    grid = dash_ag_grid.getApi(id)
                } catch {}
                count++
                if (count > 20) {
                    break;
                }
            }
            if (grid) {
                updateData(grid)
            }
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'), Input('grid', 'id')
)


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

Thanks to your example code, I have learned how to implement row grouping with serverSide rowModelType.
But to take it one step further, I would like to allow users to freely apply row grouping in the columns of the sidebar.

In the current version, applying column to row grouping from the sidebar is disabled.

grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            #getRowId="params.data.id",
            columnDefs=columnDefs,            
            defaultColDef=dict(
                resizable=True,
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
            dashGridOptions={
                "sideBar": True
            },


        ),
    ]
)

Hello @wowwwn,

Not sure I understand the issue here, here is an updated version that should work:

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, dcc, State
import requests, json
import flask
import pandas as pd

app = Dash(__name__)

server = app.server

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

rowData = df.to_dict('records')

columnDefs = [
    # Row group by country and by year is enabled.
    {"field": "country", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "year", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "athlete", "sortable": True, "filter": True},
    {"field": "age", "sortable": True, "filter": True},
    {"field": "date", "sortable": True, "filter": True},
    {"field": "sport", "sortable": True, "filter": True},
    {"field": "total", "sortable": True, "filter": True, "aggFunc": "sum"},
]

def extractRowsFromData(request, df):
    response = []
    print(request)
    dff = df.copy()
    groupBy = [i['id'] for i in request['rowGroupCols']]
    agg = {i['id']: i['aggFunc'] for i in request['valueCols']}
    if not request['groupKeys']:
        dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
    else:
        for i in range(len(request['groupKeys'])):
            dff = dff[dff[request['rowGroupCols'][i]['id']] == request['groupKeys'][i]]
        if len(request['groupKeys']) != len(groupBy):
            dff = dff.groupby(groupBy[:len(request['groupKeys'])+1]).agg(agg).reset_index()
    dff = dff.sort_values(by=[i['colId'] for i in request['sortModel']], ascending=[i['sort'] == 'asc' for i in request['sortModel']])
    return {'rowData': dff.to_dict('records')[request['startRow']: request['endRow']], 'rowCount': len(dff)}

@server.route('/api/serverData', methods=['POST'])
def serverData():
    response = extractRowsFromData(flask.request.json, df)
    return json.dumps(response)


grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            dashGridOptions={"rowSelection": "multiple", "sideBar": True},
            defaultColDef=dict(
                resizable=True,
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
            style={'overflow': 'auto', 'resize': 'both'}
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
        grid,
    ]
)

app.clientside_callback(
    """async function (id) {
        const updateData = (grid) => {
          var datasource = createServerSideDatasource();
          grid.setServerSideDatasource(datasource);
        };
        var grid;
        grid = await window.dash_ag_grid.getApiAsync(id)
        if (grid) {
            updateData(grid)
        }
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'), Input('grid', 'id')
)


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

Take a look at this version and see if it works for you:

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, dcc, State
import requests, json
import flask
import pandas as pd

app = Dash(__name__)

server = app.server

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

rowData = df.to_dict('records')

columnDefs = [
    # Row group by country and by year is enabled.
    {"field": "country", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "year", "sortable": True, "filter": True, "rowGroup": True, "hide": True},
    {"field": "athlete", "sortable": True, "filter": True},
    {"field": "age", "sortable": True, "filter": True},
    {"field": "date", "sortable": True, "filter": True},
    {"field": "sport", "sortable": True, "filter": True},
    {"field": "total", "sortable": True, "filter": True, "aggFunc": "sum"},
]

def extractRowsFromData(request, df):
    response = []
    print(request)
    dff = df.copy()
    groupBy = []
    if request['rowGroupCols']:
        groupBy = [i['id'] for i in request['rowGroupCols']]
    agg = {}
    if request['valueCols']:
        agg = {i['id']: i['aggFunc'] for i in request['valueCols']}
    if not request['groupKeys']:
        if groupBy:
            if agg:
                dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
            else:
                dff = dff.groupby(groupBy[0]).agg('count').reset_index()
    else:
        for i in range(len(request['groupKeys'])):
            dff = dff[dff[request['rowGroupCols'][i]['id']] == request['groupKeys'][i]]
        if len(request['groupKeys']) != len(groupBy):
            if agg:
                dff = dff.groupby(groupBy[:len(request['groupKeys'])+1]).agg(agg).reset_index()
            else:
                dff = dff.groupby(groupBy[:len(request['groupKeys']) + 1]).agg('count').reset_index()
    dff = dff.sort_values(by=[i['colId'] for i in request['sortModel']], ascending=[i['sort'] == 'asc' for i in request['sortModel']])
    return {'rowData': dff.to_dict('records')[request['startRow']: request['endRow']], 'rowCount': len(dff)}

@server.route('/api/serverData', methods=['POST'])
def serverData():
    response = extractRowsFromData(flask.request.json, df)
    return json.dumps(response)


grid = html.Div(
    [
        dag.AgGrid(
            id="grid",
            columnDefs=columnDefs,
            dashGridOptions={"rowSelection": "multiple", "sideBar": True},
            defaultColDef=dict(
                resizable=True,
                enableRowGroup=True,
                enableValue=True,
                enablePivot=True
            ),
            enableEnterpriseModules=True,
            rowModelType="serverSide",
            style={'overflow': 'auto', 'resize': 'both'}
        ),
    ]
)

app.layout = html.Div(
    [
        dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
        grid,
    ]
)

app.clientside_callback(
    """async function (id) {
        const updateData = (grid) => {
          var datasource = createServerSideDatasource();
          grid.setServerSideDatasource(datasource);
        };
        var grid;
        grid = await window.dash_ag_grid.getApiAsync(id)
        if (grid) {
            updateData(grid)
        }
        return window.dash_clientside.no_update
    }""",
    Output('grid', 'id'), Input('grid', 'id')
)


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

You might need to work with your data some, but this will let you change things about the sidebar. For more info, check here:

3 Likes

Thank you for your help.
I’ve confirmed that the desired feature works through your example.
I’ll try applying it to my code now. Thank you.

1 Like

7 posts were split to a new topic: serverSide rowModelType with Storing Changes