Hi,
I am currently using the dash ag grid to create a dash app and finding it very useful. However, I am unsure how to implement the following feature and would appreciate any help.
I would like to implement a feature where if a certain column in a group row is modified when Row Groupings are done, all the column data within that group is modified.
For example, if you run my provided dash app below, all data cells can be edited, but the row representing the group is blocked to edit.
I would like to modify the cell in the red box to change all the column data in that group to the same value.
I am sharing my current version of the dash app code (app.py, assets/dashAgGrid.js). I am using the rowModel as serverSide.
Thank you always.
#app.py
from dash import Dash, Input, Output, html, dcc, State
import dash_ag_grid as dag
import requests, json
import flask
import pandas as pd
import pprint
app = Dash(__name__)
server = app.server
df = pd.read_csv(
#"https://raw.githubusercontent.com/plotly/datasets/master/ag-grid/olympic-winners.csv"
"./datasets/olympic-winners.csv"
)
df = df.dropna()
rowData = df.to_dict("records")
def filterDf(df, data, col):
operators = {
"greaterThanOrEqual": "ge",
"lessThanOrEqual": "le",
"lessThan": "lt",
"greaterThan": "gt",
"notEqual": "ne",
"equals": "eq",
}
if "filter" in data:
crit1 = data["filter"]
crit1 = pd.Series(crit1).astype(df[col].dtype)[0]
if "type" in data:
if data["type"] == "contains":
df = df.loc[df[col].str.contains(crit1, na=False)]
elif data["type"] == "notContains":
df = df.loc[~df[col].str.contains(crit1, na=False)]
elif data["type"] == "startsWith":
df = df.loc[df[col].str.startswith(crit1, na=False)]
elif data["type"] == "notStartsWith":
df = df.loc[~df[col].str.startswith(crit1, na=False)]
elif data["type"] == "endsWith":
df = df.loc[df[col].str.endswith(crit1, na=False)]
elif data["type"] == "notEndsWith":
df = df.loc[~df[col].str.endswith(crit1, na=False)]
elif data["type"] == "blank":
df = df.loc[df[col].isnull()]
elif data["type"] == "notBlank":
df = df.loc[~df[col].isnull()]
else:
df = df.loc[getattr(df[col], operators[data["type"]])(crit1)]
elif data["filterType"] == "set":
df = df.loc[df[col].isin(data["values"])]
return df
def extractRowsFromData(request, df):
response = []
pprint.pprint(request)
dff = df.copy()
if request["filterModel"]:
fils = request["filterModel"]
for k in fils:
try:
if "operator" in fils[k]:
if fils[k]["operator"] == "AND":
for fil in fils[k]["conditions"]:
dff = filterDf(dff, fil, k)
else:
dffs = []
for fil in fils[k]["conditions"]:
dffs.append(filterDf(dff, fil, k))
dff = pd.concat(dffs)
else:
dff = filterDf(dff, fils[k], k)
except:
pass
sorting = []
asc = []
if request["sortModel"]:
for sort in request["sortModel"]:
sorting.append(sort["colId"])
asc.append(sort["sort"] == "asc")
# dff = dff.sort_values(by=sorting, ascending=asc)
groupBy = []
if request["rowGroupCols"]:
groupBy = [i["id"] for i in request["rowGroupCols"]]
if groupBy:
group_counts = dff.groupby(groupBy).size().reset_index(name="childCount")
dff = dff.merge(group_counts, on=groupBy, how="left")
agg = {}
if request["valueCols"]:
agg = {i["id"]: i["aggFunc"] for i in request["valueCols"]}
if not request["groupKeys"]:
if groupBy:
if agg:
dff = dff.groupby(groupBy[0]).agg(agg).reset_index()
else:
dff = dff.groupby(groupBy[0]).agg("count").reset_index()
else:
for i in range(len(request["groupKeys"])):
dff = dff[dff[request["rowGroupCols"][i]["id"]] == request["groupKeys"][i]]
if len(request["groupKeys"]) != len(groupBy):
if agg:
dff = (
dff.groupby(groupBy[: len(request["groupKeys"]) + 1])
.agg(agg)
.reset_index()
)
else:
dff = (
dff.groupby(groupBy[: len(request["groupKeys"]) + 1])
.agg("count")
.reset_index()
)
if groupBy:
print(f"sorting: {sorting}")
print(f"asc: {asc}")
print(f"groupBy[0]: {groupBy[0]}")
if groupBy[0] in sorting:
print(f"dff: {dff.head()}")
print(f"asc[0]: {asc[0]}")
dff = dff.sort_values(by="childCount", ascending=asc[0])
else:
dff = dff.sort_values(by=sorting, ascending=asc)
else:
dff = dff.sort_values(by=sorting, ascending=asc)
print(f"{pprint.pformat(dff)}")
return {
"rowData": dff.to_dict("records")[request["startRow"] : request["endRow"]],
"rowCount": len(dff),
}
@server.route("/api/serverData", methods=["POST"])
def serverData():
response = extractRowsFromData(flask.request.json, df)
return json.dumps(response)
grid = html.Div(
[
dag.AgGrid(
id="grid",
columnDefs=[
{"field": "country"},
{"field": "year"},
{"field": "athlete"},
{"field": "age"},
{"field": "date"},
{"field": "sport"},
{"field": "total"},
# {"field": "childCount", "hide": True}, # hide default
],
defaultColDef={
"filter": True,
"sortable": True,
"resizable": True,
"enableRowGroup": True,
"enableValue": True,
"enablePivot": True,
'editable': True,
},
dashGridOptions={
"rowSelection": "multiple",
"sideBar": True,
"rowGroupPanelShow": "always",
"suppressRowGroupHidesColumns": True,
"getChildCount": {"function": "getChildCount(params)"},
},
enableEnterpriseModules=True,
rowModelType="serverSide",
style={"overflow": "auto", "resize": "both", "height": "60vh"},
),
]
)
app.layout = html.Div(
[
dcc.Markdown("Example: Organisational Hierarchy using Tree Data "),
grid,
]
)
app.clientside_callback(
"""async function (id) {
const updateData = (grid) => {
var datasource = createServerSideDatasource();
grid.setServerSideDatasource(datasource);
};
var grid;
grid = await window.dash_ag_grid.getApiAsync(id)
if (grid) {
updateData(grid)
}
return window.dash_clientside.no_update
}""",
Output("grid", "id"),
Input("grid", "id"),
)
if __name__ == "__main__":
app.run(debug=True)
# assets/dashAgGrid.js
async function getServerData(request) {
response = await fetch('./api/serverData', {'method': 'POST', 'body': JSON.stringify(request),
'headers': {'content-type': 'application/json'}})
return response.json()
}
function createServerSideDatasource() {
const dataSource = {
getRows: async (params) => {
console.log('ServerSideDatasource.getRows: params = ', params);
var result = await getServerData(params.request);
console.log('getRows: result = ', result);
params.success(result);
},
};
return dataSource;
}
var dagfuncs = window.dashAgGridFunctions = window.dashAgGridFunctions || {};
dagfuncs.getChildCount = function(data) {
// here child count is stored in the 'childCount' property
// console.log('data = ', data);
return data.childCount;
}