Ag-grid row wise styling

Does anyone know how to style a ag-grid table in Dash row wise? I currently use a jupyter notebook and ipyaggrid.Grid to generate a ag-grid based datatable. When defining columnDefs for my Grid object, I could pass a Javascript function as a string to the cellRenderer key (like shown in the code below).

I tried to move the same table to a plotly dash app using the open source AgGrid component from dash_ag_grid. When running the app I get following error in the browser console when passing same javascript string to columnDef’s cellRenderer in AgGrid:

AG Grid: Looking for component [
function (params){…
…] but it wasn’t found. Have you registered the component?

Does someone know how to pass a function to the cellRenderer or how to style ag-grid tables in general in Dash row wise?

# define javascript function to style column "Item"

cell_renderer_items = '''
function (params){
    
    let v = params.value;


    // header (bold)
        if (['Sales','Gross Profit','EBITDA','Operating Profit (EBIT)','Net Operating Profit After Tax (NOPAT)',
              'FCF Yield (%)','ROE',  'ROIC unadjusted','ROIC adjusted', 'EVA (ROIC unadjusted - WACC)',
              'EVA (ROIC adjusted - WACC)','EV/IC','P/B'].indexOf(params.node.data.Item) >= 0) { 
            return `<div style = 'text-align:left'><b>${v}</b></div>`;
    }
    // sub-item important (bold, italic & margin-left)
        else if (['Gross Margin (%)','EBITDA Margin (%)','EBIT Margin (OPM) (%)',
        'NOPAT Margin (%)'].indexOf(params.node.data.Item) >= 0) { 
        return `<div style = 'text-align:left'><span style='margin-left: 1rem'><b><i>${v}</i></b></span></div>`;
    }
    // sub-item (normal -> margin-left)
        else if (['Current Rental Expense','Rental Expense Year 1','Rental Expense Year 2','Rental Expense Year 3',
                   'Rental Expense Year 4','Rental Expense Year 5','Rental Expense Beyond Year 5',
                   'Total IC adjusted','NOPAT adjusted'].indexOf(params.node.data.Item) >= 0) { 
            return `<div'><span style='margin-left: 1rem'>${v}</span></div>`;
        }
    // sub-item (italic -> margin-left)
    else if (['YoY Sales Growth (%)','YoY COGS Growth (%)','YoY SG&A Growth (%)','YoY D&A Growth (%)',
              'YoY OPM Growth (bps)','YoY NOPAT Margin Growth (bps)','Goodwill to EV (%)','Capex to Sales (%)',
              'Capex to Depreciation (%)','FCF Margin (%)','Equity','NOPAT','Invested Capital (IC)'].indexOf(params.node.data.Item) >= 0) { 
        return `<div'><span style='margin-left: 1rem'><i>${v}</i></span></div>`;
    }
    // sub-item (italic -> margin-left)
    else if (['Net Income','Interest Expense on Debt','Nonoperating Income (Net)'].indexOf(params.node.data.Item) >= 0) { 
        return `<div'><span style='margin-left: 2rem'><i>${v}</i></span></div>`;
    }
        // main header (grey background, text color white, bold)
    else if (['Profit & Loss','Balance Sheet','Cash Flow','DuPont Analysis','Profitability','Key Multiples'].indexOf(params.node.data.Item) >= 0) { 
            return `<div style = 'text-align:center'><span style='color: white'><b>${v}</b></span></div>`;
    }
    // header (normal)
    else {
        return v;
    }      
}

'''
# define javascript function to allow row based cell editing

cell_editable_func = '''
function (params){
if (['Profit & Loss','Balance Sheet','Cash Flow','DuPont Analysis','Profitability','Key Multiples',
    'YoY Sales Growth (%)','YoY COGS Growth (%)',
    'Gross Profit','Gross Margin (%)','YoY SG&A Growth (%)','EBITDA Margin (%)','YoY D&A Growth (%)','EBIT Margin (OPM) (%)',
    'YoY OPM Growth (bps)','Net Operating Profit After Tax (NOPAT)','NOPAT Margin (%)','YoY NOPAT Margin Growth (bps)',
    'Goodwill to EV (%)','Capex to Sales (%)','Capex to Depreciation (%)','Net Debt/EBITDA','ROE','Total Equity',
    'Net Pension Liabilities to EV (%)','FCF','FCF Margin (%)','Cash conversion (FCF/EBITDA)','Cash conversion (CFO/EBITDA)',
    'Operating Profit Margin (OPM)','ROIC unadjusted','NOPAT','Invested Capital (IC)','PV Future Lease Obligations',
    'Total IC adjusted','NOPAT adjusted','ROIC adjusted','EVA (ROIC unadjusted - WACC)','EVA (ROIC adjusted - WACC)','EV/IC',
    'EV/Sales (T12M)','EV/EBITDA (T12M)','P/S','P/E','EPS (Growth)'].indexOf(params.node.data.Item) >= 0)
    {
        return false;
    }
else{
        return true;
    }

}
'''

column_defs = [
{'field': 'Item', 'type': 'textColumn', 'editable': False, 'pinned': 'left','lockPinned': True,
                        'width':280,'suppressSizeToFit': True, 'cellRenderer': cell_renderer_items}]

for col in df.columns:
     if col != 'Item':
         column_defs.append({'field': col, 'type': 'numberColumn', 'suppressMovable': True, 'editable': cell_editable_func})

Grid(
   grid_data = df.reset_index()
  ,grid_options = {'columnDefs':column_defs}
)

The resulting grid looks like this:

1 Like

Hello @merlinho,

Welcome to the community!

I have been working on row styling formatting, and how to make the functions work.

It will be similar to how the cellStyles work, with a condition and style that you want to apply.

Currently, this is not available, it is in my current PR for the grid.

In the mean time, you can use rowClassRules, but we will not allow these in the future because of a security risk for JavaScript eval, unless you turn on those features via dangerously_allow_code.

3 Likes

Hello @merlinho,

I was mistaken, you can use rowstyle formatting, check here:

2 Likes

Hi @jinnyzor

Thanks for helping out :slight_smile:

I will have a try with row style formatting!

1 Like

Hi @jinnyzor

I managed to style the “Item” column with cellStyling…

       # header (bold)
        item_header = [
            {
                "condition": f"value == '{item}'",
                "style": {"backgroundColor":"#969696", "color": "white", 'text-align':'center','font-weight': 'bold'},
            }
            for item in ['Profit & Loss','Balance Sheet','Cash Flow','DuPont Analysis','Profitability','Key Multiples']
        ]

        # sub-item important (bold, italic & margin-left)
        item_sub_item_important =[
            {
                "condition": f"value == '{item}'",
                "style": {"margin-left": "1rem", 'font-style':'italic','font-weight': 'bold'},
            }
            for item in ['Gross Margin (%)','EBITDA Margin (%)','EBIT Margin (OPM) (%)',
                           'NOPAT Margin (%)']
        ]

        # sub-item (italic -> margin-left)
        item_sub_item_italic = [
            {
                "condition": f"value == '{item}'",
                "style": {"margin-left": "1rem", 'font-style':'italic'},
            }
            for item in ['YoY Sales Growth (%)','YoY COGS Growth (%)','YoY SG&A Growth (%)','YoY D&A Growth (%)',
                         'YoY OPM Growth (bps)','YoY NOPAT Margin Growth (bps)','Goodwill to EV (%)','Capex to Sales (%)',
                         'Capex to Depreciation (%)','FCF Margin (%)','Equity','NOPAT','Invested Capital (IC)']
        ]

styleConditions =  item_header + item_sub_item_important  + item_sub_item_italic

cellStyle = {
            "styleConditions": styleConditions
        }


AgGrid(
....
   ,cellStyle=cellStyle
....
)
#

…but I actually don’t know how to get a row wise value formatting as well as defining cell editable functionality with the getRowStyle property.

I tried something like:

AgGrid(
....
,getRowStyle = {
  "styleConditions":[{
                    "condition" : f'''value == '{item}' '''
                   ,"editable" : False   # does not work        
} for item in ['Profit & Loss','Balance Sheet','Cash Flow','DuPont Analysis','Profitability','Key Multiples'] ]
}
....
)

which does not work since you can only pass stylings (e.g. “style”: “background-color”:“red”) to the “styleConditions” object. It would be nice to customize valueFormatting or cell editable flag for a row too.

If there are any other settings to get row wise value formatting and row wise cell editable functionality to work, I would be very thankful for any help and some code examples.

For the row styling, you need to reference something like f"data.<fieldname> == '{item}'"

Look at how this example is done:

getRowStyle = {
    "styleConditions": [
        {
            "condition": "data.sickDays > 5 && data.sickDays <= 7",
            "style": {"backgroundColor": "sandybrown"},
        },
        {"condition": "data.sickDays >= 8", "style": {"backgroundColor": "lightcoral"}},
    ]
}

value is what is available at the cell level, since the row level does not have a specific cell, it will not work. Thus you have to reference the params.data of the available function that this generates. This is simply represented as the data object in the condition statement.


As far as individual editing capabilities, that would be conditional editing in the column.

Which I can add something like that easily, it will just take some time to get accepted into the package.

4 Likes

Hi @jinnyzor

Thanks for your code but it will still not fix my problem, I guess. Or is it possible to pass some value formatters to the style property? As far as I know a function is needed to get formatted values back and a css styling will may handle background-color, text-font, color and so on but cannot format values …

If you could add conditional editing int the columnDefs, it would be great! :slight_smile:

By valueFormatters in the style, you mean using things like Number, d3, etc?

1 Like

exactly :slight_smile:

How can I achieve this kind of formatting row wise? If it is not possible with getRowStyle, it would be also nice to have a conditional valueFormatter in the columnDefs.

This isn’t available currently, but should be possible in future updates.

1 Like

You are thinking of changing the format of individual cells in the same column based upon what information is in the row?

col1 row1 = $, col1 row2 = %, col1 row3 = text?


This isnt possible on the row, but you can use something like a cellRendererSelector:

Again, this is currently not available right now.

However, with how we are in theory going to do this, you could manage all of this inside just the cellRenderer component.

1 Like

exactly, that’s what I am looking for!

Perfect I’m therefore looking forward to the next updates for the Ag-Grid component :).

Thanks again for your help! It is amazing on what nice tools you are working on. Thanks to the whole plotly dash team! :slight_smile:

2 Likes

Hello @merlinho,

Check out here, this is a way you can access this now.

Great! this will solve it! :slight_smile:

I actually run into another problem, I am sure that Dash Ag-Grid can already handle it, but I don’t know how to get it to work…

I want to highlight a cell based on values of other columns in the table. Let’s say, I want to change background color to green if a condition is true and red if false. Whenever values get edited, styling should update automatically.

E.g. highlight Comp. Name if Revenue < COGS (cost of goods sold):

image

Can that be achieved by using cellStyle?

In Dash DataTable this could be done via style_data_conditional:

style_data_conditional=[
        {
        {
            'if': {
                'filter_query': '{Revenue} < {COGS}',
                'column_id': 'Comp. Name'
            },
            'backgroundColor': 'red',
        },
            'if': {
                'filter_query': '{Revenue} > {COGS}',
                'column_id': 'Comp. Name'
            },
            'backgroundColor': 'green',
        }
]

Underneath cellStyles, you can query other columns by using, data.<column> in the conditions argument. :slight_smile:

2 Likes

I do not think data. works for aggregated rows.
@merlinho have you found a solution for styling an aggregated row based on a value of a column? If so, do you mind sharing it?

Hello @Drii,

Welcome to the community!

What exactly do you mean by styling the row based upon the value?

How have you tried?

Hi @jinnyzor! Thanks so much!

I am trying to style an ag-grid based upon a value from a column/field that is aggregated.

Think of a pivot table where the style of a row depends on the value of a different column. I tried using cellRendererSelector, but I could not get the function (translating it from aggrid-js to aggrid-dash) working either. Because when the row is grouped I believe params.data. is not available.

This what I am trying to achieve:

Ref.: JavaScript Data Grid: Group Cell Renderer

Ref.: [group-renderer-c & cellRendererSelector] JavaScript Data Grid: Group Cell Renderer

Have you tried passing a regular cellStyle to the autoGroupColumnDef?

Also, we added a log() functionality where you can print to the JavaScript console the available information.

Check out this section here:

1 Like