Row Sorting

Sorting is enabled by default for all columns. You can sort a column by clicking on the column header. To enable / disable sorting per column use the sortable column definition attribute.

# enable sorting on 'name' and 'age' columns only
columnDefs = [
    {'field': 'name'},
    {'field': 'age'},
    {'field': 'address', 'sortable': False},
]

To disable sorting for all columns, set sorting in the Default Column Definition.

# enable sorting on all columns by default
defaultColDef = {
    'sortable': False
}

columnDefs = [
    {'field': 'name'},
    {'field': 'age'},
    # enable sorting on address column
    {'field': 'address', 'sortable': True},
];

In the following example, all columns are sortable, except the Total column.

import dash_ag_grid as dag
from dash import Dash, html
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": "country"},
    {"field": "athlete"},
    {"field": "age"},
    {"field": "sport"},
    {"field": "total", "sortable": False},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-sorting-simple",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False}
        ),
    ],
)

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

Sorting Dates

The dates in the grid are strings and if they are not formatted as yyyy-mm-dd, then to sort them correctly they can be converted into date objects. To convert them, use
the JavaScript function d3.timeParse(). This is similar to the Python strptime() function.

date_obj= d3.timeParse(specifier)(date string)

Please
see Date Filter
for more info and examples.

In this dataset, the date is a string with the format dd/mm/yyyy. We turn it into a date object using valueGetter
with the d3.timeParse()

"valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},

In the example below, try clicking on the date column header, and you will see the date sorts correctly.
This example also demonstrates the date filter. For more information,
see

import dash_ag_grid as dag
from dash import Dash, html
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": "country"},
    {
        "field": "date",
        "filter": "agDateColumnFilter",
        "valueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "valueFormatter": {"function": "params.data.date"},
    },
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-sorting-dates",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"filter": True},
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False}
        ),
    ],
)

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

Custom Sorting

Custom sorting is provided at a column level by configuring a comparator on the Column Definition.

The following example from
the AG Grid docs
shows custom sorting using the comparator property and a custom function to determine the sort order. The example parses
a string date field. Note that with Dash, it’s easier to use the provided d3 functions as shown in the example above
rather than write your own custom function.

Example below shows the following:

View the JavaScript functions used for this example

These JavaScript functions must be added to the dashAgGridFunctions.js file in the assets folder.
See JavaScript Functions
for more information.

var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {};

dagfuncs.dateComparator = function (date1, date2) {
    const date1Number = monthToComparableNumber(date1);
    const date2Number = monthToComparableNumber(date2);
    if (date1Number === null && date2Number === null) {
        return 0;
    }
    if (date1Number === null) {
        return -1;
    }
    if (date2Number === null) {
        return 1;
    }
    return date1Number - date2Number;
}

// for example 29/08/2004 gets converted to 20040829
function monthToComparableNumber(date) {
    if (date === undefined || date === null) {
        return null;
    }
    const yearNumber = parseInt(date.split('/')[2]);
    const monthNumber = parseInt(date.split('/')[1]);
    const dayNumber = parseInt(date.split('/')[0]);
    return (yearNumber * 10000) + (monthNumber * 100) + dayNumber;
}
import dash_ag_grid as dag
from dash import Dash, html
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", "sort": "desc"},
    {"field": "age"},
    {"field": "country"},
    {"field": "year", "unSortIcon": True},
    {"field": "date", "comparator": {"function": "dateComparator"}},
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-sorting-custom-comparator",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False}
        ),
    ],
)

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

Multi Column Sorting

It is possible to sort by multiple columns. The default action for multiple column sorting is for the user to hold down
<kbd>Shift<kbd> while clicking the column header.

To change the default action, to use the <kbd>Ctrl<kbd> key (or <kbd>
Command<kbd> key on Apple) instead, set the Grid Option:

dashGridOptions = {'multiSortKey': 'ctrl'}

Try it in the example above. This image shows sorting by Country, then by Date, then by Athlete.

Multi column sorting

It is also possible to disable the multi sorting behavior with the Grid Option:

dashGridOptions = {'suppressMultiSort': True}

Or force the multi sorting behavior without key press with the Grid Option:

dashGridOptions = {'alwaysMultiSort': True}

Row Animations

Row animations occur after filtering, sorting, resizing height and expanding / collapsing a row group.

The grid will animate the rows in the following scenarios:

Disable Animation

Row animations can be disabled by setting the dash grid options animateRows = False.

dashGridOptions = {'animateRows': False}

Sorting Order and Animation

By default, the sorting order is:

ascending -> descending -> none

In other words, when you click a column that is not sorted, it will sort ascending. The next click will make it sort
descending. Another click will remove the sort.

It is possible to override this behavior by providing your own sortingOrder on either the dashGridOptions or
the columnDefs. If defined both in columnDefs and dashGridOptions, the columnDefs will get preference, allowing
you to define a common default, and then tailor per column.

The example below shows animation of the rows plus different combinations of sorting orders as follows:

import dash_ag_grid as dag
from dash import Dash, html
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", "sortingOrder": ['asc', 'desc']},
    {"field": "age", "sortingOrder": ['desc', 'asc']},
    {"field": "country", "sortingOrder": ['desc', None]},
    {"field": "year", "sortingOrder": ['asc']},
    {"field": "sport"},
    {"field": "total"},
]
app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-sorting-sort-order-animation",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={
                'sortingOrder': ['desc', 'asc', None],
            },
        ),
    ],
)

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

Sorting with Callbacks

You can control column sorting from a callback using
the Column State. For example, you can sort columns, capture the
current sort to restore it later, or clear the sort based on another user action, such as selecting a button, like in
the following example.

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

app = Dash(__name__)

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

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

app.layout = html.Div(
    [
        html.Div([
            html.Button("Athlete Asc", id="row-sorting-athlete-asc"),
            html.Button("Athlete Desc", id="row-sorting-athlete-desc"),
        ]),
        html.Div([
            html.Button("Country > Sport", id="row-sorting-country>sport"),
            html.Button("Sport > Country", id="row-sorting-sport>country"),
        ]),
        html.Div([
            html.Button("Save Sort", id="row-sorting-save-sort"),
            html.Button("Restore Sort", id="row-sorting-restore-sort"),
            html.Button("Clear Sort", id="row-sorting-clear-sort"),
        ]),
        dag.AgGrid(
            id="row-sorting-callback",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={"animateRows": False}
        ),
        dcc.Store(id='row-sorting-store-state')
    ]
)


@callback(
    Output("row-sorting-callback", "columnState"),
    State("row-sorting-callback", "columnState"),
    Input("row-sorting-athlete-asc", "n_clicks"),
    Input("row-sorting-athlete-desc", "n_clicks"),
    Input("row-sorting-country>sport", "n_clicks"),
    Input("row-sorting-sport>country", "n_clicks"),
    Input("row-sorting-clear-sort", "n_clicks"),
    prevent_initial_call=True,
)
def update_sort_state(col_state, *_):
    if ctx.triggered_id == "row-sorting-athlete-asc":
        return [
            {
                'colId': col['colId'],
                'sort': 'asc' if col['colId'] == 'athlete' else None
            } for col in col_state
        ]
    elif ctx.triggered_id == "row-sorting-athlete-desc":
        return [
            {
                'colId': col['colId'],
                'sort': 'desc' if col['colId'] == 'athlete' else None
            } for col in col_state
        ]
    elif ctx.triggered_id == "row-sorting-country>sport":
        return [
            {
                'colId': col['colId'],
                'sort': 'asc' if col['colId'] in ['country', 'sport'] else None,
                'sortIndex': 0 if col['colId'] == 'country' else 1 if col['colId'] == 'sport' else None,
            } for col in col_state
        ]
    elif ctx.triggered_id == "row-sorting-sport>country":
        return [
            {
                'colId': col['colId'],
                'sort': 'asc' if col['colId'] in ['country', 'sport'] else None,
                'sortIndex': 1 if col['colId'] == 'country' else 0 if col['colId'] == 'sport' else None,
            } for col in col_state
        ]
    elif ctx.triggered_id == "row-sorting-clear-sort":
        return [
            {
                'colId': col['colId'],
                'sort': None,
            } for col in col_state
        ]


@callback(
    Output("row-sorting-store-state", "data"),
    Input("row-sorting-save-sort", "n_clicks"),
    State("row-sorting-callback", "columnState"),
    prevent_initial_call=True,
)
def save_column_state(_, col_state):
    sort_state = [
        {
            'colId': col['colId'],
            'sort': col['sort'],
            'sortIndex': col['sortIndex'],
        } for col in col_state
    ]
    return json.dumps(sort_state)


@callback(
    Output("row-sorting-callback", "columnState", allow_duplicate=True),
    Input("row-sorting-restore-sort", "n_clicks"),
    State("row-sorting-store-state", "data"),
    prevent_initial_call=True,
)
def restore_column_state(_, saved_sort_state):
    return json.loads(saved_sort_state) if saved_sort_state else no_update


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

Accented Sort

By default, sorting doesn’t take into consideration locale-specific characters. If you need to make your sort
locale-specific you can configure this by setting the Grid Option:

dashGridOptions = {"accentedSort": True}

Using this feature is more expensive, if you need to sort a very large amount of data, you might find that this causes
the sort to be noticeably slower.

The following example is configured to use this feature.

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

app = Dash(__name__)

data = [
    {'accented': 'aäàá'},
    {'accented': 'aáàä'},
    {'accented': 'aàáä'},
]

columnDefs = [
    {"headerName": "Row ID", "valueGetter": {"function": "params.node.id"}},
    {'field': 'accented', "cellStyle": {'font-size': 25}}
]

app.layout = html.Div(
    [
        dcc.Checklist(
            id='chk-row-sorting-accented',
            options={'accented_ON': 'Enable accented sorting'},
        ),
        dag.AgGrid(
            id="row-sorting-accented",
            rowData=data,
            columnDefs=columnDefs,
            dashGridOptions={"animateRows": False}
        ),
    ]
)


@callback(
    Output("row-sorting-accented", "dashGridOptions"),
    Input("chk-row-sorting-accented", "value"),
    prevent_initial_call=True,
)
def enable_accented_sort(accented_ON):
    return {'animateRows': True, 'accentedSort': bool(accented_ON)}


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

Post-Sort

It is also possible to perform some post-sorting if you require additional control over the sorted rows.

This can be done by providing the function to use to the Grid Option:

dashGridOptions = {"postSortRows": {"function": "postSort(params)"}}

postSortRows (function) Function to perform additional sorting after the grid has sorted the rows.

The following example uses this configuration to perform a post-sort on the rows. The custom function puts rows with
Michael Phelps at the top always.

View the JavaScript functions used for this example

These JavaScript functions must be added to the dashAgGridFunctions.js file in the assets folder.
See JavaScript Functions
for more information.

var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {};

dagfuncs.postSort = function (params) {
    const rowNodes = params.nodes;
    // here we put Michael Phelps rows on top while preserving the sort order
    let nextInsertPos = 0;
    for (let i = 0; i < rowNodes.length; i++) {
        const athlete = rowNodes[i].data ? rowNodes[i].data.athlete : undefined;
        if (athlete === 'Michael Phelps') {
            rowNodes.splice(nextInsertPos, 0, rowNodes.splice(i, 1)[0]);
            nextInsertPos++;
        }
    }
}
import dash_ag_grid as dag
from dash import Dash, html
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"},
    {"field": "country", "sort": 'asc'},
    {"field": "year"},
    {"field": "sport"},
    {"field": "total"},
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="row-sorting-post-sort",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            columnSize="sizeToFit",
            dashGridOptions={
                "postSortRows": {"function": "postSort(params)"},
                "animateRows": False
            }
        ),
    ]
)

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