I recently had to build a Dash Ag Grid where multiple columns interact i.e. changing Width or Length changes the calculated Area and changing Area changes either Width or Length correspondingly. This was slightly confsuing so here is a simple example of how to do it.
To get started
uv init
uv add pandas dash_ag_grid
Then run the code using
uv run main.py
main.py:
import dash_ag_grid as dag
import pandas as pd
from dash import Dash, Input, Output, html
app = Dash()
rowData = [
{
"Name": "Iris-setosa",
"Sepal": {"Width": 3.5, "Length": 5.1},
"Petal": {"Width": 0.2, "Length": 1.4},
},
{
"Name": "Iris-setosa",
"Sepal": {"Width": 3.0, "Length": 4.9},
"Petal": {"Width": 0.2, "Length": 1.4},
},
]
columnDefs = [
{"field": "Name"},
]
app.layout = html.Div(
[
html.Button("Update Row Data", id="update-row-data"),
dag.AgGrid(
id="column-definitions-nested-data",
rowData=rowData,
defaultColDef={"filter": True, "editable": True, "flex": 1},
columnDefs=columnDefs,
columnSize="responsiveSizeToFit",
dashGridOptions={"animateRows": False},
),
]
)
@app.callback(
[
Output("column-definitions-nested-data", "rowData"),
Output("column-definitions-nested-data", "columnDefs"),
Output("column-definitions-nested-data", "columnSize"),
],
[
Input("update-row-data", "n_clicks"),
Input("column-definitions-nested-data", "rowData"),
],
prevent_initial_call=False,
)
def update_row_data(n_clicks, rowData):
columnDefs = [
{"field": "Name", "width": 200},
{
"field": "Sepal.Width",
"valueSetter": {"function": "SetParams(params)"},
},
{
"field": "Sepal.Length",
"valueSetter": {"function": "SetParams(params)"},
},
{
"field": "Area",
"valueFormatter": {"function": "numberFormatter(params)"},
"valueSetter": {
"function": "SetParams(params)"
}, # Called when I manually set the Area
},
]
df = pd.DataFrame.from_dict(rowData)
# # df["Area"] = df["Sepal.Width"] * df["Sepal.Length"]
# # df = df.explode(["Sepal", "Petal"])
print(df)
print(df.columns)
# print(datetime.datetime.now())
# rowData = df.to_dict(orient="records")
return rowData, columnDefs, "responsiveSizeToFit"
if __name__ == "__main__":
app.run(debug=True, port="8051")
assets/dashAgGridFunctions.js
var dagfuncs = (window.dashAgGridFunctions = window.dashAgGridFunctions || {});
dagfuncs.SetParams = (params) => {
/**
* Calculate the area based on the width and length fields.
* @param {Object} params - The parameters containing data and column definitions.
* @returns {boolean}
* - Returns true if the area was successfully calculated and set, otherwise returns false.
*/
// Start by setting the new value for the current column
// Determine the active column
const activeColumn = params.column.colId;
const parts = activeColumn.split(".");
if (parts.length > 1) {
let obj = params.data;
for (let i = 0; i < parts.length - 1; i++) {
obj = obj[parts[i]];
}
obj[parts[parts.length - 1]] = params.newValue;
} else {
params.data[activeColumn] = params.newValue;
}
console.log("Setting params for column:", activeColumn, "with value:", params.newValue);
if (activeColumn === "Area") {
// params.data.Area = params.newValue;
if (params.data.Sepal.Width != 0) {
params.data.Sepal.Length = params.newValue / params.data.Sepal.Width; // Example of setting another field based on Area
}
else if (params.data.Sepal.Length != 0) {
params.data.Sepal.Width = params.newValue / params.data.Sepal.Length;
}
else {
// If both are null, we can't calculate anything
params.data.Sepal.Width = Math.sqrt(params.data.Area);
params.data.Sepal.Length = Math.sqrt(params.data.Area);
}
}
// Then calculate derived values
return dagfuncs.SetArea(params);
};
dagfuncs.SetArea = (params) => {
/**
* Calculate the area based on the width and length fields.
* @param {Object} params - The parameters containing data and column definitions.
* @returns {boolean}
* - Returns true if the area was successfully calculated and set, otherwise returns false.
*/
const width = params.data.Sepal.Width;
const length = params.data.Sepal.Length;
if (width != null && length != null) {
params.data.Area = width * length;
return true;
}
return false;
};
Now you can load the example and change Width, Length or Area freely. In the callback the value is also returned so you can use it in your Python code.