Dash Ag Grid switch two cells by click

Hi everyone,

I have a data like this:

I want when I click the switch button, the corresponding values in column “Treatment” and “Comparator” will be switched. I write the code, however, when I click the button in the first row, it turns out:


I don’t why it adds a lot of repetitive rows.

Here is my code:

from dash import Dash, html, Input, Output, callback, State
import dash_ag_grid as dag
import os
import pandas as pd
import numpy as np
import itertools
import dash_mantine_components as dmc
import dash_iconify
import json



app = Dash(__name__)




@callback(
    Output("grid_treat_compare", "rowData"),
    Input("grid_treat_compare", "cellRendererData"),
    State("grid_treat_compare", "rowData"),
)
def showChange(n,rawdat):
    rawdat = pd.DataFrame(rawdat)
    dict = n
    if dict is not None and dict['colId'] == 'switch':
        rowid = int(dict['rowIndex'])
        # Swap values in the treatment and comparator columns for the given row
        treatment = rawdat.loc[rowid, 'treatment']
        comparator = rawdat.loc[rowid, 'comparator']
        rawdat.loc[rowid, 'treatment'] = comparator
        rawdat.loc[rowid, 'comparator'] = treatment 

    return rawdat.to_dict("records")




treat_compare={'treatment': {0: 'ADA', 1: 'ADA', 2: 'ADA', 3: 'ADA', 4: 'ADA'},
 'comparator': {0: 'APRE', 1: 'BIME', 2: 'BRODA', 3: 'CERTO', 4: 'CICLO'}}

treat_compare = pd.DataFrame(treat_compare)

ColumnDefs = [
   
    {"headerName": "Treatment", 
     "field": "treatment",
     "editable": False,
     "resizable": False,
     'cellStyle': {
        'background-color': '#a6d4a6bd'}
     },
    
     {"headerName": "Switch", 
     "field": "switch",
     "editable": False,
     "resizable": False,
     "cellRenderer": "DMC_Button",
     "cellRendererParams": {
            # "variant": "text",
            "icon": "subway:round-arrow-3",  # Use 'icon' instead of 'leftIcon' or 'rightIcon'
            "color": "#ffc000",
        },
    #  'cellStyle': {
    #     'border-right': 'solid 0.8px'}
     },

    {"headerName": "Comparator", 
     "field": "comparator",
     "editable": False,
     "resizable": False,
     'cellStyle': {
        'background-color': '#a6d4a6bd'}
     },
]

treat_compare_grid = dag.AgGrid(
    id="grid_treat_compare",
    # className="absolute-alpine color-fonts",
    # enableEnterpriseModules=True,
    # licenseKey=os.environ["AG_GRID_KEY"],
    columnDefs=ColumnDefs,
    rowData = treat_compare.to_dict("records"),
    dangerously_allow_code=True,
    defaultColDef={
                    'filter':False,
                    "floatingFilter": False,
                    "wrapText": True, 
                    # 'autoHeight': True,
                    "enableRowGroup": False,
                    "enableValue": False,
                    "enablePivot": False,
                    'cellStyle': {'white-space': 'pre',
                                  'display': 'grid',
                                  'text-align': 'center',
                                  'align-items': 'center',
                                  },
                    # "tooltipComponent": "CustomTooltip"
                    },
    columnSize="sizeToFit", 
    getRowId='params.data.Reference', 
)


app.layout = html.Div(
    [
        treat_compare_grid,
        html.Div(id='output')
    ]
)

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

And here is the js file for cellRenderer:

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

dagcomponentfuncs.DMC_Button = function (props) {
    const { setData } = props;

    function onClick() {
        setData();
    }

    // Create the icon element
    let icon;
    if (props.icon) {
        icon = React.createElement(window.dash_iconify.DashIconify, {
            icon: props.icon,
            style: {
                color: props.color, // Apply color directly to the icon
                fontSize: '24px' // Adjust size as needed
            },
        });
    }

    // Return a minimal button with no background or borders
    return React.createElement(
        "div", // Change from a button to a div or span for no button appearance
        {
            onClick,
            style: {
                background: 'none',   // No background
                border: 'none',       // No border
                cursor: 'pointer',    // Pointer cursor for clickability
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
            },
        },
        icon // Use only the icon as content
    );
};

Thank you for your help!!!

Hi @leatqy

You can actually do this all within the onClick handler in the cell renderer. No need for the dash callback. Try changing your render like this:


dagcomponentfuncs.DMC_Button = function (props) {
    const { setData, data } = props;

    function onClick() {       
        const temp = data.treatment;
        props.node.setDataValue( "treatment", data.comparator );
        props.node.setDataValue( "comparator", temp );
        setData();
    }

    // Create the icon element
    let icon;
    if (props.icon) {
        icon = React.createElement(window.dash_iconify.DashIconify, {
            icon: props.icon,
            style: {
                color: props.color, // Apply color directly to the icon
                fontSize: '24px' // Adjust size as needed
            },
        });
    }

    // Return a minimal button with no background or borders
    return React.createElement(
        "div", // Change from a button to a div or span for no button appearance
        {
            onClick,
            style: {
                background: 'none',   // No background
                border: 'none',       // No border
                cursor: 'pointer',    // Pointer cursor for clickability
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
            },
        },
        icon // Use only the icon as content
    );
};




More info in the docs here:

Thank you very much. It’s very helpful. But can you tell me what’s wrong with the callback function?

Hi @leatqy

The issue is the row-ids. You are using:

getRowId=‘params.data.Reference’,

but the Reference column does not exist in the data.

Be sure to make it a column that is not editable. For example you could add a column using the df index like this:



treat_compare = {'treatment': {0: 'ADA', 1: 'ADA', 2: 'ADA', 3: 'ADA', 4: 'ADA'},
                 'comparator': {0: 'APRE', 1: 'BIME', 2: 'BRODA', 3: 'CERTO', 4: 'CICLO'}}

treat_compare = pd.DataFrame(treat_compare)
treat_compare["index"] = treat_compare.index


Then add it to the grid like this:

getRowId="params.data.index",

Note there is a better and more efficient way to update the grid using row transactions:

2 Likes

Thank you very much!!!