diff --git a/vizro-core/examples/kpi/app.py b/vizro-core/examples/kpi/app.py index 8cfa06ee4..95ef241ac 100644 --- a/vizro-core/examples/kpi/app.py +++ b/vizro-core/examples/kpi/app.py @@ -2,11 +2,11 @@ import pandas as pd import vizro.models as vm -from utils._charts import COLUMN_DEFS, KPI, bar, choropleth, line, pie +from dash import Input, Output, callback +from utils._charts import COLUMN_DEFS, KPI, AgGridPage, bar, choropleth, infinite_scroll_ag_grid, line, pie from utils._helper import clean_data_and_add_columns from vizro import Vizro from vizro.actions import filter_interaction -from vizro.tables import dash_ag_grid # DATA -------------------------------------------------------------------------------------------- df_complaints = pd.read_csv("https://query.data.world/s/glbdstahsuw3hjgunz3zssggk7dsfu?dws=00000") @@ -217,14 +217,15 @@ ], ) -page_table = vm.Page( +page_table = AgGridPage( title="List of complaints", components=[ vm.AgGrid( - figure=dash_ag_grid( + figure=infinite_scroll_ag_grid( + id="kpi_grid", data_frame=df_complaints, columnDefs=COLUMN_DEFS, - dashGridOptions={"pagination": True}, + getRowId="params.data.Complaint ID", ) ) ], @@ -244,5 +245,101 @@ ), ) + +# CALLBACKS ----------------------------------------------------------------------------------- +operators = { + "greaterThanOrEqual": "ge", + "lessThanOrEqual": "le", + "lessThan": "lt", + "greaterThan": "gt", + "notEqual": "ne", + "equals": "eq", +} + + +def filter_df(df, data, col): + if data["filterType"] == "date": + crit1 = data["dateFrom"] + crit1 = pd.Series(crit1).astype(df[col].dtype)[0] + if "dateTo" in data: + crit2 = data["dateTo"] + crit2 = pd.Series(crit2).astype(df[col].dtype)[0] + else: + crit1 = data["filter"] + crit1 = pd.Series(crit1).astype(df[col].dtype)[0] + if "filterTo" in data: + crit2 = data["filterTo"] + crit2 = pd.Series(crit2).astype(df[col].dtype)[0] + if data["type"] == "contains": + df = df.loc[df[col].str.contains(crit1)] + elif data["type"] == "notContains": + df = df.loc[~df[col].str.contains(crit1)] + elif data["type"] == "startsWith": + df = df.loc[df[col].str.startswith(crit1)] + elif data["type"] == "notStartsWith": + df = df.loc[~df[col].str.startswith(crit1)] + elif data["type"] == "endsWith": + df = df.loc[df[col].str.endswith(crit1)] + elif data["type"] == "notEndsWith": + df = df.loc[~df[col].str.endswith(crit1)] + elif data["type"] == "inRange": + if data["filterType"] == "date": + df = df.loc[df[col].astype("datetime64[ns]").between_time(crit1, crit2)] + else: + df = df.loc[df[col].between(crit1, crit2)] + 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)] + return df + + +@callback( + Output("kpi_grid", "getRowsResponse"), + Input("kpi_grid", "getRowsRequest"), +) +def infinite_scroll(request): + dff = df_complaints.copy() + + if request: + if request["filterModel"]: + fils = request["filterModel"] + for k in fils: + try: + if "operator" in fils[k]: + if fils[k]["operator"] == "AND": + dff = filter_df(dff, fils[k]["condition1"], k) + dff = filter_df(dff, fils[k]["condition2"], k) + else: + dff1 = filter_df(dff, fils[k]["condition1"], k) + dff2 = filter_df(dff, fils[k]["condition2"], k) + dff = pd.concat([dff1, dff2]) + else: + dff = filter_df(dff, fils[k], k) + except: + pass + dff = dff + + if request["sortModel"]: + sorting = [] + asc = [] + for sort in request["sortModel"]: + sorting.append(sort["colId"]) + if sort["sort"] == "asc": + asc.append(True) + else: + asc.append(False) + dff = dff.sort_values(by=sorting, ascending=asc) + + lines = len(dff.index) + if lines == 0: + lines = 1 + + partial = dff.iloc[request["startRow"] : request["endRow"]] + return {"rowData": partial.to_dict("records"), "rowCount": lines} + + if __name__ == "__main__": Vizro().build(dashboard).run() diff --git a/vizro-core/examples/kpi/utils/_charts.py b/vizro-core/examples/kpi/utils/_charts.py index d6ffd5147..91c9bce61 100644 --- a/vizro-core/examples/kpi/utils/_charts.py +++ b/vizro-core/examples/kpi/utils/_charts.py @@ -2,12 +2,15 @@ from typing import List, Literal, Optional +import dash_ag_grid as dag import dash_bootstrap_components as dbc import pandas as pd import vizro.models as vm import vizro.plotly.express as px from dash import html from vizro.models.types import capture +from vizro.tables._dash_ag_grid import _DATA_TYPE_DEFINITIONS +from vizro.tables._utils import _set_defaults_nested # CUSTOM COMPONENTS ------------------------------------------------------------- @@ -40,6 +43,13 @@ def build(self): ) +class AgGridPage(vm.Page): + """Page without the on page load mechanism.""" + + def pre_build(self): + pass + + # CUSTOM CHARTS ---------------------------------------------------------------- @capture("graph") def bar( @@ -191,3 +201,36 @@ def choropleth( }, {"field": "Timely response?", "cellRenderer": "markdown", "headerName": "On time?", "flex": 3}, ] + + +@capture("ag_grid") +def infinite_scroll_ag_grid(data_frame: pd.DataFrame, **kwargs) -> dag.AgGrid: + """Implementation of infinite scroll AgGrid with sensible defaults to be used in [`AgGrid`][vizro.models.AgGrid].""" + defaults = { + "className": "ag-theme-quartz-dark ag-theme-vizro", + "columnDefs": [{"field": col} for col in data_frame.columns], + "rowModelType": "infinite", + "defaultColDef": { + "resizable": True, + "sortable": True, + "filter": True, + "filterParams": { + "buttons": ["apply", "reset"], + "closeOnApply": True, + }, + }, + "dashGridOptions": { + "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, + "animateRows": False, + "rowSelection": "multiple", + "rowBuffer": 0, + "cacheBlockSize": 100, + "cacheOverflowSize": 2, + "maxConcurrentDatasourceRequests": 2, + "infiniteInitialRowCount": 100, + "maxBlocksInCache": 10, + }, + "style": {"height": "100%"}, + } + kwargs = _set_defaults_nested(kwargs, defaults) + return dag.AgGrid(**kwargs)