Dash AG Grid - How to apply a conditional style to spanned row header and spanned column headers wo breaking the default spanned style ? - MRE

Hi there;

Assuming we have a dash AG Grid like this:

Whose code is

import dash_ag_grid as dag
import pandas as pd
from dash import Dash, html

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

df = pd.read_excel("data/external_dummy_data_for_debug/olympic_winners.xlsx")

dfg = df.groupby(["country", "year"])[["bronze", "gold", "total"]].sum().reset_index()
df_agg_f = dfg[dfg["country"].isin(["France", "Germany", "Italy"]) & dfg["year"].isin([2008, 2012])].copy(deep=True)

columnDefs = [{
    "field": "country", "minWidth": 50, "maxWidth": 100,
    "rowSpan": {"function": "params.node.id %2 === 0 ? 2 : 1"},
    "cellStyle": {
        "backgroundColor": "var(--ag-header-background-color)",
        # "styleConditions": [{
        #     "condition": "params.data['bronze'] > 21",
        #     "style": {"color": "red"}
        # }]
    },
}, {"field": "year", "maxWidth": 80}, {"field": "bronze", "maxWidth": 80}, {"field": "gold", "maxWidth": 80}]

app = Dash()

app.layout = [
    html.Div([
        dag.AgGrid(
            id="row-spanning-simple-example",
            rowData=df_agg_f.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"sortable": False},
            columnSize="autoSize",
            dashGridOptions={"suppressRowTransform": True},
        ),
    ], style={"width": 400})
]


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

How to proceed to apply a specific font color on the "country"values, without breaking the actual spanning, when “bronze” is larger than 21 ?
If I uncomment the 4 commented lines in the above code:

        "styleConditions": [{
            "condition": "params.data['bronze'] > 21",
            "style": {"color": "red"}
        }]

Then I get this:

It seems that if I change the font color, it completely overwrites the style of spanned rows. How to fix this ?

The goal is to get only the first row of each spanned cell, displayed in red (the first “France”, “Germany”, “Italy”), while keeping all the cells background of the first column in grey, if the column “bronze” contains a value >21).


(paint art)

It does not make a lot of sense with this MRE but it does in my app

Sidequestion: is there a way to center vertically the text on spanned row ?

Hello @David22,

For vertically aligning, you could give it a alignItems: center, id try with using the css stylesheet and target that component.

As far as the condition, I’d make sure that condition takes into account the spanned rows. Just log the params in the condition and see what is being passed as the params.

I’d also recommend trying cellClassRules vs cellStyling. Just my personal opinion. :blush:

Hi @jinnyzor

Thanks for you reply. I compared the CSS of 2 cells belonging to the same “index header”;

top cell vs bottom cell CSS (relevant part only) before and after applying conditional cell style:

class="ag-cell ag-cell-not-inline-editing ag-cell-normal-height ag-column-first ag-cell-value" 
style="text-align: right; background-color: var(--ag-header-background-color); height: 50px; z-index: 1;">

class="ag-cell ag-cell-not-inline-editing ag-cell-normal-height ag-column-first ag-cell-value" 
style="text-align: right; background-color: var(--ag-header-background-color);">

After:

class="ag-cell ag-cell-not-inline-editing ag-cell-normal-height ag-column-first ag-cell-value" 
style="color: rgb(227, 227, 227); height: 50px; z-index: 1; ">

class="ag-cell ag-cell-not-inline-editing ag-cell-normal-height ag-column-first ag-cell-value"
style="color: rgb(227, 227, 227);">

Conclusions:

  1. With and without conditional style cell, the top cell’s height is 50px. If decreased to 25px, we see the text present in both cells.

  2. The single diff when comparing before and after is background-color, which must be reapplied independently of whether the condition is met or not. So,

{
    "field": "country", "minWidth": 50, "maxWidth": 100,
    "rowSpan": {"function": "params.node.id %2 === 0 ? 2 : 1"},
    "cellStyle": {
        # "backgroundColor": "var(--ag-header-background-color)",
        "styleConditions": [{
            "condition": "params.data['bronze'] > 21",
            "style": {
                "color": "red",
                "backgroundColor": "var(--ag-header-background-color)",
            }
        }],
        "defaultStyle":{"backgroundColor": "var(--ag-header-background-color)"}
    },
}

gives

  1. Regarding “align-items”: center, it indeed works but it is necessary to add “display”: grid too. Then:

Generally speaking, how do you “log the params” ? In the console ? How do you access this thing ? I’m not familiar with JS

Why do you suggest to use cellClassRules vs cellStyling ? Is it because class rules can be added the ones to the other, while cellStyling takes some kind of priority and overwrite class rules ?

Here is something that should work, for some reason it looks like the node.id was shifting around, so I used the node.rowIndex for this, however, you can use something applicable to your info:

style.css

.greater {
    color: red !important;
}

.hidden {
    display: none !important;
}

.spanning {
    background-color: var(--ag-header-background-color);
    display: flex !important;
    align-items: center !important;
}

app.py:

import dash_ag_grid as dag
import pandas as pd
from dash import Dash, html

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

# df = pd.read_excel("data/external_dummy_data_for_debug/olympic_winners.xlsx")

dfg = df.groupby(["country", "year"])[["bronze", "gold", "total"]].sum().reset_index()
df_agg_f = dfg[dfg["country"].isin(["France", "Germany", "Italy"]) & dfg["year"].isin([2008, 2012])].copy(deep=True)

columnDefs = [{
    "field": "country", "minWidth": 50, "maxWidth": 100,
    "rowSpan": {"function": "params.node.rowIndex %2 === 0 ? 2 : 1"},
    "cellClassRules": {
        "hidden": "params.node.rowIndex %2 !== 0",
        "greater": "params.data['bronze'] > 21",
        "spanning": "params.node.rowIndex %2 === 0"
    },
}, {"field": "year", "maxWidth": 80}, {"field": "bronze", "maxWidth": 80}, {"field": "gold", "maxWidth": 80}]

app = Dash()

app.layout = [
    html.Div([
        dag.AgGrid(
            id="row-spanning-simple-example",
            rowData=df_agg_f.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"sortable": False},
            columnSize="autoSize",
            dashGridOptions={"suppressRowTransform": True},
        ),
    ], style={"width": 400})
]


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

Thanks, indeed, clearer. I used rowSpan so no need of creating a class to hide anything;

I’ve been trying to figure out one last thing the whole afternoon. I’ld like to change a column header style if all the values in that column are equal to zero.

CSS:

.spanned-row-txt-vertically-centered {
    background-color: var(--ag-header-background-color);
    display: flex !important;
    align-items: center !important;
}

.partially-hidden {
    color: red !important;
}
import dash_ag_grid as dag
import pandas as pd
from dash import Dash, html

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

df = pd.read_excel("data/external_dummy_data_for_debug/olympic_winners.xlsx")

dfg = df.groupby(["country", "year"])[["bronze", "gold", "total"]].sum().reset_index()
df_agg_f = dfg[
    dfg["country"].isin(["France", "Germany", "Italy"]) & dfg["year"].isin([2008, 2012])
].copy(deep=True)
df_agg_f["choc"] = df_agg_f["gold"] * 0
columnDefs = [{
    "headerName": "",
    "field": "country", "minWidth": 50, "maxWidth": 100,
    "rowSpan": {"function": "params.node.id %2 === 0 ? 2 : 1"},
    "cellClassRules": {
        "partially-hidden": "params.data['bronze'] > 21",
        "spanned-row-txt-vertically-centered": "params.node.rowIndex %2 === 0"
    }
}, {
    "headerName": "", "field": "year", "maxWidth": 70
}, {
    "headerName": "real",
    "children": [{
        "headerName": "bronze #", "field": "bronze", "maxWidth": 90
    }, {
        "headerName": "gold #", "field": "gold", "maxWidth": 90
    }]
}, {
    "headerName": "fake",
    "children": [{
        "headerName": "choc #", "field": "choc", "maxWidth": 90, "cellStyle": {"function": "log(params)"}
    }]
}]

app = Dash()

app.layout = [
    html.Div([
        dag.AgGrid(
            id="row-spanning-simple-example",
            rowData=df_agg_f.to_dict("records"),
            columnDefs=columnDefs,
            defaultColDef={"sortable": False},
            columnSize="autoSize",
            dashGridOptions={"suppressRowTransform": True},
        ),
    ], style={"width": 500})
]


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

In the above example, how to get a blue background for “fake” and “choc #” if and only if all the values in this column are 0 ?

I tried with a function like this:

    "cellStyle": {
        "function": """
            function(params) {
                // Check if the current cell is in the first row
                if (params.node.rowIndex === 0) {
                    // Check if all values in the 'choc' column are zero
                    let allZero = true;
                    params.api.forEachNode(function(node) {
                        if (node.data.choc !== 0) {
                            allZero = false;
                        }
                    });

                    // Apply green background if all values are zero
                    if (allZero) {
                        return { backgroundColor: 'green' };
                    }
                }
                return null;
            }
        """

But it does not make anything. I assume there is no other choice than iterating over all the row of the relevant column, and then apply a conditional style. In my case there is only 10-15 rows, so, it should’nt be a problem from a memory standpoint. Provided that the code works…

In such a case, would it be better to add a “dummy” row containing the total of each column in the original dataframe, then hide it when the grid is rendered, but use the content of the last row to apply a conditional style on the header?
That sounds more like a workaround than a fix. If I wished to use some JS instead, how to proceed ? Is "params.ap.forEachNode the way to go ?

You could do this before passing to the grid…

If you arent editable, then it is definitely in the realm of possibility.

Otherwise, you could attach a callback to cellValueChanged and pull the rowData to compare…