Filter Conditions

This section describes the Filter Conditions shared by the three Simple Filters provided by the
grid, Text Filter, Number Filter
and Date Filter.

Each Simple Filter follows the same layout. The filter consists of one or more Filter Conditions separated by zero or
more Join Operators.

The only layout difference is the type of input field presented to the user: a text field for Text Filters, a number
field for Number Filters, and a date picker field for Date Filters.

Simple Filter Parts

Filter Options

Each filter provides a dropdown list of filter options to select from. Each filter option represents a filtering
strategy, for example, ‘equals’, ‘not equals’, etc.

Each filter’s default filter options can be found on their respective pages:

Information on defining Custom Filter Options can be
found below.

Filter Value

Each filter option takes zero (a possibility with custom options), one (for most) or two (for ‘inRange’) values. The
value type depends on the filter type, for example, the Date Filter takes Date values.

Number of Conditions

By default, each filter initially only displays one Filter Condition. When the user completes all the visible Filter
Conditions, another Filter Condition becomes visible. When the user clears the last completed Filter Condition, any
empty Filter Conditions on either side are hidden if required. Additionally, when the filter is closed, any empty Filter
Conditions not at the end are removed if required.

The maximum number of Filter Conditions can be controlled by setting the Filter Parameter maxNumConditions (the
default value is 2).

It is also possible to always display a certain number of Filter Conditions by setting the Filter
Parameter numAlwaysVisibleConditions. In this case, Filter Conditions at the end will be disabled until the previous
Filter Condition has been completed.

Join Operator

The Join Operator decides how the Filter Conditions are joined, using either AND or OR. All Join Operators have the
same value, with only the first one being editable when there are multiple.

Example: Simple Filter Conditions

The following example demonstrates Filter Condition configuration that can be applied to any Simple Filter.

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",
        "filterParams": {
            "filterOptions": ["contains", "startsWith", "endsWith"],
            "defaultOption": "startsWith",
        },
    },
    {
        "field": 'sport',
        "filterParams": {"maxNumConditions": 10},
    },
    {
        "field": "age",
        "filter": "agNumberColumnFilter",
        "filterParams": {
            "numAlwaysVisibleConditions": 2,
            "defaultJoinOperator": "OR",
        },
        "maxWidth": 100,
    },
    {
        "field": 'date',
        "filter": 'agDateColumnFilter',
        "filterValueGetter": {"function": "d3.timeParse('%d/%m/%Y')(params.data.date)"},
        "filterParams": {"maxNumConditions": 1},
    },
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="filter-conditions-simple",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"flex": 1, "minWidth": 150, "filter": True},
            dashGridOptions={"animateRows": False}
        ),
    ]
)

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

Custom Filter Options

For applications that have bespoke filtering requirements, it is also possible to add new custom filtering options to
the number, text and date filters. For example, a ‘Not Equal (with Nulls)’ filter option could be included alongside the
built-in ‘Not Equal’ option.

Custom filter options are supplied to the grid via filterParams.filterOptions and must define the following
properties:

The displayKey should contain a unique key value that doesn’t clash with the built-in filter keys. A default
displayName should also be provided but can be replaced by a locale-specific value using
a getLocaleText.

The custom filter logic is implemented through the predicate function, which receives the filterValues typed by the
user along with the cellValue from the grid, and returns True or False.

The number of filterValues and corresponding inputs is controlled by the optional property numberOfInputs:

Custom FilterOptions can be supplied alongside the built-in filter option string keys as shown below:

columnDefs: [
    {
        "field": 'age',
        "filter": 'agNumberColumnFilter',
        "filterParams": {
            "filterOptions": [
                'lessThan',  # built-in filter option
                {
                    "displayKey": 'lessThanWithNulls',
                    "displayName": 'Less Than with Nulls',
                    # See below to define the "predicate" functions
                    "predicate": "lessThanWithNullsFilter",
                },
                'greaterThan',
                {
                    "displayKey": 'greaterThanWithNulls',
                    "displayName": 'Greater Than with Nulls',
                    "predicate": "greaterThanWithNullsFilter",
                },
                {
                    "displayKey": 'betweenExclusive',
                    "displayName": 'Between (Exclusive)',
                    "predicate": "betweenExclusiveFilter",
                    "numberOfInputs": 2,
                }
            ]
        }
    }
]

Using the following JavaScript functions for the predicate parameters. 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.lessThanWithNullsFilter = ([filterValue], cellValue) => cellValue == null || cellValue < filterValue;
dagfuncs.greaterThanWithNullsFilter = ([filterValue], cellValue) => cellValue == null || cellValue > filterValue;
dagfuncs.betweenExclusiveFilter = ([fv1, fv2], cellValue) => cellValue == null || fv1 < cellValue && fv2 > cellValue;

Example: Custom Filter Options

The following example demonstrates several custom filter options:

The Athlete column has the contains built-in filter and four custom filter options managed by
a Text Filter:

The Age column has the Choose one built-in filter (inactive filter option) and five custom filter options managed
by a Number Filter:

Moreover, the maxNumConditions=1 option is used to only display one Filter Condition.

The Date column has the equals built-in filter and three custom filter options managed by
a Date Filter:

NOTE: a custom comparator is still required for the built-in date filter options, for example equals.

The Country column has the notEqual built-in filter and one custom filter option:

It also demonstrates how localisation can be achieved via the gridOptions.getLocaleText(params) function, where the
default displayName Does not equal is replaced by ! Not Equals ! for the built-in filter option notEqual.

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 || {});

// Custom filters
// athlete
dagfuncs.startsAFilter = (_, cellValue) => cellValue != null && cellValue.indexOf('A') === 0;
dagfuncs.startsNFilter = (_, cellValue) => cellValue != null && cellValue.indexOf('N') === 0;
dagfuncs.regExpFilter = ([filterValues], cellValue) => cellValue == null || new RegExp(filterValues, 'gi').test(cellValue);
dagfuncs.betweenExclusiveTextFilter = ([fv1, fv2], cellValue) => {
    return cellValue == null || (fv1.toLowerCase() < cellValue.toLowerCase() && fv2.toLowerCase() > cellValue.toLowerCase())
};
// age
dagfuncs.evenNumbersFilter = (_, cellValue) => cellValue != null && cellValue % 2 === 0;
dagfuncs.oddNumbersFilter = (_, cellValue) => cellValue != null && cellValue % 2 !== 0;
dagfuncs.blanksFilter = (_, cellValue) => cellValue == null;
dagfuncs.age5YearsAgoFilter = ([fv1], cellValue) => cellValue == null || cellValue - 5 === fv1;
dagfuncs.betweenExclusiveNumberFilter = ([fv1, fv2], cellValue) => cellValue == null || (fv1 < cellValue && fv2 > cellValue);
// date
dagfuncs.equalsWithNullsFilter = ([filterValue], cellValue) => {
    if (cellValue == null) return true;

    const parts = cellValue.split('/');
    const cellDate = new Date(
        Number(parts[2]),
        Number(parts[1] - 1),
        Number(parts[0])
    );
    return cellDate.getTime() === filterValue.getTime();
};
dagfuncs.leapYearFilter = (_, cellValue) => {
    if (cellValue == null) return true;

    const year = Number(cellValue.split('/')[2]);
    return year % 4 === 0 && year % 200 !== 0;
};
dagfuncs.betweenExclusiveDateFilter = ([fv1, fv2], cellValue) => {
    if (cellValue == null) return true;

    const parts = cellValue.split('/');
    const cellDate = new Date(
        Number(parts[2]),
        Number(parts[1] - 1),
        Number(parts[0])
    );
    return (
        cellDate.getTime() > fv1.getTime() && cellDate.getTime() < fv2.getTime()
    );
};
dagfuncs.dateComparator = (filterLocalDateAtMidnight, cellValue) => {
    const dateAsString = cellValue;
    if (dateAsString == null) return -1;
    const dateParts = dateAsString.split('/');
    const cellDate = new Date(
        Number(dateParts[2]),
        Number(dateParts[1]) - 1,
        Number(dateParts[0])
    );

    if (filterLocalDateAtMidnight.getTime() === cellDate.getTime()) {
        return 0;
    }

    if (cellDate < filterLocalDateAtMidnight) {
        return -1;
    }

    if (cellDate > filterLocalDateAtMidnight) {
        return 1;
    }
    return 0;
};
// country
dagfuncs.notEqualNoNullsFilter = ([filterValue], cellValue) => {
    if (cellValue == null) return false;

    return cellValue.toLowerCase() !== filterValue.toLowerCase();
};
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"
)

athleteFilterParams = {
    "filterOptions": [
        'contains',
        {
            "displayKey": 'startsA',
            "displayName": 'Starts With "A"',
            "predicate": {"function": "startsAFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'startsN',
            "displayName": 'Starts With "N"',
            "predicate": {"function": "startsNFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'regexp',
            "displayName": 'Regular Expression',
            "predicate": {"function": "regExpFilter"},
            "numberOfInputs": 1,
        },
        {
            "displayKey": 'betweenExclusive',
            "displayName": 'Between (Exclusive)',
            "predicate": {"function": "betweenExclusiveTextFilter"},
            "numberOfInputs": 2,
        },
    ],
}

ageFilterParams = {
    "filterOptions": [
        'empty',
        {
            "displayKey": 'evenNumbers',
            "displayName": 'Even Numbers',
            "predicate": {"function": "evenNumbersFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'oddNumbers',
            "displayName": 'Odd Numbers',
            "predicate": {"function": "oddNumbersFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'blanks',
            "displayName": 'Blanks',
            "predicate": {"function": "blanksFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'age5YearsAgo',
            "displayName": 'Age 5 Years Ago',
            "predicate": {"function": "age5YearsAgoFilter"},
            "numberOfInputs": 1,
        },
        {
            "displayKey": 'betweenExclusive',
            "displayName": 'Between (Exclusive)',
            "predicate": {"function": "betweenExclusiveNumberFilter"},
            "numberOfInputs": 2,
        },
    ],
    "maxNumConditions": 1,
}

dateFilterParams = {
    "filterOptions": [
        'equals',
        {
            "displayKey": 'equalsWithNulls',
            "displayName": 'Equals (with Nulls)',
            "predicate": {"function": "equalsWithNullsFilter"},
        },
        {
            "displayKey": 'leapYear',
            "displayName": 'Leap Year',
            "predicate": {"function": "leapYearFilter"},
            "numberOfInputs": 0,
        },
        {
            "displayKey": 'betweenExclusive',
            "displayName": 'Between (Exclusive)',
            "predicate": {"function": "betweenExclusiveDateFilter"},
            "numberOfInputs": 2,
        },
    ],
    "comparator": {"function": "dateComparator"},
}

countryFilterParams = {
    "filterOptions": [
        'notEqual',
        {
            "displayKey": 'notEqualNoNulls',
            "displayName": 'Not Equals without Nulls',
            "predicate": {"function": "notEqualNoNullsFilter"},
        },
    ],
}

columnDefs = [
    {
        "field": "athlete",
        "filterParams": athleteFilterParams,
    },
    {
        "field": "age",
        "filter": "agNumberColumnFilter",
        "filterParams": ageFilterParams,
    },
    {
        "field": 'date',
        "filter": 'agDateColumnFilter',
        "filterParams": dateFilterParams,
    },
    {
        "field": 'country',
        "filterParams": countryFilterParams,
    }
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="filter-conditions-custom-options",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"flex": 1, "filter": True},
            dashGridOptions={
                "animateRows": False,
                "getLocaleText": {"function": "(params.key === 'notEqual') ? '! Not Equals !' : params.defaultValue"}
            }
        ),
    ]
)

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

Customising Filter Placeholder Text

Filter placeholder text can be customised on a per-column basis using filterParams.filterPlaceholder in the Column
Definition. The placeholder can be either a string or a function as shown in the snippet below:

columnDefs: [
    {
        "field": 'age',
        "filter": 'agNumberColumnFilter',
        "filterParams": {
            "filterPlaceholder": 'Age...'
        }
    },
    {
        "field": 'total',
        "filter": 'agNumberColumnFilter',
        "filterParams": {
            "filterPlaceholder": {"function": "`${params.filterOptionKey} ${params.placeholder}`"}
        }
    }
]

When filterPlaceholder is a function, the parameters are made up of the following:

The following example shows the various ways of specifying filter placeholders. Click on the filter menu for the
different columns in the header row to see the following:

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",
        "filter": "agTextColumnFilter",
        "filterParams": {
            "filterPlaceholder": 'Country...',
        },
    },
    {
        "field": 'sport',
        "filter": 'agTextColumnFilter',
        "filterParams": {
            "filterPlaceholder": {"function": "`${params.filterOptionKey} - ${params.placeholder}`"},
        },
    },
    {
        "field": 'total',
        "filter": 'agNumberColumnFilter',
        "filterParams": {
            "filterPlaceholder": {"function": "`${params.filterOption} total`"},
        },
    }
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="filter-conditions-custom-placeholder",
            rowData=df.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"flex": 1, "filter": True},
            dashGridOptions={"animateRows": False}
        ),
    ]
)

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