Infinite Scroll

If you are an Enterprise user you should consider using the Server-Side Row Model instead of the Infinite Row Model. It offers the same functionality and many additional features.

Infinite scrolling allows the grid to lazy-load rows from the server depending on the scroll position is of the grid. In its simplest form, the more the user scrolls down, the more rows get loaded.

The grid will have an ‘auto extending’ vertical scroll. That means as the scroll reaches the bottom position, the grid will extend the height to allow scrolling even further down, almost making it impossible for the user to reach the bottom. This will stop happening once the grid has extended the scroll to reach the last record in the table.

Turning on Infinite Scrolling

dag.AgGrid(
    rowModelType="infinite",
)

Datasource Interface

Each time the grid wants more rows, it calls getRows() on the datasource. Use the getRowsRequest prop as the Input of the Dash callback. The callback responds with the rows requested. Use getRowsResponse as the Output of the callback to provide data to the grid.

Infinite Scroll Simple Example

Tip: You can also use the keyboard to navigate. Select a row to focus the grid. Then use the page up and page down keys to scroll by page. Use the home and end keys to quickly go to the first and last rows.

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

app = Dash(__name__)

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

df = pd.DataFrame(data=raw_data)

app.layout = html.Div(
    [
        dcc.Markdown("Infinite scroll with selectable rows"),
        dag.AgGrid(
            id="infinite-grid",
            columnSize="sizeToFit",
            columnDefs=[{"field": "id"}, {"field": "name"}],
            defaultColDef={"sortable": True},
            rowModelType="infinite",
            dashGridOptions={
                # The number of rows rendered outside the viewable area the grid renders.
                "rowBuffer": 0,
                # How many blocks to keep in the store. Default is no limit, so every requested block is kept.
                "maxBlocksInCache": 1,
                "rowSelection": "multiple",
            },
        ),
        html.Div(id="infinite-output"),
    ],
    style={"margin": 20},
)


@callback(
    Output("infinite-output", "children"), Input("infinite-grid", "selectedRows")
)
def display_selected_car2(selectedRows):
    if selectedRows:
        return [f"You selected id {s['id']} and name {s['name']}" for s in selectedRows]
    return no_update


@callback(
    Output("infinite-grid", "getRowsResponse"),
    Input("infinite-grid", "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)}


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

Infinite scroll with selectable rows

Aggregation and Grouping

Aggregation and grouping are not available in infinite scrolling. This is because to do so would require the grid knowing the entire dataset, which is not possible when using the Infinite Row Model. If you need aggregation and / or grouping for large datasets, check the Server-Side Row Model for doing aggregations on the server-side.

Sorting & Filtering

The grid cannot do sorting or filtering for you, as it does not have all of the data. Sorting or filtering must be done on the server-side. For this reason, if the sort or filter changes, the grid will use the datasource to get the data again and provide the sort and filter state to you.

Infinite Scroll with Sort and Filter

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


app = Dash(__name__)

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

df = pd.DataFrame(data=raw_data)

app.layout = html.Div(
    [
        dcc.Markdown("Infinite scroll with sort and filter"),
        dag.AgGrid(
            id="infinite-sort-filter-grid-2",
            columnSize="sizeToFit",
            columnDefs=[
                {"field": "id", "filter": "agNumberColumnFilter"},
                {"field": "name"},
            ],
            defaultColDef={"sortable": True, "filter": True, "floatingFilter": True},
            rowModelType="infinite",
            dashGridOptions={
                # The number of rows rendered outside the viewable area the grid renders.
                "rowBuffer": 0,
                # How many blocks to keep in the store. Default is no limit, so every requested block is kept.
                "maxBlocksInCache": 1,
                "rowSelection": "multiple",
            },
        ),
    ],
    style={"margin": 20},
)

operators = {
    "greaterThanOrEqual": "ge",
    "lessThanOrEqual": "le",
    "lessThan": "lt",
    "greaterThan": "gt",
    "notEqual": "ne",
    "equals": "eq",
}


def filterDf(df, data, col):
    if data["filterType"] == "date":
        crit1 = data["dateFrom"]
        crit1 = pd.Series(crit1).astype(df[col].dtype)[0]
        if "dateTo" in data:
            crit2 = data["dateTo"]
            crit2 = pd.Series(crit2).astype(df[col].dtype)[0]
    else:
        crit1 = data["filter"]
        crit1 = pd.Series(crit1).astype(df[col].dtype)[0]
        if "filterTo" in data:
            crit2 = data["filterTo"]
            crit2 = pd.Series(crit2).astype(df[col].dtype)[0]
    if data["type"] == "contains":
        df = df.loc[df[col].str.contains(crit1)]
    elif data["type"] == "notContains":
        df = df.loc[~df[col].str.contains(crit1)]
    elif data["type"] == "startsWith":
        df = df.loc[df[col].str.startswith(crit1)]
    elif data["type"] == "notStartsWith":
        df = df.loc[~df[col].str.startswith(crit1)]
    elif data["type"] == "endsWith":
        df = df.loc[df[col].str.endswith(crit1)]
    elif data["type"] == "notEndsWith":
        df = df.loc[~df[col].str.endswith(crit1)]
    elif data["type"] == "inRange":
        if data["filterType"] == "date":
            df = df.loc[df[col].astype("datetime64[ns]").between_time(crit1, crit2)]
        else:
            df = df.loc[df[col].between(crit1, crit2)]
    elif data["type"] == "blank":
        df = df.loc[df[col].isnull()]
    elif data["type"] == "notBlank":
        df = df.loc[~df[col].isnull()]
    else:
        df = df.loc[getattr(df[col], operators[data["type"]])(crit1)]
    return df


@callback(
    Output("infinite-sort-filter-grid-2", "getRowsResponse"),
    Input("infinite-sort-filter-grid-2", "getRowsRequest"),
)
def infinite_scroll(request):
    dff = df.copy()

    if request:
        if request["filterModel"]:
            fils = request["filterModel"]
            for k in fils:
                try:
                    if "operator" in fils[k]:
                        if fils[k]["operator"] == "AND":
                            dff = filterDf(dff, fils[k]["condition1"], k)
                            dff = filterDf(dff, fils[k]["condition2"], k)
                        else:
                            dff1 = filterDf(dff, fils[k]["condition1"], k)
                            dff2 = filterDf(dff, fils[k]["condition2"], k)
                            dff = pd.concat([dff1, dff2])
                    else:
                        dff = filterDf(dff, fils[k], k)
                except:
                    pass
            dff = dff

        if request["sortModel"]:
            sorting = []
            asc = []
            for sort in request["sortModel"]:
                sorting.append(sort["colId"])
                if sort["sort"] == "asc":
                    asc.append(True)
                else:
                    asc.append(False)
            dff = dff.sort_values(by=sorting, ascending=asc)

        lines = len(dff.index)
        if lines == 0:
            lines = 1

        partial = dff.iloc[request["startRow"] : request["endRow"]]
        return {"rowData": partial.to_dict("records"), "rowCount": lines}


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

Infinite scroll with sort and filter

Infinite Scroll with Pagination

Pagination in AG Grid is supported in all the different row models. Here is an example with the Infinite Row Model.

import dash_ag_grid as dag
from dash import Dash, Input, Output, html, no_update, callback


import pandas as pd

app = Dash(__name__)


df = pd.read_csv(
    "https://raw.githubusercontent.com/plotly/datasets/master/liquor_iowa_2021.csv"
)


app.layout = html.Div(
    [
        dag.AgGrid(
            id="infinite-scroll-pagination-example",
            columnDefs=[{"field": i} for i in df.columns],
            rowModelType="infinite",
            columnSize="autoSize",
            defaultColDef=dict(
                resizable=True, sortable=True, filter=True, minWidth=100
            ),
            dashGridOptions={"pagination": True},
        ),
    ]
)


@callback(
    Output("infinite-scroll-pagination-example", "getRowsResponse"),
    Input("infinite-scroll-pagination-example", "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)}


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