Dash AgGrid Dynamic Row Spanning

Are you ready for this?

Check this out, this will support sorting and filters:

app.py

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

app = Dash(__name__)

data = [
    {
        'localTime': '5:00am',
        'show': {'name': 'Wake Up Dublin', 'presenter': 'Andrew Connell'},
        'a': 0.231, 'b': 0.523, 'c': 0.423, 'd': 0.527,
    },
    {'localTime': '5:15am',
     'show': {'name': 'Wake Up Dublin', 'presenter': 'Andrew Connell'},'a': 0.423, 'b': 0.452, 'c': 0.523, 'd': 0.543},
    {'localTime': '5:30am', 'show': {'name': 'Wake Up Dublin', 'presenter': 'Andrew Connell'},'a': 0.537, 'b': 0.246, 'c': 0.426, 'd': 0.421},
    {'localTime': '5:45am', 'show': {'name': 'Wake Up Dublin', 'presenter': 'Andrew Connell'},'a': 0.893, 'b': 0.083, 'c': 0.532, 'd': 0.983},
    {
        'localTime': '6:00am',
        'show': {'name': 'Pure Back In The Day', 'presenter': 'Kevin Flanagan'},
        'a': 0.231, 'b': 0.523, 'c': 0.423, 'd': 0.527,
    },
    {'localTime': '6:15am', 'a': 0.423, 'b': 0.452, 'c': 0.523, 'd': 0.543, 'show': {'name': 'Pure Back In The Day', 'presenter': 'Kevin Flanagan'},},
    {'localTime': '6:30am', 'a': 0.537, 'b': 0.246, 'c': 0.426, 'd': 0.421, 'show': {'name': 'Pure Back In The Day', 'presenter': 'Kevin Flanagan'},},
    {'localTime': '6:45am', 'a': 0.893, 'b': 0.083, 'c': 0.532, 'd': 0.983, 'show': {'name': 'Pure Back In The Day', 'presenter': 'Kevin Flanagan'},},
    {
        'localTime': '7:00am',
        'show': {'name': 'The Queens Breakfast', 'presenter': 'Tony Smith'},
        'a': 0.231, 'b': 0.523, 'c': 0.423, 'd': 0.527,
    },
    {'localTime': '7:15am', 'a': 0.423, 'b': 0.452, 'c': 0.523, 'd': 0.543, 'show': {'name': 'The Queens Breakfast', 'presenter': 'Tony Smith'},},
    {'localTime': '7:30am', 'a': 0.537, 'b': 0.246, 'c': 0.426, 'd': 0.421, 'show': {'name': 'The Queens Breakfast', 'presenter': 'Tony Smith'},},
    {'localTime': '7:45am', 'a': 0.893, 'b': 0.083, 'c': 0.532, 'd': 0.983, 'show': {'name': 'The Queens Breakfast', 'presenter': 'Tony Smith'},},
    {
        'localTime': '8:00am',
        'show': {'name': 'Cosmetic Surgery', 'presenter': 'Niall Crosby'},
        'a': 0.231, 'b': 0.523, 'c': 0.423, 'd': 0.527,
    },
    {'localTime': '8:15am', 'a': 0.423, 'b': 0.452, 'c': 0.523, 'd': 0.543, 'show': {'name': 'Cosmetic Surgery', 'presenter': 'Niall Crosby'},},
    {'localTime': '8:30am', 'a': 0.537, 'b': 0.246, 'c': 0.426, 'd': 0.421, 'show': {'name': 'Cosmetic Surgery', 'presenter': 'Niall Crosby'},},
    {'localTime': '8:45am', 'a': 0.893, 'b': 0.083, 'c': 0.532, 'd': 0.983, 'show': {'name': 'Cosmetic Surgery', 'presenter': 'Niall Crosby'},},
    {
        'localTime': '8:00am',
        'show': {'name': 'Brickfield Park Sessions', 'presenter': 'Bricker McGee'},
        'a': 0.231, 'b': 0.523, 'c': 0.423, 'd': 0.527,
    },
    {'localTime': '8:15am', 'a': 0.423, 'b': 0.452, 'c': 0.523, 'd': 0.543, 'show': {'name': 'Brickfield Park Sessions', 'presenter': 'Bricker McGee'},},
    {'localTime': '8:30am', 'a': 0.537, 'b': 0.246, 'c': 0.426, 'd': 0.421, 'show': {'name': 'Brickfield Park Sessions', 'presenter': 'Bricker McGee'},},
    {'localTime': '8:45am', 'a': 0.893, 'b': 0.083, 'c': 0.532, 'd': 0.983, 'show': {'name': 'Brickfield Park Sessions', 'presenter': 'Bricker McGee'},},
]

columnDefs = [
    {'field': 'localTime'},
    {
        'field': 'show',
        'cellRenderer': "RowSpanningComplexCellRenderer",
        'rowSpan': {"function": "rowSpanningComplex(params, 'show')"},
        'cellClassRules': {"show-cell": "params.value && params.data.rowSpanning"},
        "width": 300
    },
    {'field': 'a'},
    {'field': 'b'},
    {'field': 'c'},
    {'field': 'd'},
    {'field': 'rowSpanning', 'hide': True}
]

app.layout = html.Div(
    [
        dag.AgGrid(
            id="grid",
            rowData=data,
            columnDefs=columnDefs,
            defaultColDef={"resizable": True, 'sortable': True, 'filter': True, 'flex': 1},
            dashGridOptions={"suppressRowTransform": True},
        ),
    ],
)

app.clientside_callback(
    """async function () {
        var api = await dash_ag_grid.getApiAsync("grid")
        api.redrawRows();        
        return dash_clientside.no_update
    }""",
    Output("grid", "id"),
    Input("grid", "virtualRowData"),
)

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

js file

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

dagfuncs.rowSpanningComplex = function (params, key) {
    spanning = 0
    var found;
    var firstFound;
    var included;
    params.api.forEachNodeAfterFilterAndSort((node) => {
        if (JSON.stringify(node.data[key]) == JSON.stringify(params.data[key]) || params.node.rowIndex == node.rowIndex) {
            if (!found && found != 0 && !included) {
                found = node.rowIndex
                firstFound = node.rowIndex
                if (found == params.node.rowIndex) {
                    included = true
                }
                spanning++
            } else {
                if ((node.rowIndex - found) == 1) {
                    found = node.rowIndex
                    if (found == params.node.rowIndex) {
                        included = true
                    }
                    spanning++
            }
        }
         }
         else if (!included) {
            found = null
            spanning = 0
        }
    })
    params.node.setDataValue('rowSpanning', included ? (firstFound == params.node.rowIndex ? spanning || 1 : 0) : 1)

    return included ? (firstFound == params.node.rowIndex ? spanning || 1 : 0) : 1;
}

var dagcomponentfuncs = (window.dashAgGridComponentFunctions = window.dashAgGridComponentFunctions || {});

dagcomponentfuncs.RowSpanningComplexCellRenderer = function (props) {
    let children;
    if (props.value && props.data.rowSpanning) {
        children = [
            React.createElement('div', {className: 'show-name'}, props.value.name),
            React.createElement('div', {className: 'show-presenter'}, props.value.presenter),
        ]
    }
    return React.createElement('div', null, children)
}

css file

.show-cell {
    background: #119dff;
    border-left: 1px solid lightgrey !important;
    border-right: 1px solid lightgrey !important;
    border-bottom: 1px solid lightgrey !important;
}

.show-name {
    font-weight: bold;
}

.show-presenter {
    font-style: italic;
}

3 Likes