From 96b6259cecda7fa89682c3b04d42c2f67b70c13f Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 10 Jan 2024 12:54:07 +0100 Subject: [PATCH 001/128] Minimal viable AGGrid implementation --- vizro-core/examples/default/app_dev.py | 151 ++++++++++++++++++ vizro-core/pyproject.toml | 3 +- .../src/vizro/actions/_actions_utils.py | 31 +++- .../_callback_mapping_utils.py | 35 ++-- .../src/vizro/models/_components/table.py | 47 +++++- 5 files changed, 248 insertions(+), 19 deletions(-) create mode 100644 vizro-core/examples/default/app_dev.py diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py new file mode 100644 index 000000000..0bcdb340a --- /dev/null +++ b/vizro-core/examples/default/app_dev.py @@ -0,0 +1,151 @@ +"""Example to show dashboard configuration.""" + +import sys + +import dash_ag_grid as dag +import pandas as pd + +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro +from vizro.actions import export_data, filter_interaction + +####################################### +from vizro.models.types import capture +from vizro.tables import dash_data_table + +print("PYTHON EXECUTABLE", sys.executable) + + +@capture("action") +def AgGrid(data_frame=None): + """Custom AgGrid.""" + return dag.AgGrid( + id="get-started-example-basic", + rowData=data_frame.to_dict("records"), + columnDefs=[{"field": col} for col in data_frame.columns], + ) + + +####################################### + +df = px.data.gapminder() +df_mean = ( + df.groupby(by=["continent", "year"]).agg({"lifeExp": "mean", "pop": "mean", "gdpPercap": "mean"}).reset_index() +) + +df_transformed = df.copy() +df_transformed["lifeExp"] = df.groupby(by=["continent", "year"])["lifeExp"].transform("mean") +df_transformed["gdpPercap"] = df.groupby(by=["continent", "year"])["gdpPercap"].transform("mean") +df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") +df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) + + +def create_benchmark_analysis(): + """Function returns a page to perform analysis on country level.""" + # Apply formatting to table columns + columns = [ + {"id": "country", "name": "country"}, + {"id": "continent", "name": "continent"}, + {"id": "year", "name": "year"}, + {"id": "lifeExp", "name": "lifeExp", "type": "numeric", "format": {"specifier": ",.1f"}}, + {"id": "gdpPercap", "name": "gdpPercap", "type": "numeric", "format": {"specifier": "$,.2f"}}, + {"id": "pop", "name": "pop", "type": "numeric", "format": {"specifier": ",d"}}, + ] + + page_country = vm.Page( + title="Benchmark Analysis", + # description="Discovering how the metrics differ for each country and export data for further investigation", + # layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), + components=[ + vm.Table( + id="table_country_new", + title="Click on a cell in country column:", + figure=AgGrid( + data_frame=df, + ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Table( + id="table_country", + title="Click on a cell in country column:", + figure=dash_data_table( + id="dash_data_table_country", + data_frame=df, + columns=columns, + style_data_conditional=[ + { + "if": {"filter_query": "{gdpPercap} < 1045", "column_id": "gdpPercap"}, + "backgroundColor": "#ff9222", + }, + { + "if": { + "filter_query": "{gdpPercap} >= 1045 && {gdpPercap} <= 4095", + "column_id": "gdpPercap", + }, + "backgroundColor": "#de9e75", + }, + { + "if": { + "filter_query": "{gdpPercap} > 4095 && {gdpPercap} <= 12695", + "column_id": "gdpPercap", + }, + "backgroundColor": "#aaa9ba", + }, + { + "if": {"filter_query": "{gdpPercap} > 12695", "column_id": "gdpPercap"}, + "backgroundColor": "#00b4ff", + }, + ], + sort_action="native", + style_cell={"textAlign": "left"}, + ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Graph( + id="line_country", + figure=px.line( + df_concat, + title="Country vs. Continent", + x="year", + y="gdpPercap", + color="color", + labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, + color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, + markers=True, + hover_name="country", + ), + ), + vm.Button( + text="Export data", + actions=[ + vm.Action( + function=export_data( + targets=["line_country"], + ) + ), + ], + ), + ], + controls=[ + vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), + vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), + vm.Parameter( + targets=["line_country.y"], + selector=vm.Dropdown( + options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" + ), + ), + ], + ) + return page_country + + +dashboard = vm.Dashboard( + pages=[ + create_benchmark_analysis(), + ], +) + +if __name__ == "__main__": + Vizro(assets_folder="../assets").build(dashboard).run() diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 72a3d8a53..61631f8c3 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -28,7 +28,8 @@ dependencies = [ "tornado>=6.3.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-TORNADO-5537286 "setuptools>=65.5.1", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-SETUPTOOLS-3180412 "werkzeug>=3.0.1", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-WERKZEUG-6035177 - "MarkupSafe" # required to sanitize user input + "MarkupSafe", # required to sanitize user input, + "dash-ag-grid" ] description = "Vizro is a package to facilitate visual analytics." dynamic = ["version"] diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 7d4b3ba8d..c2e909fb2 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -111,7 +111,7 @@ def _get_parent_vizro_model(_underlying_callable_object_id: str) -> VizroBaseMod ) -def _apply_table_filter_interaction( +def _apply_dashtable_filter_interaction( data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: ctd_active_cell = ctd_filter_interaction["active_cell"] @@ -133,6 +133,26 @@ def _apply_table_filter_interaction( return data_frame +def _apply_aggrid_filter_interaction( + data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] +) -> pd.DataFrame: + ctd_cellClicked = ctd_filter_interaction["cellClicked"] + if not ctd_cellClicked["value"]: + return data_frame + + # ctd_active_cell["id"] represents the underlying table id, so we need to fetch its parent Vizro Table actions. + source_table_actions = _get_component_actions(_get_parent_vizro_model(ctd_cellClicked["id"])) + + for action in source_table_actions: + if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: + continue + column = ctd_cellClicked["value"]["colId"] + clicked_data = ctd_cellClicked["value"]["value"] + data_frame = data_frame[data_frame[column].isin([clicked_data])] + + return data_frame + + def _apply_filter_interaction( data_frame: pd.DataFrame, ctds_filter_interaction: List[Dict[str, CallbackTriggerDict]], @@ -147,7 +167,14 @@ def _apply_filter_interaction( ) if "active_cell" in ctd_filter_interaction and "derived_viewport_data" in ctd_filter_interaction: - data_frame = _apply_table_filter_interaction( + data_frame = _apply_dashtable_filter_interaction( + data_frame=data_frame, + target=target, + ctd_filter_interaction=ctd_filter_interaction, + ) + + if "cellClicked" in ctd_filter_interaction: + data_frame = _apply_aggrid_filter_interaction( data_frame=data_frame, target=target, ctd_filter_interaction=ctd_filter_interaction, diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index 2f14429b8..8cfd09966 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -8,7 +8,7 @@ from vizro.actions import _on_page_load, _parameter, export_data, filter_interaction from vizro.managers import data_manager, model_manager from vizro.managers._model_manager import ModelID -from vizro.models import Action, Page, Table, VizroBaseModel +from vizro.models import Action, Page, VizroBaseModel from vizro.models._action._actions_chain import ActionsChain from vizro.models._controls import Filter, Parameter from vizro.models.types import ControlType @@ -108,18 +108,27 @@ def _get_inputs_of_figure_interactions( for action in figure_interactions_on_page: # TODO: Consider do we want to move the following logic into Model implementation triggered_model = _get_triggered_model(action_id=ModelID(str(action.id))) - if isinstance(triggered_model, Table): - inputs.append( - { - "active_cell": State( - component_id=triggered_model._callable_object_id, component_property="active_cell" - ), - "derived_viewport_data": State( - component_id=triggered_model._callable_object_id, - component_property="derived_viewport_data", - ), - } - ) + if hasattr(triggered_model, "table_type"): # not check this, put this configuration inside the models + if triggered_model.table_type == "DataTable": + inputs.append( + { + "active_cell": State( + component_id=triggered_model._callable_object_id, component_property="active_cell" + ), + "derived_viewport_data": State( + component_id=triggered_model._callable_object_id, + component_property="derived_viewport_data", + ), + } + ) + elif triggered_model.table_type == "AgGrid": + inputs.append( + { + "cellClicked": State( + component_id=triggered_model._callable_object_id, component_property="cellClicked" + ), + } + ) else: inputs.append( { diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 5ee771261..d6b854446 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -5,14 +5,14 @@ from pandas import DataFrame try: - from pydantic.v1 import Field, PrivateAttr, validator + from pydantic.v1 import Field, PrivateAttr, root_validator, validator except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator import vizro.tables as vt from vizro.managers import data_manager from vizro.models import Action, VizroBaseModel -from vizro.models._action._actions_chain import _action_validator_factory +from vizro.models._action._actions_chain import _set_actions from vizro.models._components._components_utils import _process_callable_data_frame from vizro.models._models_utils import _log_call from vizro.models.types import CapturedCallable @@ -20,6 +20,18 @@ logger = logging.getLogger(__name__) +def _get_table_type(figure): # this function can be applied also in pre-build + kwargs = figure._arguments.copy() + + # This workaround is needed because the underlying table object requires a data_frame + kwargs["data_frame"] = DataFrame() + + # The underlying table object is pre-built, so we can fetch its ID. + underlying_table_object = figure._function(**kwargs) + table_type = underlying_table_object.__class__.__name__ + return table_type + + class Table(VizroBaseModel): """Wrapper for table components to visualize in dashboard. @@ -34,17 +46,42 @@ class Table(VizroBaseModel): type: Literal["table"] = "table" figure: CapturedCallable = Field(..., import_path=vt, description="Table to be visualized on dashboard") title: str = Field("", description="Title of the table") + # foo: str = Field(None, exclude=True) actions: List[Action] = [] _callable_object_id: str = PrivateAttr() + _table_type: str = ( + PrivateAttr() + ) # Ideally we would be able to use the populated content of this field in the `set_actions` validator. # Component properties for actions and interactions _output_property: str = PrivateAttr("children") # validator - set_actions = _action_validator_factory("active_cell") _validate_callable = validator("figure", allow_reuse=True, always=True)(_process_callable_data_frame) + @validator("actions") + def set_actions(cls, v, values): + table_type = _get_table_type(values["figure"]) + if table_type == "DataTable": + return _set_actions(v, values, "active_cell") + elif table_type == "AgGrid": + return _set_actions(v, values, "cellClicked") + else: + raise ValueError(f"Table type {table_type} not supported.") + + # set_actions = _action_validator_factory("cellClicked") # Need to make this sit with the captured callable + + # Approach similar to layout model - need to confirm if we can do without __init__ and populate at another time + def __init__(self, **data): + super().__init__(**data) + self._table_type = _get_table_type(self.figure) + + @property + def table_type(self): + return self._table_type + + # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) @@ -68,6 +105,7 @@ def pre_build(self): # The underlying table object is pre-built, so we can fetch its ID. underlying_table_object = self.figure._function(**kwargs) + table_type = underlying_table_object.__class__.__name__ if not hasattr(underlying_table_object, "id"): raise ValueError( @@ -76,6 +114,9 @@ def pre_build(self): ) self._callable_object_id = underlying_table_object.id + self._table_type = table_type + # Idea: fetch it from the functions attributes? Or just hard-code it here? Can check difference between AGGrid and dashtable because we call it already + # Once we recognise, two ways to go: 1) slightly change model properties 2) inject dash dependencies, def build(self): return dcc.Loading( From a74a28ff3f4802fa3b5e0735a8947b014413f357 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 10 Jan 2024 11:55:41 +0000 Subject: [PATCH 002/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/src/vizro/models/_components/table.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index d6b854446..d628a5939 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -20,7 +20,7 @@ logger = logging.getLogger(__name__) -def _get_table_type(figure): # this function can be applied also in pre-build +def _get_table_type(figure): # this function can be applied also in pre-build kwargs = figure._arguments.copy() # This workaround is needed because the underlying table object requires a data_frame @@ -81,7 +81,6 @@ def __init__(self, **data): def table_type(self): return self._table_type - # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) From 4878865ff0f438f740fc0d1a10dd73c4fc2fc01c Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 11 Jan 2024 16:22:01 +0100 Subject: [PATCH 003/128] Improve MVP --- vizro-core/examples/default/app_dev.py | 25 ++----------------- .../src/vizro/models/_components/table.py | 7 +++--- vizro-core/src/vizro/tables/__init__.py | 3 ++- vizro-core/src/vizro/tables/dash_aggrid.py | 12 +++++++++ 4 files changed, 20 insertions(+), 27 deletions(-) create mode 100644 vizro-core/src/vizro/tables/dash_aggrid.py diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index 0bcdb340a..48b485ae6 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -1,8 +1,4 @@ """Example to show dashboard configuration.""" - -import sys - -import dash_ag_grid as dag import pandas as pd import vizro.models as vm @@ -10,24 +6,7 @@ from vizro import Vizro from vizro.actions import export_data, filter_interaction -####################################### -from vizro.models.types import capture -from vizro.tables import dash_data_table - -print("PYTHON EXECUTABLE", sys.executable) - - -@capture("action") -def AgGrid(data_frame=None): - """Custom AgGrid.""" - return dag.AgGrid( - id="get-started-example-basic", - rowData=data_frame.to_dict("records"), - columnDefs=[{"field": col} for col in data_frame.columns], - ) - - -####################################### +from vizro.tables import dash_data_table, dash_ag_grid df = px.data.gapminder() df_mean = ( @@ -61,7 +40,7 @@ def create_benchmark_analysis(): vm.Table( id="table_country_new", title="Click on a cell in country column:", - figure=AgGrid( + figure=dash_ag_grid( data_frame=df, ), actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index d628a5939..5c39fe39e 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -5,7 +5,7 @@ from pandas import DataFrame try: - from pydantic.v1 import Field, PrivateAttr, root_validator, validator + from pydantic.v1 import Field, PrivateAttr, validator except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator @@ -114,8 +114,9 @@ def pre_build(self): self._callable_object_id = underlying_table_object.id self._table_type = table_type - # Idea: fetch it from the functions attributes? Or just hard-code it here? Can check difference between AGGrid and dashtable because we call it already - # Once we recognise, two ways to go: 1) slightly change model properties 2) inject dash dependencies, + # Idea: fetch it from the functions attributes? Or just hard-code it here? + # Can check difference between AGGrid and dashtable because we call it already + # Once we recognize, two ways to go: 1) slightly change model properties 2) inject dash dependencies, def build(self): return dcc.Loading( diff --git a/vizro-core/src/vizro/tables/__init__.py b/vizro-core/src/vizro/tables/__init__.py index 5a813ad8c..8216c605a 100644 --- a/vizro-core/src/vizro/tables/__init__.py +++ b/vizro-core/src/vizro/tables/__init__.py @@ -1,4 +1,5 @@ from vizro.tables.dash_table import dash_data_table +from vizro.tables.dash_aggrid import dash_ag_grid # Please keep alphabetically ordered -__all__ = ["dash_data_table"] +__all__ = ["dash_ag_grid","dash_data_table"] diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py new file mode 100644 index 000000000..9457fddb9 --- /dev/null +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -0,0 +1,12 @@ +from vizro.models.types import capture +import dash_ag_grid as dag + + +@capture("action") +def dash_ag_grid(data_frame=None): + """Custom AgGrid.""" + return dag.AgGrid( + id="get-started-example-basic", + rowData=data_frame.to_dict("records"), + columnDefs=[{"field": col} for col in data_frame.columns], + ) \ No newline at end of file From f9e14cb874eaffeb8040d6f15b75a97e6145304d Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:24:25 +0000 Subject: [PATCH 004/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/examples/default/app_dev.py | 3 +-- vizro-core/src/vizro/tables/__init__.py | 4 ++-- vizro-core/src/vizro/tables/dash_aggrid.py | 5 +++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index 48b485ae6..f91c256e7 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -5,8 +5,7 @@ import vizro.plotly.express as px from vizro import Vizro from vizro.actions import export_data, filter_interaction - -from vizro.tables import dash_data_table, dash_ag_grid +from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() df_mean = ( diff --git a/vizro-core/src/vizro/tables/__init__.py b/vizro-core/src/vizro/tables/__init__.py index 8216c605a..0c91e3346 100644 --- a/vizro-core/src/vizro/tables/__init__.py +++ b/vizro-core/src/vizro/tables/__init__.py @@ -1,5 +1,5 @@ -from vizro.tables.dash_table import dash_data_table from vizro.tables.dash_aggrid import dash_ag_grid +from vizro.tables.dash_table import dash_data_table # Please keep alphabetically ordered -__all__ = ["dash_ag_grid","dash_data_table"] +__all__ = ["dash_ag_grid", "dash_data_table"] diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 9457fddb9..f2a87eb4e 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -1,6 +1,7 @@ -from vizro.models.types import capture import dash_ag_grid as dag +from vizro.models.types import capture + @capture("action") def dash_ag_grid(data_frame=None): @@ -9,4 +10,4 @@ def dash_ag_grid(data_frame=None): id="get-started-example-basic", rowData=data_frame.to_dict("records"), columnDefs=[{"field": col} for col in data_frame.columns], - ) \ No newline at end of file + ) From 7d370c2248bb044a967b8d878dcfa54c9b17a259 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 11 Jan 2024 18:01:53 +0100 Subject: [PATCH 005/128] Remove super().__init__ and property --- .../_callback_mapping_utils.py | 6 ++--- .../src/vizro/models/_components/table.py | 25 +++---------------- 2 files changed, 6 insertions(+), 25 deletions(-) diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index 8cfd09966..944e26b38 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -108,8 +108,8 @@ def _get_inputs_of_figure_interactions( for action in figure_interactions_on_page: # TODO: Consider do we want to move the following logic into Model implementation triggered_model = _get_triggered_model(action_id=ModelID(str(action.id))) - if hasattr(triggered_model, "table_type"): # not check this, put this configuration inside the models - if triggered_model.table_type == "DataTable": + if hasattr(triggered_model, "_table_type"): # not check this, put this configuration inside the models + if triggered_model._table_type == "DataTable": inputs.append( { "active_cell": State( @@ -121,7 +121,7 @@ def _get_inputs_of_figure_interactions( ), } ) - elif triggered_model.table_type == "AgGrid": + elif triggered_model._table_type == "AgGrid": inputs.append( { "cellClicked": State( diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 5c39fe39e..acc0f3226 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -29,7 +29,7 @@ def _get_table_type(figure): # this function can be applied also in pre-build # The underlying table object is pre-built, so we can fetch its ID. underlying_table_object = figure._function(**kwargs) table_type = underlying_table_object.__class__.__name__ - return table_type + return underlying_table_object, table_type class Table(VizroBaseModel): @@ -46,7 +46,6 @@ class Table(VizroBaseModel): type: Literal["table"] = "table" figure: CapturedCallable = Field(..., import_path=vt, description="Table to be visualized on dashboard") title: str = Field("", description="Title of the table") - # foo: str = Field(None, exclude=True) actions: List[Action] = [] _callable_object_id: str = PrivateAttr() @@ -62,7 +61,7 @@ class Table(VizroBaseModel): @validator("actions") def set_actions(cls, v, values): - table_type = _get_table_type(values["figure"]) + _, table_type = _get_table_type(values["figure"]) if table_type == "DataTable": return _set_actions(v, values, "active_cell") elif table_type == "AgGrid": @@ -70,17 +69,6 @@ def set_actions(cls, v, values): else: raise ValueError(f"Table type {table_type} not supported.") - # set_actions = _action_validator_factory("cellClicked") # Need to make this sit with the captured callable - - # Approach similar to layout model - need to confirm if we can do without __init__ and populate at another time - def __init__(self, **data): - super().__init__(**data) - self._table_type = _get_table_type(self.figure) - - @property - def table_type(self): - return self._table_type - # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) @@ -97,14 +85,7 @@ def __getitem__(self, arg_name: str): @_log_call def pre_build(self): if self.actions: - kwargs = self.figure._arguments.copy() - - # This workaround is needed because the underlying table object requires a data_frame - kwargs["data_frame"] = DataFrame() - - # The underlying table object is pre-built, so we can fetch its ID. - underlying_table_object = self.figure._function(**kwargs) - table_type = underlying_table_object.__class__.__name__ + underlying_table_object, table_type = _get_table_type(self.figure) if not hasattr(underlying_table_object, "id"): raise ValueError( From c4229eaa72bb81379aadba7922c059fe5d5e1630 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 16 Jan 2024 10:15:24 +0100 Subject: [PATCH 006/128] Inital styling of AGGrid --- vizro-core/examples/default/app_dev.py | 74 +++++++++++----------- vizro-core/src/vizro/static/css/aggrid.css | 26 ++++++++ vizro-core/src/vizro/tables/dash_aggrid.py | 4 +- 3 files changed, 67 insertions(+), 37 deletions(-) create mode 100644 vizro-core/src/vizro/static/css/aggrid.css diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index f91c256e7..35c5d4df8 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -41,8 +41,10 @@ def create_benchmark_analysis(): title="Click on a cell in country column:", figure=dash_ag_grid( data_frame=df, + # className="ag-theme-alpine ag-theme-acmecorp", + className="ag-theme-custom-theme", ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), vm.Table( id="table_country", @@ -78,43 +80,43 @@ def create_benchmark_analysis(): sort_action="native", style_cell={"textAlign": "left"}, ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Graph( - id="line_country", - figure=px.line( - df_concat, - title="Country vs. Continent", - x="year", - y="gdpPercap", - color="color", - labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, - color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, - markers=True, - hover_name="country", - ), - ), - vm.Button( - text="Export data", - actions=[ - vm.Action( - function=export_data( - targets=["line_country"], - ) - ), - ], - ), - ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), - vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), - vm.Parameter( - targets=["line_country.y"], - selector=vm.Dropdown( - options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" - ), + # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), + # vm.Graph( + # id="line_country", + # figure=px.line( + # df_concat, + # title="Country vs. Continent", + # x="year", + # y="gdpPercap", + # color="color", + # labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, + # color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, + # markers=True, + # hover_name="country", + # ), + # ), + # vm.Button( + # text="Export data", + # actions=[ + # vm.Action( + # function=export_data( + # targets=["line_country"], + # ) + # ), + # ], + # ), ], + # controls=[ + # vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), + # vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), + # vm.Parameter( + # targets=["line_country.y"], + # selector=vm.Dropdown( + # options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" + # ), + # ), + # ], ) return page_country diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css new file mode 100644 index 000000000..e537be4bf --- /dev/null +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -0,0 +1,26 @@ +#page-container .ag-theme-custom-theme { + --ag-background-color:var(--main-container-bg-color); + --ag-foreground-color:var(--text-primary); + --ag-header-foreground-color:var(--text-primary); + --ag-font-size:14px; + --ag-borders: none; +} + +/*#page-container .ag-theme-custom-theme .ag-header {*/ +/* --ag-header-foreground-color: pink;*/ +/*}*/ + +#page-container .ag-theme-custom-theme .ag-row { + border-bottom: 1px solid var(--border-subtle-alpha-01); +} +#page-container .ag-theme-custom-theme .ag-header-cell { + border-bottom: 1px solid var(--border-subtle-alpha-01); + color: var(--text-secondary); +} +#page-container .ag-theme-custom-theme .ag-header-cell:hover { + border-bottom: 1px solid var(--state-overlays-selected-hover); + color: var(--text-primary); +} +#page-container .ag-theme-custom-theme .ag-header-row { + color: var(--text-primary); +} \ No newline at end of file diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index f2a87eb4e..108253aa3 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -4,10 +4,12 @@ @capture("action") -def dash_ag_grid(data_frame=None): +def dash_ag_grid(data_frame=None,**kwargs): """Custom AgGrid.""" return dag.AgGrid( id="get-started-example-basic", rowData=data_frame.to_dict("records"), columnDefs=[{"field": col} for col in data_frame.columns], + dashGridOptions = {"rowHeight": 40}, + **kwargs, ) From 648294d92b66d43d774d2561467886d767cda589 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 16 Jan 2024 11:29:17 +0100 Subject: [PATCH 007/128] Add icons --- vizro-core/examples/default/app_dev.py | 2 ++ vizro-core/src/vizro/static/css/aggrid.css | 13 ++++++++++--- vizro-core/src/vizro/tables/dash_aggrid.py | 2 +- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index 35c5d4df8..4f4cd4e23 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -43,6 +43,8 @@ def create_benchmark_analysis(): data_frame=df, # className="ag-theme-alpine ag-theme-acmecorp", className="ag-theme-custom-theme", + defaultColDef={"resizable": True, "sortable": True}, + columnSize="sizeToFit", ), # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index e537be4bf..234878039 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -4,15 +4,14 @@ --ag-header-foreground-color:var(--text-primary); --ag-font-size:14px; --ag-borders: none; + --ag-icon-font-family: agGridBalham; + --ag-icon-size: 14px; } /*#page-container .ag-theme-custom-theme .ag-header {*/ /* --ag-header-foreground-color: pink;*/ /*}*/ -#page-container .ag-theme-custom-theme .ag-row { - border-bottom: 1px solid var(--border-subtle-alpha-01); -} #page-container .ag-theme-custom-theme .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-01); color: var(--text-secondary); @@ -23,4 +22,12 @@ } #page-container .ag-theme-custom-theme .ag-header-row { color: var(--text-primary); +} + +#page-container .ag-theme-custom-theme .ag-row { + border-bottom: 1px solid var(--border-subtle-alpha-01); +} +#page-container .ag-theme-custom-theme .ag-cell { + display: flex; + align-items: center; } \ No newline at end of file diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 108253aa3..026dedda6 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -9,7 +9,7 @@ def dash_ag_grid(data_frame=None,**kwargs): return dag.AgGrid( id="get-started-example-basic", rowData=data_frame.to_dict("records"), - columnDefs=[{"field": col} for col in data_frame.columns], + columnDefs=[{"field": col, 'filter': True} for col in data_frame.columns], dashGridOptions = {"rowHeight": 40}, **kwargs, ) From 4d63d9cdc33fd890e1213af00a3b7b87f8360821 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 16 Jan 2024 11:35:51 +0100 Subject: [PATCH 008/128] Left align left column --- vizro-core/src/vizro/static/css/aggrid.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 234878039..fa60f4690 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -15,6 +15,7 @@ #page-container .ag-theme-custom-theme .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-01); color: var(--text-secondary); + padding-left: 0px; } #page-container .ag-theme-custom-theme .ag-header-cell:hover { border-bottom: 1px solid var(--state-overlays-selected-hover); @@ -30,4 +31,5 @@ #page-container .ag-theme-custom-theme .ag-cell { display: flex; align-items: center; + padding-left: 0px; } \ No newline at end of file From ac212b3127bab478f996ddca19b56e16082b95b6 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 16 Jan 2024 15:17:21 +0100 Subject: [PATCH 009/128] Proper deselect, hover, select behaviour for header --- vizro-core/src/vizro/static/css/aggrid.css | 29 ++++++++++++------- .../src/vizro/static/css/token_names.css | 2 ++ vizro-core/src/vizro/static/css/variables.css | 2 ++ 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index fa60f4690..95cad5ad8 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -8,28 +8,35 @@ --ag-icon-size: 14px; } -/*#page-container .ag-theme-custom-theme .ag-header {*/ -/* --ag-header-foreground-color: pink;*/ -/*}*/ - #page-container .ag-theme-custom-theme .ag-header-cell { - border-bottom: 1px solid var(--border-subtle-alpha-01); - color: var(--text-secondary); + border-bottom: 1px solid var(--border-subtle-alpha-02); padding-left: 0px; } #page-container .ag-theme-custom-theme .ag-header-cell:hover { - border-bottom: 1px solid var(--state-overlays-selected-hover); - color: var(--text-primary); + border-bottom: 1px solid var(--border-hover); +} +#page-container .ag-theme-custom-theme .ag-header-cell:focus { + border-bottom: 1px solid var(--border-selected); + color: var(--text-primary); } #page-container .ag-theme-custom-theme .ag-header-row { - color: var(--text-primary); + color: var(--text-secondary); } #page-container .ag-theme-custom-theme .ag-row { - border-bottom: 1px solid var(--border-subtle-alpha-01); + border-bottom: 1px solid var(--border-subtle-alpha-02); } #page-container .ag-theme-custom-theme .ag-cell { display: flex; align-items: center; padding-left: 0px; -} \ No newline at end of file +} +/* Changes header on click, this has the advantage of not remaining when cell is not sorted, but still "clicked" */ +/* Ie after clicking a third time, it should probably go back to the initial state*/ +/* However the line appearing is not in extended enough*/ +/* Alternative currently used: .ag-header-cell:focus*/ +/*#page-container .ag-theme-custom-theme .ag-header-cell-sorted-asc,*/ +/*#page-container .ag-theme-custom-theme .ag-header-cell-sorted-desc {*/ +/* border-bottom: 1px solid var(--border-selected);*/ +/* color: var(--text-primary);*/ +/*}*/ \ No newline at end of file diff --git a/vizro-core/src/vizro/static/css/token_names.css b/vizro-core/src/vizro/static/css/token_names.css index ebd452554..bcd5fdc29 100644 --- a/vizro-core/src/vizro/static/css/token_names.css +++ b/vizro-core/src/vizro/static/css/token_names.css @@ -31,6 +31,7 @@ --border-dark-mode-selected: #fff; --fill-dark-mode-subtle: rgba(255, 255, 255, 0.1); --border-dark-mode-subtle-alpha-01: rgba(255, 255, 255, 0.1); + --border-dark-mode-subtle-alpha-02: rgba(255, 255, 255, 0.16); --status-dark-mode-success: rgba(64, 216, 110, 1); --tags-dark-mode-text-color: rgba(255, 255, 255, 0.6); --status-dark-mode-warning: rgba(255, 193, 7, 1); @@ -67,6 +68,7 @@ --border-light-mode-selected: #141721; --fill-light-mode-subtle: rgba(0, 0, 0, 0.1); --border-light-mode-subtle-alpha-01: rgba(20, 23, 33, 0.1); + --border-light-mode-subtle-alpha-02: rgba(20, 23, 33, 0.16); --status-light-mode-success: rgba(38, 191, 86, 1); --tags-light-mode-text-color: rgba(255, 255, 255, 0.6); --status-light-mode-warning: rgba(241, 124, 2, 1); diff --git a/vizro-core/src/vizro/static/css/variables.css b/vizro-core/src/vizro/static/css/variables.css index 6e52915a3..226e8df6f 100644 --- a/vizro-core/src/vizro/static/css/variables.css +++ b/vizro-core/src/vizro/static/css/variables.css @@ -84,6 +84,7 @@ 0px 2px 4px -1px rgba(20, 23, 33, 0.88); --fill-subtle: var(--fill-dark-mode-subtle); --border-subtle-alpha-01: var(--border-dark-mode-subtle-alpha-01); + --border-subtle-alpha-02: var(--border-dark-mode-subtle-alpha-02); --status-success: var(--status-dark-mode-success); --tags-text-color: var(--tags-dark-mode-text-color); --tooltip-text-primary: rgba(20, 23, 33, 0.88); @@ -145,6 +146,7 @@ 0px 2px 4px -1px rgba(20, 23, 33, 0.08); --fill-subtle: var(--fill-light-mode-subtle); --border-subtle-alpha-01: var(--border-light-mode-subtle-alpha-01); + --border-subtle-alpha-02: var(--border-light-mode-subtle-alpha-02); --status-success: var(--status-light-mode-success); --tags-text-color: var(--tags-light-mode-text-color); --tooltip-text-primary: rgba(255, 255, 255, 0.88); From 7e40f37c1c86488e634d141e6538617fc0086be9 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 17 Jan 2024 10:01:51 +0100 Subject: [PATCH 010/128] Base custom theme on existing alpine theme --- vizro-core/examples/default/app_dev.py | 7 ++-- vizro-core/src/vizro/static/css/aggrid.css | 48 ++++++++++++---------- vizro-core/src/vizro/tables/dash_aggrid.py | 6 +-- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index 4f4cd4e23..e159ea058 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -4,7 +4,6 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.actions import export_data, filter_interaction from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() @@ -41,8 +40,8 @@ def create_benchmark_analysis(): title="Click on a cell in country column:", figure=dash_ag_grid( data_frame=df, - # className="ag-theme-alpine ag-theme-acmecorp", - className="ag-theme-custom-theme", + className="ag-theme-alpine vizro", + # className="ag-theme-custom-theme", defaultColDef={"resizable": True, "sortable": True}, columnSize="sizeToFit", ), @@ -82,7 +81,7 @@ def create_benchmark_analysis(): sort_action="native", style_cell={"textAlign": "left"}, ), - # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), # vm.Graph( # id="line_country", diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 95cad5ad8..7ed6468bf 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,42 +1,46 @@ -#page-container .ag-theme-custom-theme { - --ag-background-color:var(--main-container-bg-color); - --ag-foreground-color:var(--text-primary); - --ag-header-foreground-color:var(--text-primary); - --ag-font-size:14px; - --ag-borders: none; - --ag-icon-font-family: agGridBalham; - --ag-icon-size: 14px; +#page-container .ag-theme-alpine.vizro { + --ag-background-color: var(--main-container-bg-color); + --ag-foreground-color: var(--text-primary); + --ag-odd-row-background-color: var(--main-container-bg-color); + --ag-header-foreground-color: var(--text-primary); + --ag-header-background-color: var(--main-container-bg-color); + --ag-header-column-resize-handle-display: none; + --ag-font-size: 14px; + --ag-borders: none; + --ag-icon-font-family: agGridBalham; + --ag-icon-size: 14px; } -#page-container .ag-theme-custom-theme .ag-header-cell { +#page-container .ag-theme-alpine .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-02); - padding-left: 0px; + padding-left: 0px; } -#page-container .ag-theme-custom-theme .ag-header-cell:hover { - border-bottom: 1px solid var(--border-hover); +#page-container .ag-theme-alpine .ag-header-cell:hover { + border-bottom: 1px solid var(--border-hover); } -#page-container .ag-theme-custom-theme .ag-header-cell:focus { - border-bottom: 1px solid var(--border-selected); - color: var(--text-primary); +#page-container .ag-theme-alpine .ag-header-cell:focus { + border-bottom: 1px solid var(--border-selected); + color: var(--text-primary); } -#page-container .ag-theme-custom-theme .ag-header-row { +#page-container .ag-theme-alpine .ag-header-row { color: var(--text-secondary); + font-weight: normal; } -#page-container .ag-theme-custom-theme .ag-row { +#page-container .ag-theme-alpine .ag-row { border-bottom: 1px solid var(--border-subtle-alpha-02); } -#page-container .ag-theme-custom-theme .ag-cell { +#page-container .ag-theme-alpine .ag-cell { display: flex; align-items: center; - padding-left: 0px; + padding-left: 0px; } /* Changes header on click, this has the advantage of not remaining when cell is not sorted, but still "clicked" */ /* Ie after clicking a third time, it should probably go back to the initial state*/ /* However the line appearing is not in extended enough*/ /* Alternative currently used: .ag-header-cell:focus*/ -/*#page-container .ag-theme-custom-theme .ag-header-cell-sorted-asc,*/ -/*#page-container .ag-theme-custom-theme .ag-header-cell-sorted-desc {*/ +/*#page-container .ag-theme-alpine .ag-header-cell-sorted-asc,*/ +/*#page-container .ag-theme-alpine .ag-header-cell-sorted-desc {*/ /* border-bottom: 1px solid var(--border-selected);*/ /* color: var(--text-primary);*/ -/*}*/ \ No newline at end of file +/*}*/ diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 026dedda6..19af078c8 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -4,12 +4,12 @@ @capture("action") -def dash_ag_grid(data_frame=None,**kwargs): +def dash_ag_grid(data_frame=None, **kwargs): """Custom AgGrid.""" return dag.AgGrid( id="get-started-example-basic", rowData=data_frame.to_dict("records"), - columnDefs=[{"field": col, 'filter': True} for col in data_frame.columns], - dashGridOptions = {"rowHeight": 40}, + columnDefs=[{"field": col, "filter": True} for col in data_frame.columns], + dashGridOptions={"rowHeight": 40}, **kwargs, ) From 64a4eb9cafe32966333b2c1369fa91c132c605d2 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 24 Jan 2024 13:15:28 +0100 Subject: [PATCH 011/128] Initial commit with rendering Grid --- vizro-core/examples/_dev/app.py | 190 ++++++++++++++---- .../src/vizro/models/_components/grid.py | 0 vizro-core/src/vizro/tables/__init__.py | 3 +- vizro-core/src/vizro/tables/dash_aggrid.py | 20 ++ 4 files changed, 176 insertions(+), 37 deletions(-) create mode 100644 vizro-core/src/vizro/models/_components/grid.py create mode 100644 vizro-core/src/vizro/tables/dash_aggrid.py diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 1ceb7de01..ae6fca88f 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,54 +1,172 @@ -"""Rough example used by developers.""" +"""Example to show dashboard configuration.""" +from typing import List + +import pandas as pd +from dash import State, dash_table import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.tables import dash_data_table +from vizro.actions import export_data, filter_interaction +from vizro.models.types import capture +from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() +df_mean = ( + df.groupby(by=["continent", "year"]).agg({"lifeExp": "mean", "pop": "mean", "gdpPercap": "mean"}).reset_index() +) -table_and_container = vm.Page( - title="Table and Container", - components=[ - vm.Container( - title="Container w/ Table", - components=[ - vm.Table( - title="Table Title", - figure=dash_data_table( - id="dash_data_table_country", - data_frame=df, - page_size=30, - ), - ) - ], +df_transformed = df.copy() +df_transformed["lifeExp"] = df.groupby(by=["continent", "year"])["lifeExp"].transform("mean") +df_transformed["gdpPercap"] = df.groupby(by=["continent", "year"])["gdpPercap"].transform("mean") +df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") +df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) + + +def my_custom_table(data_frame=None, id: str = None, chosen_columns: List[str] = None): + """Custom table.""" + columns = [{"name": i, "id": i} for i in chosen_columns] + defaults = { + "style_as_list_view": True, + "style_data": {"border_bottom": "1px solid var(--border-subtle-alpha-01)", "height": "40px"}, + "style_header": { + "border_bottom": "1px solid var(--state-overlays-selected-hover)", + "border_top": "1px solid var(--main-container-bg-color)", + "height": "32px", + }, + } + return dash_table.DataTable(data=data_frame.to_dict("records"), columns=columns, id=id, **defaults) + + +my_custom_table.action_info = { + "filter_interaction_input": lambda x: { + "active_cell": State(component_id=x._callable_object_id, component_property="active_cell"), + "derived_viewport_data": State( + component_id=x._callable_object_id, + component_property="derived_viewport_data", ), - vm.Container( - title="Another Container", - components=[ - vm.Graph( - figure=px.scatter( - df, - title="Graph_2", - x="gdpPercap", - y="lifeExp", - size="pop", - color="continent", + } +} + +my_custom_table = capture("table")(my_custom_table) + + +def create_benchmark_analysis(): + """Function returns a page to perform analysis on country level.""" + # Apply formatting to table columns + columns = [ + {"id": "country", "name": "country"}, + {"id": "continent", "name": "continent"}, + {"id": "year", "name": "year"}, + {"id": "lifeExp", "name": "lifeExp", "type": "numeric", "format": {"specifier": ",.1f"}}, + {"id": "gdpPercap", "name": "gdpPercap", "type": "numeric", "format": {"specifier": "$,.2f"}}, + {"id": "pop", "name": "pop", "type": "numeric", "format": {"specifier": ",d"}}, + ] + + page_country = vm.Page( + title="Benchmark Analysis", + # description="Discovering how the metrics differ for each country and export data for further investigation", + # layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), + components=[ + vm.Table( + id="table_country_new", + title="Click on a cell in country column:", + figure=dash_ag_grid( + id="dash_ag_grid_country", + data_frame=df, + ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Table( + id="table_country", + title="Click on a cell in country column:", + figure=dash_data_table( + id="dash_data_table_country", + data_frame=df, + columns=columns, + style_data_conditional=[ + { + "if": {"filter_query": "{gdpPercap} < 1045", "column_id": "gdpPercap"}, + "backgroundColor": "#ff9222", + }, + { + "if": { + "filter_query": "{gdpPercap} >= 1045 && {gdpPercap} <= 4095", + "column_id": "gdpPercap", + }, + "backgroundColor": "#de9e75", + }, + { + "if": { + "filter_query": "{gdpPercap} > 4095 && {gdpPercap} <= 12695", + "column_id": "gdpPercap", + }, + "backgroundColor": "#aaa9ba", + }, + { + "if": {"filter_query": "{gdpPercap} > 12695", "column_id": "gdpPercap"}, + "backgroundColor": "#00b4ff", + }, + ], + sort_action="native", + style_cell={"textAlign": "left"}, + ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Graph( + id="line_country", + figure=px.line( + df_concat, + title="Country vs. Continent", + x="year", + y="gdpPercap", + color="color", + labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, + color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, + markers=True, + hover_name="country", + ), + ), + vm.Button( + text="Export data", + actions=[ + vm.Action( + function=export_data( + targets=["line_country"], + ) ), + ], + ), + vm.Table( # the custom table works with its own set of states defined above + id="custom_table", + title="Custom Dash DataTable", + figure=my_custom_table( + id="custom_dash_table_callable_id", + data_frame=df, + chosen_columns=["country", "continent", "lifeExp", "pop", "gdpPercap"], ), - ], - ), - ], - controls=[vm.Filter(column="continent")], -) + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + ], + controls=[ + vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), + vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), + vm.Parameter( + targets=["line_country.y"], + selector=vm.Dropdown( + options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" + ), + ), + ], + ) + return page_country dashboard = vm.Dashboard( - title="Dashboard Title", pages=[ - table_and_container, + create_benchmark_analysis(), ], ) if __name__ == "__main__": - Vizro().build(dashboard).run() + Vizro(assets_folder="../assets").build(dashboard).run() \ No newline at end of file diff --git a/vizro-core/src/vizro/models/_components/grid.py b/vizro-core/src/vizro/models/_components/grid.py new file mode 100644 index 000000000..e69de29bb diff --git a/vizro-core/src/vizro/tables/__init__.py b/vizro-core/src/vizro/tables/__init__.py index 5a813ad8c..0c91e3346 100644 --- a/vizro-core/src/vizro/tables/__init__.py +++ b/vizro-core/src/vizro/tables/__init__.py @@ -1,4 +1,5 @@ +from vizro.tables.dash_aggrid import dash_ag_grid from vizro.tables.dash_table import dash_data_table # Please keep alphabetically ordered -__all__ = ["dash_data_table"] +__all__ = ["dash_ag_grid", "dash_data_table"] diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py new file mode 100644 index 000000000..da54ea069 --- /dev/null +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -0,0 +1,20 @@ +import dash_ag_grid as dag +from dash import State + +from vizro.models.types import capture + + +def dash_ag_grid(data_frame=None, **kwargs): + """Custom AgGrid.""" + return dag.AgGrid( + rowData=data_frame.to_dict("records"), columnDefs=[{"field": col} for col in data_frame.columns], **kwargs + ) + + +dash_ag_grid.action_info = { + "filter_interaction_input": lambda x: { + "cellClicked": State(component_id=x._callable_object_id, component_property="cellClicked"), + } +} + +dash_ag_grid = capture("table")(dash_ag_grid) \ No newline at end of file From 67b12e67e90bec47c72255d053c781f2d80ae5b6 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 25 Jan 2024 13:31:01 +0100 Subject: [PATCH 012/128] First implementation of separate model approach --- vizro-core/examples/_dev/app.py | 39 +++++- .../src/vizro/actions/_actions_utils.py | 20 +-- .../_callback_mapping_utils.py | 23 +--- vizro-core/src/vizro/models/__init__.py | 6 +- .../src/vizro/models/_components/__init__.py | 3 +- .../src/vizro/models/_components/graph.py | 43 +++++- .../src/vizro/models/_components/grid.py | 122 ++++++++++++++++++ .../src/vizro/models/_components/table.py | 43 +++++- vizro-core/src/vizro/models/types.py | 7 +- vizro-core/src/vizro/tables/dash_aggrid.py | 11 +- 10 files changed, 255 insertions(+), 62 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index ae6fca88f..fc7e503a7 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -22,6 +22,8 @@ df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) +gapminder_2007 = df.query("year == 2007") + def my_custom_table(data_frame=None, id: str = None, chosen_columns: List[str] = None): """Custom table.""" @@ -64,11 +66,11 @@ def create_benchmark_analysis(): ] page_country = vm.Page( - title="Benchmark Analysis", + title="Table Test", # description="Discovering how the metrics differ for each country and export data for further investigation", # layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), components=[ - vm.Table( + vm.Grid( id="table_country_new", title="Click on a cell in country column:", figure=dash_ag_grid( @@ -162,11 +164,36 @@ def create_benchmark_analysis(): return page_country -dashboard = vm.Dashboard( - pages=[ - create_benchmark_analysis(), +chart_interaction = vm.Page( + title="Chart interaction", + components=[ + vm.Graph( + figure=px.box( + gapminder_2007, + x="continent", + y="lifeExp", + color="continent", + custom_data=["continent"], + ), + actions=[vm.Action(function=filter_interaction(targets=["scatter_relation_2007"]))], + ), + vm.Graph( + id="scatter_relation_2007", + figure=px.scatter( + gapminder_2007, + x="gdpPercap", + y="lifeExp", + size="pop", + color="continent", + ), + ), ], ) + +dashboard = vm.Dashboard( + pages=[create_benchmark_analysis(), chart_interaction], +) + if __name__ == "__main__": - Vizro(assets_folder="../assets").build(dashboard).run() \ No newline at end of file + Vizro(assets_folder="../assets").build(dashboard).run() diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 7d4b3ba8d..7ca6c78c7 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -139,19 +139,13 @@ def _apply_filter_interaction( target: str, ) -> pd.DataFrame: for ctd_filter_interaction in ctds_filter_interaction: - if "clickData" in ctd_filter_interaction: - data_frame = _apply_graph_filter_interaction( - data_frame=data_frame, - target=target, - ctd_filter_interaction=ctd_filter_interaction, - ) - - if "active_cell" in ctd_filter_interaction and "derived_viewport_data" in ctd_filter_interaction: - data_frame = _apply_table_filter_interaction( - data_frame=data_frame, - target=target, - ctd_filter_interaction=ctd_filter_interaction, - ) + #TODO: make this more robust, such that it doesn't require the modelID + triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] + data_frame = triggered_model._filter_interaction( + data_frame=data_frame, + target=target, + ctd_filter_interaction=ctd_filter_interaction, + ) return data_frame diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index 96b022108..b1255f448 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -7,7 +7,7 @@ from vizro.actions import _parameter, export_data, filter_interaction from vizro.managers import model_manager from vizro.managers._model_manager import ModelID -from vizro.models import Action, Page, Table +from vizro.models import Action, Page from vizro.models._controls import Filter, Parameter from vizro.models.types import ControlType @@ -51,25 +51,8 @@ def _get_inputs_of_figure_interactions( inputs = [] for action in figure_interactions_on_page: triggered_model = model_manager._get_action_trigger(action_id=ModelID(str(action.id))) - if isinstance(triggered_model, Table): - inputs.append( - { - "active_cell": State( - component_id=triggered_model._callable_object_id, component_property="active_cell" - ), - "derived_viewport_data": State( - component_id=triggered_model._callable_object_id, - component_property="derived_viewport_data", - ), - } - ) - else: - inputs.append( - { - "clickData": State(component_id=triggered_model.id, component_property="clickData"), - } - ) - + if hasattr(triggered_model, "_get_figure_interaction_input"): + inputs.append(triggered_model._get_figure_interaction_input()) return inputs diff --git a/vizro-core/src/vizro/models/__init__.py b/vizro-core/src/vizro/models/__init__.py index d25d374b2..212f7df02 100644 --- a/vizro-core/src/vizro/models/__init__.py +++ b/vizro-core/src/vizro/models/__init__.py @@ -1,7 +1,7 @@ # Keep this import at the top to avoid circular imports since it's used in every model. from ._base import VizroBaseModel # noqa: I001 from ._action import Action -from ._components import Card, Container, Graph, Table +from ._components import Card, Container, Graph, Grid, Table from ._components.form import Button, Checklist, Dropdown, RadioItems, RangeSlider, Slider from ._controls import Filter, Parameter from ._navigation.accordion import Accordion @@ -12,7 +12,7 @@ from ._layout import Layout from ._page import Page -Container.update_forward_refs(Button=Button, Card=Card, Graph=Graph, Table=Table, Layout=Layout) +Container.update_forward_refs(Button=Button, Card=Card, Graph=Graph, Grid=Grid, Table=Table, Layout=Layout) Page.update_forward_refs( Accordion=Accordion, Button=Button, @@ -20,6 +20,7 @@ Container=Container, Filter=Filter, Graph=Graph, + Grid=Grid, Parameter=Parameter, Table=Table, ) @@ -40,6 +41,7 @@ "Dropdown", "Filter", "Graph", + "Grid", "Layout", "NavBar", "NavLink", diff --git a/vizro-core/src/vizro/models/_components/__init__.py b/vizro-core/src/vizro/models/_components/__init__.py index 743ae87e0..6122367be 100644 --- a/vizro-core/src/vizro/models/_components/__init__.py +++ b/vizro-core/src/vizro/models/_components/__init__.py @@ -3,6 +3,7 @@ from vizro.models._components.card import Card from vizro.models._components.container import Container from vizro.models._components.graph import Graph +from vizro.models._components.grid import Grid from vizro.models._components.table import Table -__all__ = ["Button", "Card", "Container", "Graph", "Table"] +__all__ = ["Button", "Card", "Container", "Graph", "Grid", "Table"] diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index 03f8e8b58..881733090 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -1,7 +1,7 @@ import logging -from typing import List, Literal +from typing import Dict, List, Literal -from dash import ctx, dcc +from dash import State, ctx, dcc from dash.exceptions import MissingCallbackContextException from plotly import graph_objects as go @@ -10,9 +10,13 @@ except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator +import pandas as pd + import vizro.plotly.express as px from vizro import _themes as themes -from vizro.managers import data_manager +from vizro.actions._actions_utils import CallbackTriggerDict, _get_component_actions +from vizro.managers import data_manager, model_manager +from vizro.managers._model_manager import ModelID from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory from vizro.models._components._components_utils import _process_callable_data_frame @@ -70,6 +74,39 @@ def __getitem__(self, arg_name: str): return self.type return self.figure[arg_name] + # Interaction methods + def _get_figure_interaction_input(self): + """Requiried properties when using pre-defined `filter_interaction`""" + return { + "clickData": State(component_id=self.id, component_property="clickData"), + "modelID": State(component_id=self.id, component_property="id"), + } + + def _filter_interaction( + self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] + ) -> pd.DataFrame: + """Function to be carried out for pre-defined `filter_interaction`""" + ctd_click_data = ctd_filter_interaction["clickData"] + if not ctd_click_data["value"]: + return data_frame + + source_graph_id: ModelID = ctd_click_data["id"] + source_graph_actions = _get_component_actions(model_manager[source_graph_id]) + try: + custom_data_columns = model_manager[source_graph_id]["custom_data"] + except KeyError as exc: + raise KeyError(f"No `custom_data` argument found for source graph with id {source_graph_id}.") from exc + + customdata = ctd_click_data["value"]["points"][0]["customdata"] + + for action in source_graph_actions: + if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: + continue + for custom_data_idx, column in enumerate(custom_data_columns): + data_frame = data_frame[data_frame[column].isin([customdata[custom_data_idx]])] + + return data_frame + @_log_call def build(self): # The empty figure here is just a placeholder designed to be replaced by the actual figure when the filters diff --git a/vizro-core/src/vizro/models/_components/grid.py b/vizro-core/src/vizro/models/_components/grid.py index e69de29bb..ba5d9a484 100644 --- a/vizro-core/src/vizro/models/_components/grid.py +++ b/vizro-core/src/vizro/models/_components/grid.py @@ -0,0 +1,122 @@ +import logging +from typing import Dict, List, Literal + +import pandas as pd +from dash import State, dash_table, dcc, html + +try: + from pydantic.v1 import Field, PrivateAttr, validator +except ImportError: # pragma: no cov + from pydantic import Field, PrivateAttr, validator + +import vizro.tables as vt +from vizro.actions._actions_utils import CallbackTriggerDict, _get_component_actions, _get_parent_vizro_model +from vizro.managers import data_manager +from vizro.models import Action, VizroBaseModel +from vizro.models._action._actions_chain import _action_validator_factory +from vizro.models._components._components_utils import _process_callable_data_frame +from vizro.models._models_utils import _log_call +from vizro.models.types import CapturedCallable + +logger = logging.getLogger(__name__) + + +class Grid(VizroBaseModel): + """Wrapper for table components to visualize in dashboard. + + Args: + type (Literal["grid"]): Defaults to `"grid"`. + figure (CapturedCallable): Grid like object to be displayed. Current choices include: + [`dash-ag-grid.AgGrid`](https://dash.plotly.com/dash-ag-grid). + title (str): Title of the table. Defaults to `""`. + actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. + """ + + type: Literal["grid"] = "grid" + figure: CapturedCallable = Field(..., import_path=vt, description="Grid to be visualized on dashboard") + title: str = Field("", description="Title of the grid") + actions: List[Action] = [] + + _callable_object_id: str = PrivateAttr() + + # Component properties for actions and interactions + _output_property: str = PrivateAttr("children") + + # validator + set_actions = _action_validator_factory("cellClicked") + _validate_callable = validator("figure", allow_reuse=True, always=True)(_process_callable_data_frame) + + # Convenience wrapper/syntactic sugar. + def __call__(self, **kwargs): + kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) + return self.figure(**kwargs) + + # Convenience wrapper/syntactic sugar. + def __getitem__(self, arg_name: str): + # See table implementation for more details. + if arg_name == "type": + return self.type + return self.figure[arg_name] + + # Interaction methods + def _get_figure_interaction_input(self): + """Requiried properties when using pre-defined `filter_interaction`""" + return { + "cellClicked": State(component_id=self._callable_object_id, component_property="cellClicked"), + "modelID": State(component_id=self.id, component_property="id"), + } + + def _filter_interaction( + self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] + ) -> pd.DataFrame: + """Function to be carried out for pre-defined `filter_interaction`""" + ctd_cellClicked = ctd_filter_interaction["cellClicked"] + if not ctd_cellClicked["value"]: + return data_frame + + # ctd_active_cell["id"] represents the underlying table id, so we need to fetch its parent Vizro Table actions. + source_table_actions = _get_component_actions(_get_parent_vizro_model(ctd_cellClicked["id"])) + + for action in source_table_actions: + if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: + continue + column = ctd_cellClicked["value"]["colId"] + clicked_data = ctd_cellClicked["value"]["value"] + data_frame = data_frame[data_frame[column].isin([clicked_data])] + + return data_frame + + @_log_call + def pre_build(self): + if self.actions: + kwargs = self.figure._arguments.copy() + + # This workaround is needed because the underlying table object requires a data_frame + kwargs["data_frame"] = pd.DataFrame() + + # The underlying table object is pre-built, so we can fetch its ID. + underlying_table_object = self.figure._function(**kwargs) + + if not hasattr(underlying_table_object, "id"): + raise ValueError( + "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" + " a valid 'id' has to be provided to the `Table` callable." + ) + + self._callable_object_id = underlying_table_object.id + + def build(self): + return dcc.Loading( + html.Div( + [ + html.H3(self.title, className="table-title") if self.title else None, + html.Div( + dash_table.DataTable(**({"id": self._callable_object_id} if self.actions else {})), id=self.id + ), + ], + className="table-container", + id=f"{self.id}_outer", + ), + color="grey", + parent_className="loading-container", + ) diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 5ee771261..32ab90d84 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -1,8 +1,8 @@ import logging -from typing import List, Literal +from typing import Dict, List, Literal -from dash import dash_table, dcc, html -from pandas import DataFrame +import pandas as pd +from dash import State, dash_table, dcc, html try: from pydantic.v1 import Field, PrivateAttr, validator @@ -10,6 +10,7 @@ from pydantic import Field, PrivateAttr, validator import vizro.tables as vt +from vizro.actions._actions_utils import CallbackTriggerDict, _get_component_actions, _get_parent_vizro_model from vizro.managers import data_manager from vizro.models import Action, VizroBaseModel from vizro.models._action._actions_chain import _action_validator_factory @@ -58,13 +59,47 @@ def __getitem__(self, arg_name: str): return self.type return self.figure[arg_name] + # Interaction methods + def _get_figure_interaction_input(self): + """Requiried properties when using pre-defined `filter_interaction`""" + return { + "active_cell": State(component_id=self._callable_object_id, component_property="active_cell"), + "derived_viewport_data": State( + component_id=self._callable_object_id, + component_property="derived_viewport_data", + ), + "modelID": State(component_id=self.id, component_property="id"), + } + + def _filter_interaction( + self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] + ) -> pd.DataFrame: + """Function to be carried out for pre-defined `filter_interaction`""" + ctd_active_cell = ctd_filter_interaction["active_cell"] + ctd_derived_viewport_data = ctd_filter_interaction["derived_viewport_data"] + if not ctd_active_cell["value"] or not ctd_derived_viewport_data["value"]: + return data_frame + + # ctd_active_cell["id"] represents the underlying table id, so we need to fetch its parent Vizro Table actions. + source_table_actions = _get_component_actions(_get_parent_vizro_model(ctd_active_cell["id"])) + + for action in source_table_actions: + if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: + continue + column = ctd_active_cell["value"]["column_id"] + derived_viewport_data_row = ctd_active_cell["value"]["row"] + clicked_data = ctd_derived_viewport_data["value"][derived_viewport_data_row][column] + data_frame = data_frame[data_frame[column].isin([clicked_data])] + + return data_frame + @_log_call def pre_build(self): if self.actions: kwargs = self.figure._arguments.copy() # This workaround is needed because the underlying table object requires a data_frame - kwargs["data_frame"] = DataFrame() + kwargs["data_frame"] = pd.DataFrame() # The underlying table object is pre-built, so we can fetch its ID. underlying_table_object = self.figure._function(**kwargs) diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index db81a78f0..4bcd086f3 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -293,6 +293,7 @@ def wrapped(*args, **kwargs): return CapturedCallable(func, *args, **kwargs) return wrapped + #TODO: should we have "grid" - also add this elif self._mode == "table": @functools.wraps(func) @@ -362,15 +363,15 @@ class OptionsDictType(TypedDict): [`Parameter`][vizro.models.Parameter].""" ComponentType = Annotated[ - Union["Button", "Card", "Container", "Graph", "Table"], + Union["Button", "Card", "Container", "Graph", "Grid", "Table"], Field( discriminator="type", description="Component that makes up part of the layout on the page.", ), ] """Discriminated union. Type of component that makes up part of the layout on the page: -[`Button`][vizro.models.Button], [`Card`][vizro.models.Card], [`Table`][vizro.models.Table] or -[`Graph`][vizro.models.Graph].""" +[`Button`][vizro.models.Button], [`Card`][vizro.models.Card], [`Table`][vizro.models.Table], +[`Graph`][vizro.models.Graph] or [`Grid`][vizro.models.Grid].""" NavPagesType = Union[List[str], Dict[str, List[str]]] "List of page IDs or a mapping from name of a group to a list of page IDs (for hierarchical sub-navigation)." diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index da54ea069..3ec3a06de 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -1,20 +1,11 @@ import dash_ag_grid as dag -from dash import State from vizro.models.types import capture +@capture("table") def dash_ag_grid(data_frame=None, **kwargs): """Custom AgGrid.""" return dag.AgGrid( rowData=data_frame.to_dict("records"), columnDefs=[{"field": col} for col in data_frame.columns], **kwargs ) - - -dash_ag_grid.action_info = { - "filter_interaction_input": lambda x: { - "cellClicked": State(component_id=x._callable_object_id, component_property="cellClicked"), - } -} - -dash_ag_grid = capture("table")(dash_ag_grid) \ No newline at end of file From 61d190ed48fdab8498ec53a0e56e7f5180a20861 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 12:43:20 +0000 Subject: [PATCH 013/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/src/vizro/actions/_actions_utils.py | 2 +- vizro-core/src/vizro/models/types.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 7ca6c78c7..e549054d5 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -139,7 +139,7 @@ def _apply_filter_interaction( target: str, ) -> pd.DataFrame: for ctd_filter_interaction in ctds_filter_interaction: - #TODO: make this more robust, such that it doesn't require the modelID + # TODO: make this more robust, such that it doesn't require the modelID triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] data_frame = triggered_model._filter_interaction( data_frame=data_frame, diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index 4bcd086f3..498bf927b 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -293,7 +293,7 @@ def wrapped(*args, **kwargs): return CapturedCallable(func, *args, **kwargs) return wrapped - #TODO: should we have "grid" - also add this + # TODO: should we have "grid" - also add this elif self._mode == "table": @functools.wraps(func) From 15928e6137a0066a9c2c39c41e8edc8b823940c4 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Mon, 29 Jan 2024 17:43:41 +0100 Subject: [PATCH 014/128] First series of PR comments --- vizro-core/examples/default/app_dev.py | 13 +++++--- vizro-core/src/vizro/static/css/aggrid.css | 39 +++++++++++++--------- vizro-core/src/vizro/tables/dash_aggrid.py | 5 ++- 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/vizro-core/examples/default/app_dev.py b/vizro-core/examples/default/app_dev.py index e159ea058..af31a4c05 100644 --- a/vizro-core/examples/default/app_dev.py +++ b/vizro-core/examples/default/app_dev.py @@ -17,7 +17,9 @@ df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) - +# Create a df with 40 columns and long column titles +df_long = pd.DataFrame( + {f"Column with a long name that needs to be truncated {i}":[1, 2, 3] for i in range(40)}) def create_benchmark_analysis(): """Function returns a page to perform analysis on country level.""" # Apply formatting to table columns @@ -39,11 +41,14 @@ def create_benchmark_analysis(): id="table_country_new", title="Click on a cell in country column:", figure=dash_ag_grid( - data_frame=df, + data_frame=df_long,#df,# className="ag-theme-alpine vizro", # className="ag-theme-custom-theme", - defaultColDef={"resizable": True, "sortable": True}, - columnSize="sizeToFit", + defaultColDef={"resizable": True, "sortable": True, "editable": True, "filter": True}, + columnSizeOptions={ + 'defaultMinWidth': 100, + }, + columnSize="sizeToFit",#"autosize",# ), # actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 7ed6468bf..2a96d7239 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,39 +1,46 @@ -#page-container .ag-theme-alpine.vizro { - --ag-background-color: var(--main-container-bg-color); - --ag-foreground-color: var(--text-primary); - --ag-odd-row-background-color: var(--main-container-bg-color); - --ag-header-foreground-color: var(--text-primary); - --ag-header-background-color: var(--main-container-bg-color); - --ag-header-column-resize-handle-display: none; - --ag-font-size: 14px; - --ag-borders: none; - --ag-icon-font-family: agGridBalham; - --ag-icon-size: 14px; +.ag-theme-alpine.vizro { + --ag-background-color: var(--main-container-bg-color); /* confirmed */ + --ag-foreground-color: var(--text-primary); /* confirmed */ + --ag-odd-row-background-color: var(--main-container-bg-color); /* confirmed */ + --ag-header-background-color: var(--main-container-bg-color); /* confirmed */ + --ag-header-column-resize-handle-display: none; /* confirmed */ + --ag-borders: none; /* confirmed */ + --ag-icon-font-family: agGridBalham; /* confirmed, but hardly visible */ + --ag-icon-size: 14px; /* confirmed */ + --ag-row-height: 48px; /* This determines the row height, but not header */ +} +/* General setting header row*/ +#page-container .ag-theme-alpine .ag-header-row { + color: var(--text-secondary); + font-weight: normal; + font-size: 14px; + height: 40px; /* this is not overwritten but has no effect */ } +/* Border at the bottom and padding*/ #page-container .ag-theme-alpine .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-02); padding-left: 0px; } +/*Border at bottom when hovering more solid*/ #page-container .ag-theme-alpine .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); } +/*Border bolder when clicking on header, text now primary color*/ #page-container .ag-theme-alpine .ag-header-cell:focus { border-bottom: 1px solid var(--border-selected); color: var(--text-primary); } -#page-container .ag-theme-alpine .ag-header-row { - color: var(--text-secondary); - font-weight: normal; -} #page-container .ag-theme-alpine .ag-row { border-bottom: 1px solid var(--border-subtle-alpha-02); + /*height: 48px; !* This does not seem to set it to 48px due to element style *!*/ } #page-container .ag-theme-alpine .ag-cell { display: flex; align-items: center; - padding-left: 0px; + padding: 0px; + font-size: 14px; } /* Changes header on click, this has the advantage of not remaining when cell is not sorted, but still "clicked" */ /* Ie after clicking a third time, it should probably go back to the initial state*/ diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 19af078c8..7f01e5a61 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -1,7 +1,7 @@ import dash_ag_grid as dag from vizro.models.types import capture - +from pandas.api.types import is_numeric_dtype @capture("action") def dash_ag_grid(data_frame=None, **kwargs): @@ -9,7 +9,6 @@ def dash_ag_grid(data_frame=None, **kwargs): return dag.AgGrid( id="get-started-example-basic", rowData=data_frame.to_dict("records"), - columnDefs=[{"field": col, "filter": True} for col in data_frame.columns], - dashGridOptions={"rowHeight": 40}, + columnDefs=[{"field": col, "flex":1, 'type': 'numericColumn' if is_numeric_dtype(data_frame[col].dtype) else None} for col in data_frame.columns], **kwargs, ) From c186d916bf33f7bd5b1821b01d896b6c22a81088 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Mon, 19 Feb 2024 20:03:11 +0100 Subject: [PATCH 015/128] Initial PR ready implementation --- vizro-core/examples/_dev/app.py | 246 +++++++----------- vizro-core/pyproject.toml | 1 + .../src/vizro/actions/_actions_utils.py | 14 +- .../src/vizro/models/_components/graph.py | 8 +- .../src/vizro/models/_components/grid.py | 12 +- .../src/vizro/models/_components/table.py | 10 +- vizro-core/src/vizro/models/types.py | 20 +- vizro-core/src/vizro/tables/_utils.py | 13 + vizro-core/src/vizro/tables/dash_aggrid.py | 73 +++++- vizro-core/src/vizro/tables/dash_table.py | 14 +- 10 files changed, 213 insertions(+), 198 deletions(-) create mode 100644 vizro-core/src/vizro/tables/_utils.py diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index fc7e503a7..10b46c71a 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,14 +1,12 @@ """Example to show dashboard configuration.""" -from typing import List +import numpy as np import pandas as pd -from dash import State, dash_table import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro from vizro.actions import export_data, filter_interaction -from vizro.models.types import capture from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() @@ -22,169 +20,105 @@ df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) -gapminder_2007 = df.query("year == 2007") - -def my_custom_table(data_frame=None, id: str = None, chosen_columns: List[str] = None): - """Custom table.""" - columns = [{"name": i, "id": i} for i in chosen_columns] - defaults = { - "style_as_list_view": True, - "style_data": {"border_bottom": "1px solid var(--border-subtle-alpha-01)", "height": "40px"}, - "style_header": { - "border_bottom": "1px solid var(--state-overlays-selected-hover)", - "border_top": "1px solid var(--main-container-bg-color)", - "height": "32px", - }, - } - return dash_table.DataTable(data=data_frame.to_dict("records"), columns=columns, id=id, **defaults) - - -my_custom_table.action_info = { - "filter_interaction_input": lambda x: { - "active_cell": State(component_id=x._callable_object_id, component_property="active_cell"), - "derived_viewport_data": State( - component_id=x._callable_object_id, - component_property="derived_viewport_data", - ), - } -} - -my_custom_table = capture("table")(my_custom_table) - - -def create_benchmark_analysis(): - """Function returns a page to perform analysis on country level.""" - # Apply formatting to table columns - columns = [ - {"id": "country", "name": "country"}, - {"id": "continent", "name": "continent"}, - {"id": "year", "name": "year"}, - {"id": "lifeExp", "name": "lifeExp", "type": "numeric", "format": {"specifier": ",.1f"}}, - {"id": "gdpPercap", "name": "gdpPercap", "type": "numeric", "format": {"specifier": "$,.2f"}}, - {"id": "pop", "name": "pop", "type": "numeric", "format": {"specifier": ",d"}}, - ] - - page_country = vm.Page( - title="Table Test", - # description="Discovering how the metrics differ for each country and export data for further investigation", - # layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), - components=[ - vm.Grid( - id="table_country_new", - title="Click on a cell in country column:", - figure=dash_ag_grid( - id="dash_ag_grid_country", - data_frame=df, - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Table( - id="table_country", - title="Click on a cell in country column:", - figure=dash_data_table( - id="dash_data_table_country", - data_frame=df, - columns=columns, - style_data_conditional=[ - { - "if": {"filter_query": "{gdpPercap} < 1045", "column_id": "gdpPercap"}, - "backgroundColor": "#ff9222", - }, - { - "if": { - "filter_query": "{gdpPercap} >= 1045 && {gdpPercap} <= 4095", - "column_id": "gdpPercap", - }, - "backgroundColor": "#de9e75", - }, - { - "if": { - "filter_query": "{gdpPercap} > 4095 && {gdpPercap} <= 12695", - "column_id": "gdpPercap", - }, - "backgroundColor": "#aaa9ba", - }, - { - "if": {"filter_query": "{gdpPercap} > 12695", "column_id": "gdpPercap"}, - "backgroundColor": "#00b4ff", - }, - ], - sort_action="native", - style_cell={"textAlign": "left"}, - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Graph( - id="line_country", - figure=px.line( - df_concat, - title="Country vs. Continent", - x="year", - y="gdpPercap", - color="color", - labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, - color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, - markers=True, - hover_name="country", - ), +grid_interaction = vm.Page( + title="Grid and Table Interaction", + components=[ + vm.Grid( + id="table_country_new", + title="Click on a cell", + figure=dash_ag_grid( + id="dash_ag_grid_1", + data_frame=px.data.gapminder(), ), - vm.Button( - text="Export data", - actions=[ - vm.Action( - function=export_data( - targets=["line_country"], - ) - ), - ], + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Table( + id="table_country", + title="Click on a cell", + figure=dash_data_table( + id="dash_data_table_country", + data_frame=df, + columns=[{"id": col, "name": col} for col in df.columns], + sort_action="native", + style_cell={"textAlign": "left"}, ), - vm.Table( # the custom table works with its own set of states defined above - id="custom_table", - title="Custom Dash DataTable", - figure=my_custom_table( - id="custom_dash_table_callable_id", - data_frame=df, - chosen_columns=["country", "continent", "lifeExp", "pop", "gdpPercap"], - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Graph( + id="line_country", + figure=px.line( + df_concat, + title="Country vs. Continent", + x="year", + y="gdpPercap", + color="color", + labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, + color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, + markers=True, + hover_name="country", ), - ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), - vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), - vm.Parameter( - targets=["line_country.y"], - selector=vm.Dropdown( - options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" + ), + vm.Button( + text="Export data", + actions=[ + vm.Action( + function=export_data( + targets=["line_country"], + ) ), + ], + ), + ], + controls=[ + vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), + vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), + vm.Parameter( + targets=["line_country.y"], + selector=vm.Dropdown( + options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" ), - ], - ) - return page_country + ), + ], +) -chart_interaction = vm.Page( - title="Chart interaction", +df2 = px.data.stocks() +df2["date_as_datetime"] = pd.to_datetime(df2["date"]) +df2["date_str"] = df2["date"].astype("str") +df2["perc_from_float"] = np.random.rand(len(df2)) +df2["random"] = np.random.uniform(-100000.000, 100000.000, len(df2)) + +grid_standard = vm.Page( + title="Grid Default", components=[ - vm.Graph( - figure=px.box( - gapminder_2007, - x="continent", - y="lifeExp", - color="continent", - custom_data=["continent"], + vm.Grid( + figure=dash_ag_grid( + id="dash_ag_grid_2", + data_frame=df2, ), - actions=[vm.Action(function=filter_interaction(targets=["scatter_relation_2007"]))], ), - vm.Graph( - id="scatter_relation_2007", - figure=px.scatter( - gapminder_2007, - x="gdpPercap", - y="lifeExp", - size="pop", - color="continent", + ], +) + +grid_custom = vm.Page( + title="Grid Custom", + components=[ + vm.Grid( + figure=dash_ag_grid( + id="dash_ag_grid_3", + data_frame=df2, + columnDefs=[ + {"field": "AAPL", "headerName": "Format Dollar", "cellDataType": "dollar"}, + {"field": "AAPL", "headerName": "Format Euro", "cellDataType": "euro"}, + {"field": "random", "headerName": "Format Numeric", "cellDataType": "numeric"}, + {"field": "perc_from_float", "headerName": "Format Percent", "cellDataType": "percent"}, + { + "field": "perc_from_float", + "headerName": "custom format", + "valueFormatter": {"function": "d3.format('.^30')(params.value)"}, + }, + ], ), ), ], @@ -192,7 +126,7 @@ def create_benchmark_analysis(): dashboard = vm.Dashboard( - pages=[create_benchmark_analysis(), chart_interaction], + pages=[grid_interaction, grid_standard, grid_custom], ) if __name__ == "__main__": diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 475303f9b..6c039e5f0 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -17,6 +17,7 @@ classifiers = [ dependencies = [ "dash>=2.14.1", # 2.14.1 needed for compatibility with werkzeug "dash_bootstrap_components", + "dash-ag-grid>=31.0.0", "pandas", "pydantic>=1.10.13", # must be synced with pre-commit mypy hook manually "dash_mantine_components", diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index e549054d5..6c26c5bba 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -139,13 +139,13 @@ def _apply_filter_interaction( target: str, ) -> pd.DataFrame: for ctd_filter_interaction in ctds_filter_interaction: - # TODO: make this more robust, such that it doesn't require the modelID - triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] - data_frame = triggered_model._filter_interaction( - data_frame=data_frame, - target=target, - ctd_filter_interaction=ctd_filter_interaction, - ) + if "modelID" in ctd_filter_interaction: + triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] + data_frame = triggered_model._filter_interaction( + data_frame=data_frame, + target=target, + ctd_filter_interaction=ctd_filter_interaction, + ) return data_frame diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index 881733090..e5ab02ee5 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -75,17 +75,17 @@ def __getitem__(self, arg_name: str): return self.figure[arg_name] # Interaction methods - def _get_figure_interaction_input(self): - """Requiried properties when using pre-defined `filter_interaction`""" + def _get_figure_interaction_input(self) -> Dict[str, State]: + """Required properties when using pre-defined `filter_interaction`.""" return { "clickData": State(component_id=self.id, component_property="clickData"), - "modelID": State(component_id=self.id, component_property="id"), + "modelID": State(component_id=self.id, component_property="id"), # required, to determine triggered model } def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: - """Function to be carried out for pre-defined `filter_interaction`""" + """Function to be carried out for pre-defined `filter_interaction`.""" ctd_click_data = ctd_filter_interaction["clickData"] if not ctd_click_data["value"]: return data_frame diff --git a/vizro-core/src/vizro/models/_components/grid.py b/vizro-core/src/vizro/models/_components/grid.py index ba5d9a484..c781dfb94 100644 --- a/vizro-core/src/vizro/models/_components/grid.py +++ b/vizro-core/src/vizro/models/_components/grid.py @@ -22,11 +22,11 @@ class Grid(VizroBaseModel): - """Wrapper for table components to visualize in dashboard. + """Wrapper for `dash-ag-grid.AgGrid` to visualize grids in dashboard. Args: type (Literal["grid"]): Defaults to `"grid"`. - figure (CapturedCallable): Grid like object to be displayed. Current choices include: + figure (CapturedCallable): Grid like object to be displayed. For more information see: [`dash-ag-grid.AgGrid`](https://dash.plotly.com/dash-ag-grid). title (str): Title of the table. Defaults to `""`. actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. @@ -59,17 +59,17 @@ def __getitem__(self, arg_name: str): return self.figure[arg_name] # Interaction methods - def _get_figure_interaction_input(self): - """Requiried properties when using pre-defined `filter_interaction`""" + def _get_figure_interaction_input(self) -> Dict[str, State]: + """Required properties when using pre-defined `filter_interaction`.""" return { "cellClicked": State(component_id=self._callable_object_id, component_property="cellClicked"), - "modelID": State(component_id=self.id, component_property="id"), + "modelID": State(component_id=self.id, component_property="id"), # required, to determine triggered model } def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: - """Function to be carried out for pre-defined `filter_interaction`""" + """Function to be carried out for pre-defined `filter_interaction`.""" ctd_cellClicked = ctd_filter_interaction["cellClicked"] if not ctd_cellClicked["value"]: return data_frame diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 32ab90d84..3b5cebf08 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -22,11 +22,11 @@ class Table(VizroBaseModel): - """Wrapper for table components to visualize in dashboard. + """Wrapper for `dash_table.DataTable` to visualize tables in dashboard. Args: type (Literal["table"]): Defaults to `"table"`. - figure (CapturedCallable): Table like object to be displayed. Current choices include: + figure (CapturedCallable): Table like object to be displayed. For more information see: [`dash_table.DataTable`](https://dash.plotly.com/datatable). title (str): Title of the table. Defaults to `""`. actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. @@ -60,15 +60,15 @@ def __getitem__(self, arg_name: str): return self.figure[arg_name] # Interaction methods - def _get_figure_interaction_input(self): - """Requiried properties when using pre-defined `filter_interaction`""" + def _get_figure_interaction_input(self) -> Dict[str, State]: + """Required properties when using pre-defined `filter_interaction`.""" return { "active_cell": State(component_id=self._callable_object_id, component_property="active_cell"), "derived_viewport_data": State( component_id=self._callable_object_id, component_property="derived_viewport_data", ), - "modelID": State(component_id=self.id, component_property="id"), + "modelID": State(component_id=self.id, component_property="id"), # required, to determine triggered model } def _filter_interaction( diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index 498bf927b..66f74547d 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -293,7 +293,6 @@ def wrapped(*args, **kwargs): return CapturedCallable(func, *args, **kwargs) return wrapped - # TODO: should we have "grid" - also add this elif self._mode == "table": @functools.wraps(func) @@ -309,9 +308,26 @@ def wrapped(*args, **kwargs): raise ValueError(f"{func.__name__} must supply a value to data_frame argument.") from exc return captured_callable + return wrapped + elif self._mode == "grid": + + @functools.wraps(func) + def wrapped(*args, **kwargs): + if "data_frame" not in inspect.signature(func).parameters: + raise ValueError(f"{func.__name__} must have data_frame argument to use capture('grid').") + + captured_callable: CapturedCallable = CapturedCallable(func, *args, **kwargs) + + try: + captured_callable["data_frame"] + except KeyError as exc: + raise ValueError(f"{func.__name__} must supply a value to data_frame argument.") from exc + return captured_callable + return wrapped raise ValueError( - "Valid modes of the capture decorator are @capture('graph'), @capture('action') or @capture('table')." + "Valid modes of the capture decorator are @capture('graph'), @capture('action'), @capture('table') or " + "@capture('grid')." ) diff --git a/vizro-core/src/vizro/tables/_utils.py b/vizro-core/src/vizro/tables/_utils.py new file mode 100644 index 000000000..ec2791917 --- /dev/null +++ b/vizro-core/src/vizro/tables/_utils.py @@ -0,0 +1,13 @@ +"""Contains utilities for the implementation of table callables.""" +from collections import defaultdict +from typing import Any, Dict, Mapping + + +def _set_defaults_nested(supplied: Mapping[str, Any], defaults: Mapping[str, Any]) -> Dict[str, Any]: + supplied = defaultdict(dict, supplied) + for default_key, default_value in defaults.items(): + if isinstance(default_value, Mapping): + supplied[default_key] = _set_defaults_nested(supplied[default_key], default_value) + else: + supplied.setdefault(default_key, default_value) + return dict(supplied) diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 3ec3a06de..5fc2aff95 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -1,11 +1,74 @@ import dash_ag_grid as dag +import pandas as pd from vizro.models.types import capture +from vizro.tables._utils import _set_defaults_nested +FORMAT_CURRENCY_EU = """d3.formatLocale({ + "decimal": ",", + "thousands": "\u00a0", + "grouping": [3], + "currency": ["", "\u00a0€"], + "percent": "\u202f%", + "nan": "" +})""" -@capture("table") +DATA_TYPE_DEFINITIONS = { + "number": { + "baseDataType": "number", + "extendsDataType": "number", + "columnTypes": ["numberColumn", "rightAligned"], + "appendColumnTypes": True, + "valueFormatter": {"function": "params.value == null ? 'NaN' : String(params.value)"}, + }, + "dollar": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": {"function": "d3.format('($,.2f')(params.value)"}, + }, + "euro": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": {"function": f"{FORMAT_CURRENCY_EU}.format('$,.2f')(params.value)"}, + }, + "percent": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": {"function": "d3.format(',.1%')(params.value)"}, + }, + "numeric": { + "baseDataType": "number", + "extendsDataType": "number", + "valueFormatter": {"function": "d3.format(',.1f')(params.value)"}, + }, +} + + +@capture("grid") def dash_ag_grid(data_frame=None, **kwargs): - """Custom AgGrid.""" - return dag.AgGrid( - rowData=data_frame.to_dict("records"), columnDefs=[{"field": col} for col in data_frame.columns], **kwargs - ) + """Implementation of `dash-ag-grid.AgGrid` with sensible defaults.""" + defaults = { + "columnDefs": [{"field": col} for col in data_frame.columns], + "rowData": data_frame.apply( + lambda x: x.dt.strftime("%Y-%m-%d") # set date columns to `dateString` for AGGrid filtering to function + if pd.api.types.is_datetime64_any_dtype(x) + else x + ).to_dict("records"), + "defaultColDef": { + # "editable": True, #do not set, as this may confuse some users + "resizable": True, + "sortable": True, + "filter": True, + "filterParams": { + "buttons": ["apply", "reset"], + "closeOnApply": True, + }, + }, + "dashGridOptions": { + "dataTypeDefinitions": DATA_TYPE_DEFINITIONS, + "animateRows": False, + "pagination": True, + }, + } + kwargs = _set_defaults_nested(kwargs, defaults) + return dag.AgGrid(**kwargs) diff --git a/vizro-core/src/vizro/tables/dash_table.py b/vizro-core/src/vizro/tables/dash_table.py index f28315834..0b44c6440 100644 --- a/vizro-core/src/vizro/tables/dash_table.py +++ b/vizro-core/src/vizro/tables/dash_table.py @@ -1,21 +1,9 @@ """Module containing the standard implementation of `dash_table.DataTable`.""" -from collections import defaultdict -from typing import Any, Dict, Mapping - import pandas as pd from dash import dash_table from vizro.models.types import capture - - -def _set_defaults_nested(supplied: Mapping[str, Any], defaults: Mapping[str, Any]) -> Dict[str, Any]: - supplied = defaultdict(dict, supplied) - for default_key, default_value in defaults.items(): - if isinstance(default_value, Mapping): - supplied[default_key] = _set_defaults_nested(supplied[default_key], default_value) - else: - supplied.setdefault(default_key, default_value) - return dict(supplied) +from vizro.tables._utils import _set_defaults_nested @capture("table") From 923be6785cd365349f708d44d54c98bf485f3cb6 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 09:03:09 +0000 Subject: [PATCH 016/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/examples/_dev/app.py | 1 - vizro-core/src/vizro/models/_components/grid.py | 1 + vizro-core/src/vizro/tables/_utils.py | 1 + vizro-core/src/vizro/tables/dash_aggrid.py | 8 +++++--- vizro-core/src/vizro/tables/dash_table.py | 1 + 5 files changed, 8 insertions(+), 4 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 10b46c71a..bf5958ac8 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -2,7 +2,6 @@ import numpy as np import pandas as pd - import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro diff --git a/vizro-core/src/vizro/models/_components/grid.py b/vizro-core/src/vizro/models/_components/grid.py index c781dfb94..35cb42316 100644 --- a/vizro-core/src/vizro/models/_components/grid.py +++ b/vizro-core/src/vizro/models/_components/grid.py @@ -30,6 +30,7 @@ class Grid(VizroBaseModel): [`dash-ag-grid.AgGrid`](https://dash.plotly.com/dash-ag-grid). title (str): Title of the table. Defaults to `""`. actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`. + """ type: Literal["grid"] = "grid" diff --git a/vizro-core/src/vizro/tables/_utils.py b/vizro-core/src/vizro/tables/_utils.py index ec2791917..63fe98c68 100644 --- a/vizro-core/src/vizro/tables/_utils.py +++ b/vizro-core/src/vizro/tables/_utils.py @@ -1,4 +1,5 @@ """Contains utilities for the implementation of table callables.""" + from collections import defaultdict from typing import Any, Dict, Mapping diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 5fc2aff95..c9f829f9e 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -50,9 +50,11 @@ def dash_ag_grid(data_frame=None, **kwargs): defaults = { "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( - lambda x: x.dt.strftime("%Y-%m-%d") # set date columns to `dateString` for AGGrid filtering to function - if pd.api.types.is_datetime64_any_dtype(x) - else x + lambda x: ( + x.dt.strftime("%Y-%m-%d") # set date columns to `dateString` for AGGrid filtering to function + if pd.api.types.is_datetime64_any_dtype(x) + else x + ) ).to_dict("records"), "defaultColDef": { # "editable": True, #do not set, as this may confuse some users diff --git a/vizro-core/src/vizro/tables/dash_table.py b/vizro-core/src/vizro/tables/dash_table.py index 0b44c6440..41cc43ded 100644 --- a/vizro-core/src/vizro/tables/dash_table.py +++ b/vizro-core/src/vizro/tables/dash_table.py @@ -1,4 +1,5 @@ """Module containing the standard implementation of `dash_table.DataTable`.""" + import pandas as pd from dash import dash_table From 69a7b0737eb3a536ef198f01e3b64ad32612b1b0 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 10:09:08 +0100 Subject: [PATCH 017/128] Linting --- vizro-core/src/vizro/models/_components/table.py | 2 +- vizro-core/src/vizro/models/types.py | 4 ++-- vizro-core/src/vizro/tables/dash_aggrid.py | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 1648716a9..25ce2c5d2 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -75,7 +75,7 @@ def _get_figure_interaction_input(self) -> Dict[str, State]: def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: - """Function to be carried out for pre-defined `filter_interaction`""" + """Function to be carried out for pre-defined `filter_interaction`.""" ctd_active_cell = ctd_filter_interaction["active_cell"] ctd_derived_viewport_data = ctd_filter_interaction["derived_viewport_data"] if not ctd_active_cell["value"] or not ctd_derived_viewport_data["value"]: diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index bfa82aae7..834fbc24d 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -242,8 +242,8 @@ class capture: """ - def __init__(self, mode: Literal["graph", "action", "table"]): - """Instantiates the decorator to capture a function call. Valid modes are "graph", "table" and "action".""" + def __init__(self, mode: Literal["graph", "action", "table", "grid"]): + """Decorator to capture a function call. Valid modes are "graph", "table", "action" and "grid".""" self._mode = mode def __call__(self, func, /): diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index c9f829f9e..f06b6a8a2 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -1,3 +1,5 @@ +"""Module containing the standard implementation of `dash-ag-grid.AgGrid`.""" + import dash_ag_grid as dag import pandas as pd From d81b1c873906dccd0e59aab6e0b79a18672a8404 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 10:31:50 +0100 Subject: [PATCH 018/128] Small updates to grid model --- vizro-core/src/vizro/models/_components/grid.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/grid.py b/vizro-core/src/vizro/models/_components/grid.py index 35cb42316..3bdaa3b11 100644 --- a/vizro-core/src/vizro/models/_components/grid.py +++ b/vizro-core/src/vizro/models/_components/grid.py @@ -92,19 +92,17 @@ def pre_build(self): if self.actions: kwargs = self.figure._arguments.copy() - # This workaround is needed because the underlying table object requires a data_frame + # taken from table implementation - see there for details kwargs["data_frame"] = pd.DataFrame() + underlying_grid_object = self.figure._function(**kwargs) - # The underlying table object is pre-built, so we can fetch its ID. - underlying_table_object = self.figure._function(**kwargs) - - if not hasattr(underlying_table_object, "id"): + if not hasattr(underlying_grid_object, "id"): raise ValueError( - "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" - " a valid 'id' has to be provided to the `Table` callable." + "Underlying `Grid` callable has no attribute 'id'. To enable actions triggered by the `Grid`" + " a valid 'id' has to be provided to the `Grid` callable." ) - self._callable_object_id = underlying_table_object.id + self._callable_object_id = underlying_grid_object.id def build(self): return dcc.Loading( From 8703d0e817b19a9733fcacdd931b07970c23ddf7 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 10:48:21 +0100 Subject: [PATCH 019/128] Remove ununsed files --- .../src/vizro/actions/_actions_utils.py | 47 ------------------- 1 file changed, 47 deletions(-) diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index e1d09eac1..e330ddb41 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -69,31 +69,6 @@ def _apply_filters(data_frame: pd.DataFrame, ctds_filters: List[CallbackTriggerD return data_frame -def _apply_graph_filter_interaction( - data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] -) -> pd.DataFrame: - ctd_click_data = ctd_filter_interaction["clickData"] - if not ctd_click_data["value"]: - return data_frame - - source_graph_id: ModelID = ctd_click_data["id"] - source_graph_actions = _get_component_actions(model_manager[source_graph_id]) - try: - custom_data_columns = model_manager[source_graph_id]["custom_data"] - except KeyError as exc: - raise KeyError(f"No `custom_data` argument found for source graph with id {source_graph_id}.") from exc - - customdata = ctd_click_data["value"]["points"][0]["customdata"] - - for action in source_graph_actions: - if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: - continue - for custom_data_idx, column in enumerate(custom_data_columns): - data_frame = data_frame[data_frame[column].isin([customdata[custom_data_idx]])] - - return data_frame - - def _get_parent_vizro_model(_underlying_callable_object_id: str) -> VizroBaseModel: from vizro.models import VizroBaseModel @@ -108,28 +83,6 @@ def _get_parent_vizro_model(_underlying_callable_object_id: str) -> VizroBaseMod ) -def _apply_table_filter_interaction( - data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] -) -> pd.DataFrame: - ctd_active_cell = ctd_filter_interaction["active_cell"] - ctd_derived_viewport_data = ctd_filter_interaction["derived_viewport_data"] - if not ctd_active_cell["value"] or not ctd_derived_viewport_data["value"]: - return data_frame - - # ctd_active_cell["id"] represents the underlying table id, so we need to fetch its parent Vizro Table actions. - source_table_actions = _get_component_actions(_get_parent_vizro_model(ctd_active_cell["id"])) - - for action in source_table_actions: - if action.function._function.__name__ != "filter_interaction" or target not in action.function["targets"]: - continue - column = ctd_active_cell["value"]["column_id"] - derived_viewport_data_row = ctd_active_cell["value"]["row"] - clicked_data = ctd_derived_viewport_data["value"][derived_viewport_data_row][column] - data_frame = data_frame[data_frame[column].isin([clicked_data])] - - return data_frame - - def _apply_filter_interaction( data_frame: pd.DataFrame, ctds_filter_interaction: List[Dict[str, CallbackTriggerDict]], target: str ) -> pd.DataFrame: From 58bb8b907be421fd8ca58a07dcac4a75526da11c Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 09:58:33 +0000 Subject: [PATCH 020/128] Change prettier settings Signed-off-by: Jo Stichbury --- .prettierignore | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.prettierignore b/.prettierignore index 79caa21f3..673199ae5 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,5 +1,2 @@ -**/docs/pages/API_reference/ -**/docs/pages/user_guides -**/docs/pages/tutorials -**/docs/index.md +**/docs/ **/.devcontainer/devcontainer.json From cb585a34204e6d18230a0beb2890940b7bdf3b7d Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Mon, 19 Feb 2024 17:25:21 +0000 Subject: [PATCH 021/128] Remove underscores Signed-off-by: Jo Stichbury --- ...y_in_vizro_ai.md => safety-in-vizro-ai.md} | 0 ...xplore_vizro_ai.md => explore-vizro-ai.md} | 0 .../api_setup.md => user-guides/api-setup.md} | 0 .../{user_guides => user-guides}/install.md | 0 .../model-config.md} | 0 .../run-vizro-ai.md} | 0 vizro-ai/mkdocs.yml | 12 ++-- .../actions.md | 0 .../manager.md | 0 .../models.md | 0 .../{API_reference => API-reference}/vizro.md | 0 .../{why_vizro.md => why-vizro.md} | 0 ...re_components.md => explore-components.md} | 0 ...{first_dashboard.md => first-dashboard.md} | 0 .../{user_guides => user-guides}/actions.md | 0 .../{user_guides => user-guides}/assets.md | 0 .../card-button.md} | 0 .../components.md | 0 .../{user_guides => user-guides}/container.md | 0 .../custom-actions.md} | 0 .../custom-charts.md} | 0 .../custom-components.md} | 0 .../custom-tables.md} | 0 .../{user_guides => user-guides}/dashboard.md | 0 .../{user_guides => user-guides}/data.md | 0 .../{user_guides => user-guides}/filters.md | 0 .../{user_guides => user-guides}/graph.md | 0 .../{user_guides => user-guides}/install.md | 0 .../integration.md | 0 .../{user_guides => user-guides}/layouts.md | 0 .../navigation.md | 0 .../{user_guides => user-guides}/pages.md | 0 .../parameters.md | 0 .../pages/{user_guides => user-guides}/run.md | 0 .../{user_guides => user-guides}/selectors.md | 0 .../{user_guides => user-guides}/table.md | 0 .../{user_guides => user-guides}/tabs.md | 0 .../{user_guides => user-guides}/themes.md | 0 vizro-core/mkdocs.yml | 62 +++++++++---------- 39 files changed, 37 insertions(+), 37 deletions(-) rename vizro-ai/docs/pages/explanation/{safety_in_vizro_ai.md => safety-in-vizro-ai.md} (100%) rename vizro-ai/docs/pages/tutorials/{explore_vizro_ai.md => explore-vizro-ai.md} (100%) rename vizro-ai/docs/pages/{user_guides/api_setup.md => user-guides/api-setup.md} (100%) rename vizro-ai/docs/pages/{user_guides => user-guides}/install.md (100%) rename vizro-ai/docs/pages/{user_guides/model_config.md => user-guides/model-config.md} (100%) rename vizro-ai/docs/pages/{user_guides/run_vizro_ai.md => user-guides/run-vizro-ai.md} (100%) rename vizro-core/docs/pages/{API_reference => API-reference}/actions.md (100%) rename vizro-core/docs/pages/{API_reference => API-reference}/manager.md (100%) rename vizro-core/docs/pages/{API_reference => API-reference}/models.md (100%) rename vizro-core/docs/pages/{API_reference => API-reference}/vizro.md (100%) rename vizro-core/docs/pages/explanation/{why_vizro.md => why-vizro.md} (100%) rename vizro-core/docs/pages/tutorials/{explore_components.md => explore-components.md} (100%) rename vizro-core/docs/pages/tutorials/{first_dashboard.md => first-dashboard.md} (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/actions.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/assets.md (100%) rename vizro-core/docs/pages/{user_guides/card_button.md => user-guides/card-button.md} (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/components.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/container.md (100%) rename vizro-core/docs/pages/{user_guides/custom_actions.md => user-guides/custom-actions.md} (100%) rename vizro-core/docs/pages/{user_guides/custom_charts.md => user-guides/custom-charts.md} (100%) rename vizro-core/docs/pages/{user_guides/custom_components.md => user-guides/custom-components.md} (100%) rename vizro-core/docs/pages/{user_guides/custom_tables.md => user-guides/custom-tables.md} (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/dashboard.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/data.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/filters.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/graph.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/install.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/integration.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/layouts.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/navigation.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/pages.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/parameters.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/run.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/selectors.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/table.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/tabs.md (100%) rename vizro-core/docs/pages/{user_guides => user-guides}/themes.md (100%) diff --git a/vizro-ai/docs/pages/explanation/safety_in_vizro_ai.md b/vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md similarity index 100% rename from vizro-ai/docs/pages/explanation/safety_in_vizro_ai.md rename to vizro-ai/docs/pages/explanation/safety-in-vizro-ai.md diff --git a/vizro-ai/docs/pages/tutorials/explore_vizro_ai.md b/vizro-ai/docs/pages/tutorials/explore-vizro-ai.md similarity index 100% rename from vizro-ai/docs/pages/tutorials/explore_vizro_ai.md rename to vizro-ai/docs/pages/tutorials/explore-vizro-ai.md diff --git a/vizro-ai/docs/pages/user_guides/api_setup.md b/vizro-ai/docs/pages/user-guides/api-setup.md similarity index 100% rename from vizro-ai/docs/pages/user_guides/api_setup.md rename to vizro-ai/docs/pages/user-guides/api-setup.md diff --git a/vizro-ai/docs/pages/user_guides/install.md b/vizro-ai/docs/pages/user-guides/install.md similarity index 100% rename from vizro-ai/docs/pages/user_guides/install.md rename to vizro-ai/docs/pages/user-guides/install.md diff --git a/vizro-ai/docs/pages/user_guides/model_config.md b/vizro-ai/docs/pages/user-guides/model-config.md similarity index 100% rename from vizro-ai/docs/pages/user_guides/model_config.md rename to vizro-ai/docs/pages/user-guides/model-config.md diff --git a/vizro-ai/docs/pages/user_guides/run_vizro_ai.md b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md similarity index 100% rename from vizro-ai/docs/pages/user_guides/run_vizro_ai.md rename to vizro-ai/docs/pages/user-guides/run-vizro-ai.md diff --git a/vizro-ai/mkdocs.yml b/vizro-ai/mkdocs.yml index 9e58cc5ac..27ae651f2 100644 --- a/vizro-ai/mkdocs.yml +++ b/vizro-ai/mkdocs.yml @@ -4,17 +4,17 @@ nav: - Vizro-AI: index.md - Get started: - Quickstart: pages/tutorials/quickstart.md - - Explore Vizro-AI: pages/tutorials/explore_vizro_ai.md + - Explore Vizro-AI: pages/tutorials/explore-vizro-ai.md - User Guides: - Fundamentals: - - Installation: pages/user_guides/install.md - - API Setup: pages/user_guides/api_setup.md - - Run Methods: pages/user_guides/run_vizro_ai.md - - Model Configuration: pages/user_guides/model_config.md + - Installation: pages/user-guides/install.md + - API Setup: pages/user-guides/api-setup.md + - Run Methods: pages/user-guides/run-vizro-ai.md + - Model Configuration: pages/user-guides/model-config.md - Explanation: - Disclaimer: pages/explanation/disclaimer.md - Safeguard Code Execution: pages/explanation/safeguard.md - - Safety in Vizro AI: pages/explanation/safety_in_vizro_ai.md + - Safety in Vizro AI: pages/explanation/safety-in-vizro-ai.md - Contribute: # - Contributing: pages/contribute/contributing.md - Authors: pages/contribute/authors.md diff --git a/vizro-core/docs/pages/API_reference/actions.md b/vizro-core/docs/pages/API-reference/actions.md similarity index 100% rename from vizro-core/docs/pages/API_reference/actions.md rename to vizro-core/docs/pages/API-reference/actions.md diff --git a/vizro-core/docs/pages/API_reference/manager.md b/vizro-core/docs/pages/API-reference/manager.md similarity index 100% rename from vizro-core/docs/pages/API_reference/manager.md rename to vizro-core/docs/pages/API-reference/manager.md diff --git a/vizro-core/docs/pages/API_reference/models.md b/vizro-core/docs/pages/API-reference/models.md similarity index 100% rename from vizro-core/docs/pages/API_reference/models.md rename to vizro-core/docs/pages/API-reference/models.md diff --git a/vizro-core/docs/pages/API_reference/vizro.md b/vizro-core/docs/pages/API-reference/vizro.md similarity index 100% rename from vizro-core/docs/pages/API_reference/vizro.md rename to vizro-core/docs/pages/API-reference/vizro.md diff --git a/vizro-core/docs/pages/explanation/why_vizro.md b/vizro-core/docs/pages/explanation/why-vizro.md similarity index 100% rename from vizro-core/docs/pages/explanation/why_vizro.md rename to vizro-core/docs/pages/explanation/why-vizro.md diff --git a/vizro-core/docs/pages/tutorials/explore_components.md b/vizro-core/docs/pages/tutorials/explore-components.md similarity index 100% rename from vizro-core/docs/pages/tutorials/explore_components.md rename to vizro-core/docs/pages/tutorials/explore-components.md diff --git a/vizro-core/docs/pages/tutorials/first_dashboard.md b/vizro-core/docs/pages/tutorials/first-dashboard.md similarity index 100% rename from vizro-core/docs/pages/tutorials/first_dashboard.md rename to vizro-core/docs/pages/tutorials/first-dashboard.md diff --git a/vizro-core/docs/pages/user_guides/actions.md b/vizro-core/docs/pages/user-guides/actions.md similarity index 100% rename from vizro-core/docs/pages/user_guides/actions.md rename to vizro-core/docs/pages/user-guides/actions.md diff --git a/vizro-core/docs/pages/user_guides/assets.md b/vizro-core/docs/pages/user-guides/assets.md similarity index 100% rename from vizro-core/docs/pages/user_guides/assets.md rename to vizro-core/docs/pages/user-guides/assets.md diff --git a/vizro-core/docs/pages/user_guides/card_button.md b/vizro-core/docs/pages/user-guides/card-button.md similarity index 100% rename from vizro-core/docs/pages/user_guides/card_button.md rename to vizro-core/docs/pages/user-guides/card-button.md diff --git a/vizro-core/docs/pages/user_guides/components.md b/vizro-core/docs/pages/user-guides/components.md similarity index 100% rename from vizro-core/docs/pages/user_guides/components.md rename to vizro-core/docs/pages/user-guides/components.md diff --git a/vizro-core/docs/pages/user_guides/container.md b/vizro-core/docs/pages/user-guides/container.md similarity index 100% rename from vizro-core/docs/pages/user_guides/container.md rename to vizro-core/docs/pages/user-guides/container.md diff --git a/vizro-core/docs/pages/user_guides/custom_actions.md b/vizro-core/docs/pages/user-guides/custom-actions.md similarity index 100% rename from vizro-core/docs/pages/user_guides/custom_actions.md rename to vizro-core/docs/pages/user-guides/custom-actions.md diff --git a/vizro-core/docs/pages/user_guides/custom_charts.md b/vizro-core/docs/pages/user-guides/custom-charts.md similarity index 100% rename from vizro-core/docs/pages/user_guides/custom_charts.md rename to vizro-core/docs/pages/user-guides/custom-charts.md diff --git a/vizro-core/docs/pages/user_guides/custom_components.md b/vizro-core/docs/pages/user-guides/custom-components.md similarity index 100% rename from vizro-core/docs/pages/user_guides/custom_components.md rename to vizro-core/docs/pages/user-guides/custom-components.md diff --git a/vizro-core/docs/pages/user_guides/custom_tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md similarity index 100% rename from vizro-core/docs/pages/user_guides/custom_tables.md rename to vizro-core/docs/pages/user-guides/custom-tables.md diff --git a/vizro-core/docs/pages/user_guides/dashboard.md b/vizro-core/docs/pages/user-guides/dashboard.md similarity index 100% rename from vizro-core/docs/pages/user_guides/dashboard.md rename to vizro-core/docs/pages/user-guides/dashboard.md diff --git a/vizro-core/docs/pages/user_guides/data.md b/vizro-core/docs/pages/user-guides/data.md similarity index 100% rename from vizro-core/docs/pages/user_guides/data.md rename to vizro-core/docs/pages/user-guides/data.md diff --git a/vizro-core/docs/pages/user_guides/filters.md b/vizro-core/docs/pages/user-guides/filters.md similarity index 100% rename from vizro-core/docs/pages/user_guides/filters.md rename to vizro-core/docs/pages/user-guides/filters.md diff --git a/vizro-core/docs/pages/user_guides/graph.md b/vizro-core/docs/pages/user-guides/graph.md similarity index 100% rename from vizro-core/docs/pages/user_guides/graph.md rename to vizro-core/docs/pages/user-guides/graph.md diff --git a/vizro-core/docs/pages/user_guides/install.md b/vizro-core/docs/pages/user-guides/install.md similarity index 100% rename from vizro-core/docs/pages/user_guides/install.md rename to vizro-core/docs/pages/user-guides/install.md diff --git a/vizro-core/docs/pages/user_guides/integration.md b/vizro-core/docs/pages/user-guides/integration.md similarity index 100% rename from vizro-core/docs/pages/user_guides/integration.md rename to vizro-core/docs/pages/user-guides/integration.md diff --git a/vizro-core/docs/pages/user_guides/layouts.md b/vizro-core/docs/pages/user-guides/layouts.md similarity index 100% rename from vizro-core/docs/pages/user_guides/layouts.md rename to vizro-core/docs/pages/user-guides/layouts.md diff --git a/vizro-core/docs/pages/user_guides/navigation.md b/vizro-core/docs/pages/user-guides/navigation.md similarity index 100% rename from vizro-core/docs/pages/user_guides/navigation.md rename to vizro-core/docs/pages/user-guides/navigation.md diff --git a/vizro-core/docs/pages/user_guides/pages.md b/vizro-core/docs/pages/user-guides/pages.md similarity index 100% rename from vizro-core/docs/pages/user_guides/pages.md rename to vizro-core/docs/pages/user-guides/pages.md diff --git a/vizro-core/docs/pages/user_guides/parameters.md b/vizro-core/docs/pages/user-guides/parameters.md similarity index 100% rename from vizro-core/docs/pages/user_guides/parameters.md rename to vizro-core/docs/pages/user-guides/parameters.md diff --git a/vizro-core/docs/pages/user_guides/run.md b/vizro-core/docs/pages/user-guides/run.md similarity index 100% rename from vizro-core/docs/pages/user_guides/run.md rename to vizro-core/docs/pages/user-guides/run.md diff --git a/vizro-core/docs/pages/user_guides/selectors.md b/vizro-core/docs/pages/user-guides/selectors.md similarity index 100% rename from vizro-core/docs/pages/user_guides/selectors.md rename to vizro-core/docs/pages/user-guides/selectors.md diff --git a/vizro-core/docs/pages/user_guides/table.md b/vizro-core/docs/pages/user-guides/table.md similarity index 100% rename from vizro-core/docs/pages/user_guides/table.md rename to vizro-core/docs/pages/user-guides/table.md diff --git a/vizro-core/docs/pages/user_guides/tabs.md b/vizro-core/docs/pages/user-guides/tabs.md similarity index 100% rename from vizro-core/docs/pages/user_guides/tabs.md rename to vizro-core/docs/pages/user-guides/tabs.md diff --git a/vizro-core/docs/pages/user_guides/themes.md b/vizro-core/docs/pages/user-guides/themes.md similarity index 100% rename from vizro-core/docs/pages/user_guides/themes.md rename to vizro-core/docs/pages/user-guides/themes.md diff --git a/vizro-core/mkdocs.yml b/vizro-core/mkdocs.yml index 101ac72bd..09a4e757b 100644 --- a/vizro-core/mkdocs.yml +++ b/vizro-core/mkdocs.yml @@ -3,48 +3,48 @@ site_url: https://mckinsey.github.io/vizro/ nav: - Vizro: index.md - Get started: - - Quickstart: pages/tutorials/first_dashboard.md - - Explore Vizro: pages/tutorials/explore_components.md + - Quickstart: pages/tutorials/first-dashboard.md + - Explore Vizro: pages/tutorials/explore-components.md - User Guides: - FUNDAMENTALS: - - Install: pages/user_guides/install.md - - Dashboards: pages/user_guides/dashboard.md - - Pages: pages/user_guides/pages.md - - Run Methods: pages/user_guides/run.md + - Install: pages/user-guides/install.md + - Dashboards: pages/user-guides/dashboard.md + - Pages: pages/user-guides/pages.md + - Run Methods: pages/user-guides/run.md - COMPONENTS: - - Overview: pages/user_guides/components.md - - Graphs: pages/user_guides/graph.md - - Tables: pages/user_guides/table.md - - Cards & Buttons: pages/user_guides/card_button.md - - Containers: pages/user_guides/container.md - - Tabs: pages/user_guides/tabs.md + - Overview: pages/user-guides/components.md + - Graphs: pages/user-guides/graph.md + - Tables: pages/user-guides/table.md + - Cards & Buttons: pages/user-guides/card-button.md + - Containers: pages/user-guides/container.md + - Tabs: pages/user-guides/tabs.md - CONTROLS: - - Filters: pages/user_guides/filters.md - - Parameters: pages/user_guides/parameters.md - - Selectors: pages/user_guides/selectors.md + - Filters: pages/user-guides/filters.md + - Parameters: pages/user-guides/parameters.md + - Selectors: pages/user-guides/selectors.md - NAVIGATION: - - Navigation: pages/user_guides/navigation.md + - Navigation: pages/user-guides/navigation.md - VISUAL FORMATTING: - - Layouts: pages/user_guides/layouts.md - - Themes: pages/user_guides/themes.md - - Assets: pages/user_guides/assets.md + - Layouts: pages/user-guides/layouts.md + - Themes: pages/user-guides/themes.md + - Assets: pages/user-guides/assets.md - ACTIONS: - - Actions: pages/user_guides/actions.md + - Actions: pages/user-guides/actions.md - DATA CONNECTIONS: - - Data: pages/user_guides/data.md - - Integrations: pages/user_guides/integration.md + - Data: pages/user-guides/data.md + - Integrations: pages/user-guides/integration.md - EXTENSIONS: - - Custom Charts: pages/user_guides/custom_charts.md - - Custom Tables: pages/user_guides/custom_tables.md - - Custom Components: pages/user_guides/custom_components.md - - Custom Actions: pages/user_guides/custom_actions.md + - Custom Charts: pages/user-guides/custom-charts.md + - Custom Tables: pages/user-guides/custom-tables.md + - Custom Components: pages/user-guides/custom-components.md + - Custom Actions: pages/user-guides/custom-actions.md - API Reference: - - Vizro: pages/API_reference/vizro.md - - Models: pages/API_reference/models.md - - Data Manager: pages/API_reference/manager.md - - Actions: pages/API_reference/actions.md + - Vizro: pages/API-reference/vizro.md + - Models: pages/API-reference/models.md + - Data Manager: pages/API-reference/manager.md + - Actions: pages/API-reference/actions.md - Explanation: - - Why Vizro: pages/explanation/why_vizro.md + - Why Vizro: pages/explanation/why-vizro.md - Contribute: - Contributing: pages/development/contributing.md - Authors: pages/development/authors.md From 770e65c92040bee8874c59cb327782b6eddef4d1 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 10:04:48 +0000 Subject: [PATCH 022/128] Add docs to gitignore Signed-off-by: Jo Stichbury --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 03b450eff..79a54175f 100644 --- a/.gitignore +++ b/.gitignore @@ -85,9 +85,9 @@ instance/ # Scrapy .scrapy -# Sphinx -docs/_build/ -my-project/site/ +# mkdocs +vizro-core/site +vizro-ai/site # PyBuilder .pybuilder/ From 520a581144971ee55006a92f9f7a0fa1feb1fe80 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 10:09:43 +0000 Subject: [PATCH 023/128] Fix broken internal links Signed-off-by: Jo Stichbury --- .../pages/tutorials/explore-components.md | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/vizro-core/docs/pages/tutorials/explore-components.md b/vizro-core/docs/pages/tutorials/explore-components.md index e8dffc753..9aeec8d55 100644 --- a/vizro-core/docs/pages/tutorials/explore-components.md +++ b/vizro-core/docs/pages/tutorials/explore-components.md @@ -8,7 +8,7 @@ functionality, and configuring layouts to suit your specific needs. This step-by-step guide is designed to equip you with the knowledge and skills required to create your own dashboards using Vizro. -In case you are completely new, you may want to review our [Get started](../tutorials/first_dashboard.md) tutorial first. +In case you are completely new, you may want to review our [Get started](../tutorials/first-dashboard.md) tutorial first. ## Let's get started! @@ -102,7 +102,7 @@ insert text into your dashboard and can be configured using markdown syntax. For bar chart that visualizes the GDP development per continent since 1952. Remember, you can combine and arrange various types of `components` on a dashboard page. -To see which components are available, refer to our [components](../user_guides/components.md) overview page. +To see which components are available, refer to our [components](../user-guides/components.md) overview page. The user guides of the components serve as a detailed resource to learn how to make the most of them in your dashboards. ??? info "Further information for `components`" @@ -220,7 +220,7 @@ text card occupies one-quarter of it. ![image3](../../assets/tutorials/dashboard/dashboard232.png) Let's see how it is looks on the dashboard when applied to the three components. If you like to learn more about how to -configure layouts, check out the [user guide](../user_guides/layouts.md) +configure layouts, check out the [user guide](../user-guides/layouts.md) !!! example "Configure layout" === "Code" @@ -287,8 +287,8 @@ By incorporating `controls` into your dashboard, you enhance its interactivity a to have greater control and customization over the displayed data and components. ??? info "Further information for `controls`" - The user guides for [Filters](../user_guides/filters.md) and [Parameters](../user_guides/parameters.md) provide a comprehensive overview on how to apply - [`Filters`][vizro.models.Filter] and [`Parameters`][vizro.models.Parameter]. For further customization, refer to the [user guide on selectors](../user_guides/selectors.md). + The user guides for [Filters](../user-guides/filters.md) and [Parameters](../user-guides/parameters.md) provide a comprehensive overview on how to apply + [`Filters`][vizro.models.Filter] and [`Parameters`][vizro.models.Parameter]. For further customization, refer to the [user guide on selectors](../user-guides/selectors.md). In order to link the `controls` to your two charts, it's important to understand the unique id assigned to each `component`. This id is unique across all dashboard pages and serves as a reference for @@ -379,7 +379,7 @@ general, `targets` for [`Parameters`][vizro.models.Parameter] are set following `scatter_iris.color_discrete_map.virginica`. This nested structure allows you to target a specific attribute within a component. In this particular example, it specifies that only the color of the virginica flower type should be changed. More information on how to set `targets` for [`Parameters`][vizro.models.Parameter] can be found in the [user guide -for parameters](../user_guides/parameters.md). +for parameters](../user-guides/parameters.md). !!! example "Second page" === "Code" @@ -707,14 +707,14 @@ After completing the tutorial you now have a solid understanding of the main ele and how to bring them together. For future dashboard creations, you can explore more about the available dashboard `components` by going through our overview page for -[components](../user_guides/components.md). To gain more in-depth knowledge about the usage and configuration -details of `controls`, check out the user guides dedicated to [Filters](../user_guides/filters.md), [Parameters](../user_guides/parameters.md) -and [Selectors](../user_guides/selectors.md). If you'd like to understand more about different ways to configure the navigation of your dashboard, head -to [Navigation](../user_guides/navigation.md). +[components](../user-guides/components.md). To gain more in-depth knowledge about the usage and configuration +details of `controls`, check out the user guides dedicated to [Filters](../user_guides/filters.md), [Parameters](../user-guides/parameters.md) +and [Selectors](../user-guides/selectors.md). If you'd like to understand more about different ways to configure the navigation of your dashboard, head +to [Navigation](../user-guides/navigation.md). Vizro doesn't end here, and we only covered the key features, but there is still much more to explore! You can learn: -- How to create you own components under [custom components](../user_guides/custom_components.md) -- How to add custom styling using [custom css](../user_guides/assets.md) -- How to use [Actions](../user_guides/actions.md) for e.g. chart interaction or custom controls -- How to create dashboards from `yaml`, `dict` or `json` following the [user guide](../user_guides/dashboard.md) +- How to create you own components under [custom components](../user-guides/custom-components.md) +- How to add custom styling using [custom css](../user-guides/assets.md) +- How to use [Actions](../user-guides/actions.md) for e.g. chart interaction or custom controls +- How to create dashboards from `yaml`, `dict` or `json` following the [user guide](../user-guides/dashboard.md) From 3fcbab3941dc8e3ba61ec81d70a613e17751c652 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 11:43:29 +0100 Subject: [PATCH 024/128] Implement default class --- vizro-core/src/vizro/static/css/aggrid.css | 6 ++++-- vizro-core/src/vizro/tables/dash_aggrid.py | 1 + 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 2a96d7239..770f2611b 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -21,6 +21,7 @@ #page-container .ag-theme-alpine .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-02); padding-left: 0px; + padding-right: 12px; } /*Border at bottom when hovering more solid*/ #page-container .ag-theme-alpine .ag-header-cell:hover { @@ -37,9 +38,10 @@ /*height: 48px; !* This does not seem to set it to 48px due to element style *!*/ } #page-container .ag-theme-alpine .ag-cell { - display: flex; + /*display: flex;*/ /*This breaks AGGrid side right alignment of numerical columns*/ align-items: center; - padding: 0px; + padding-left: 0px; + padding-right: 12px; font-size: 14px; } /* Changes header on click, this has the advantage of not remaining when cell is not sorted, but still "clicked" */ diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index f06b6a8a2..bfab01bb0 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -50,6 +50,7 @@ def dash_ag_grid(data_frame=None, **kwargs): """Implementation of `dash-ag-grid.AgGrid` with sensible defaults.""" defaults = { + "className": "ag-theme-alpine vizro", "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( lambda x: ( From 3df98f0572e08c4e6fbfe16b244fe882b860197e Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 11:46:49 +0100 Subject: [PATCH 025/128] Switch base theme to ag-grid-quartz --- vizro-core/src/vizro/static/css/aggrid.css | 18 +++++++++--------- vizro-core/src/vizro/tables/dash_aggrid.py | 3 ++- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 770f2611b..95a098f96 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,4 +1,4 @@ -.ag-theme-alpine.vizro { +.ag-theme-quartz.vizro { --ag-background-color: var(--main-container-bg-color); /* confirmed */ --ag-foreground-color: var(--text-primary); /* confirmed */ --ag-odd-row-background-color: var(--main-container-bg-color); /* confirmed */ @@ -10,7 +10,7 @@ --ag-row-height: 48px; /* This determines the row height, but not header */ } /* General setting header row*/ -#page-container .ag-theme-alpine .ag-header-row { +#page-container .ag-theme-quartz .ag-header-row { color: var(--text-secondary); font-weight: normal; font-size: 14px; @@ -18,26 +18,26 @@ } /* Border at the bottom and padding*/ -#page-container .ag-theme-alpine .ag-header-cell { +#page-container .ag-theme-quartz .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-02); padding-left: 0px; padding-right: 12px; } /*Border at bottom when hovering more solid*/ -#page-container .ag-theme-alpine .ag-header-cell:hover { +#page-container .ag-theme-quartz .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); } /*Border bolder when clicking on header, text now primary color*/ -#page-container .ag-theme-alpine .ag-header-cell:focus { +#page-container .ag-theme-quartz .ag-header-cell:focus { border-bottom: 1px solid var(--border-selected); color: var(--text-primary); } -#page-container .ag-theme-alpine .ag-row { +#page-container .ag-theme-quartz .ag-row { border-bottom: 1px solid var(--border-subtle-alpha-02); /*height: 48px; !* This does not seem to set it to 48px due to element style *!*/ } -#page-container .ag-theme-alpine .ag-cell { +#page-container .ag-theme-quartz .ag-cell { /*display: flex;*/ /*This breaks AGGrid side right alignment of numerical columns*/ align-items: center; padding-left: 0px; @@ -48,8 +48,8 @@ /* Ie after clicking a third time, it should probably go back to the initial state*/ /* However the line appearing is not in extended enough*/ /* Alternative currently used: .ag-header-cell:focus*/ -/*#page-container .ag-theme-alpine .ag-header-cell-sorted-asc,*/ -/*#page-container .ag-theme-alpine .ag-header-cell-sorted-desc {*/ +/*#page-container .ag-theme-quartz .ag-header-cell-sorted-asc,*/ +/*#page-container .ag-theme-quartz .ag-header-cell-sorted-desc {*/ /* border-bottom: 1px solid var(--border-selected);*/ /* color: var(--text-primary);*/ /*}*/ diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index bfab01bb0..97631cf91 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -50,7 +50,8 @@ def dash_ag_grid(data_frame=None, **kwargs): """Implementation of `dash-ag-grid.AgGrid` with sensible defaults.""" defaults = { - "className": "ag-theme-alpine vizro", + # "className": "ag-theme-alpine vizro", + "className": "ag-theme-quartz vizro", "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( lambda x: ( From d686f97be9d41096f1ce72bae482966132bf66f4 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 11:35:26 +0000 Subject: [PATCH 026/128] Fix broken links Signed-off-by: Jo Stichbury --- .../docs/pages/tutorials/explore-components.md | 2 +- vizro-core/docs/pages/tutorials/first-dashboard.md | 8 ++++---- vizro-core/docs/pages/user-guides/actions.md | 2 +- vizro-core/docs/pages/user-guides/card-button.md | 2 +- vizro-core/docs/pages/user-guides/components.md | 2 +- vizro-core/docs/pages/user-guides/custom-charts.md | 2 +- .../docs/pages/user-guides/custom-components.md | 2 +- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- vizro-core/docs/pages/user-guides/selectors.md | 2 +- vizro-core/docs/pages/user-guides/table.md | 2 +- vizro-core/docs/pages/user-guides/tabs.md | 4 ++-- vizro-core/src/vizro/models/types.py | 12 ++++++------ 12 files changed, 21 insertions(+), 21 deletions(-) diff --git a/vizro-core/docs/pages/tutorials/explore-components.md b/vizro-core/docs/pages/tutorials/explore-components.md index 9aeec8d55..791f5373e 100644 --- a/vizro-core/docs/pages/tutorials/explore-components.md +++ b/vizro-core/docs/pages/tutorials/explore-components.md @@ -708,7 +708,7 @@ and how to bring them together. For future dashboard creations, you can explore more about the available dashboard `components` by going through our overview page for [components](../user-guides/components.md). To gain more in-depth knowledge about the usage and configuration -details of `controls`, check out the user guides dedicated to [Filters](../user_guides/filters.md), [Parameters](../user-guides/parameters.md) +details of `controls`, check out the user guides dedicated to [Filters](../user-guides/filters.md), [Parameters](../user-guides/parameters.md) and [Selectors](../user-guides/selectors.md). If you'd like to understand more about different ways to configure the navigation of your dashboard, head to [Navigation](../user-guides/navigation.md). diff --git a/vizro-core/docs/pages/tutorials/first-dashboard.md b/vizro-core/docs/pages/tutorials/first-dashboard.md index 87d75341e..a531e2e1b 100644 --- a/vizro-core/docs/pages/tutorials/first-dashboard.md +++ b/vizro-core/docs/pages/tutorials/first-dashboard.md @@ -2,14 +2,14 @@ This tutorial introduces Vizro. It is a step-by-step guide to create a first dashboard and is designed to equip you with the knowledge to explore the documentation further. -If you want a more complete tutorial exploring more of the available features, please see the [Explore Vizro tutorial](../tutorials/explore_components.md). +If you want a more complete tutorial exploring more of the available features, please see the [Explore Vizro tutorial](../tutorials/explore-components.md). ## Let's get started! ### 1. Install Vizro and its dependencies -If you haven't already installed Vizro, follow the [installation guide](../user_guides/install.md) to do so inside a virtual environment. +If you haven't already installed Vizro, follow the [installation guide](../user-guides/install.md) to do so inside a virtual environment. If you consider yourself a beginner to python and/or virtual environments, there is also a section that avoids any use of terminals and relies only upon a graphical user interface. @@ -30,7 +30,7 @@ jupyter notebook The command opens a browser tab and you can navigate to your preferred folder for this new project. Create a new notebook `Python 3 (ipykernel)` notebook from the "New" dropdown. ??? tip "Beginners/Code novices" - If you followed the beginners steps in the the [installation guide](../user_guides/install.md), you should already be set, and you can continue below. + If you followed the beginners steps in the the [installation guide](../user-guides/install.md), you should already be set, and you can continue below. Confirm that Vizro is installed by typing the following into a cell in the notebook and running it. @@ -82,4 +82,4 @@ After running the dashboard, you can access the dashboard by typing `localhost:8 You are now ready to explore our documentation further, as you can now easily evaluate all examples. -One place to start would be the more complete [Explore Vizro tutorial](../tutorials/explore_components.md). +One place to start would be the more complete [Explore Vizro tutorial](../tutorials/explore-components.md). diff --git a/vizro-core/docs/pages/user-guides/actions.md b/vizro-core/docs/pages/user-guides/actions.md index 20ba5a969..a6e577edb 100644 --- a/vizro-core/docs/pages/user-guides/actions.md +++ b/vizro-core/docs/pages/user-guides/actions.md @@ -378,4 +378,4 @@ The order of action execution is guaranteed, and the next action in the list wil [Graph3]: ../../assets/user_guides/actions/actions_chaining.png -To enhance existing actions, please see our How-to-guide on creating [custom actions](custom_actions.md). +To enhance existing actions, please see our How-to-guide on creating [custom actions](custom-actions.md). diff --git a/vizro-core/docs/pages/user-guides/card-button.md b/vizro-core/docs/pages/user-guides/card-button.md index d9853798d..f67b80e85 100755 --- a/vizro-core/docs/pages/user-guides/card-button.md +++ b/vizro-core/docs/pages/user-guides/card-button.md @@ -688,7 +688,7 @@ In the below example we show how to configure a button to export the filtered da The [`Button`][vizro.models.Button] component is currently reserved to be used inside the main panel (right-side) of the dashboard. However, there might be use cases where one would like to place the `Button` inside the control panel (left-side) with the other controls. -In this case, simply follow the user-guide outlined for [custom components](custom_components.md) and manually add the `Button` as a valid type to the `controls` argument by running the following lines before your dashboard configurations: +In this case, simply follow the user-guide outlined for [custom components](custom-components.md) and manually add the `Button` as a valid type to the `controls` argument by running the following lines before your dashboard configurations: ```python from vizro import Vizro diff --git a/vizro-core/docs/pages/user-guides/components.md b/vizro-core/docs/pages/user-guides/components.md index 597f09678..b32c4e1ae 100755 --- a/vizro-core/docs/pages/user-guides/components.md +++ b/vizro-core/docs/pages/user-guides/components.md @@ -28,7 +28,7 @@ listed below to fill your dashboard with visuals. Use cards and buttons to visualize text, navigate to different URLs or attach any [action](actions.md). - [:octicons-arrow-right-24: View User Guide](card_button.md) + [:octicons-arrow-right-24: View User Guide](card-button.md) - :octicons-table-16:{ .lg .middle } __Container__ diff --git a/vizro-core/docs/pages/user-guides/custom-charts.md b/vizro-core/docs/pages/user-guides/custom-charts.md index d4fb7f236..d2951c3b0 100644 --- a/vizro-core/docs/pages/user-guides/custom-charts.md +++ b/vizro-core/docs/pages/user-guides/custom-charts.md @@ -1,7 +1,7 @@ # How to create custom charts This guide shows you how to create custom charts and how to add them to your dashboard. -The [`Graph`][vizro.models.Graph] model accepts the `figure` argument, where you can enter _any_ [`plotly.express`](https://plotly.com/python/plotly-express/) chart as explained in the [user guide on graphs][graph.md]. +The [`Graph`][vizro.models.Graph] model accepts the `figure` argument, where you can enter _any_ [`plotly.express`](https://plotly.com/python/plotly-express/) chart as explained in the [user guide on graphs](graph.md). ## Overview of custom charts diff --git a/vizro-core/docs/pages/user-guides/custom-components.md b/vizro-core/docs/pages/user-guides/custom-components.md index 752869c51..3ff4f7bed 100644 --- a/vizro-core/docs/pages/user-guides/custom-components.md +++ b/vizro-core/docs/pages/user-guides/custom-components.md @@ -29,7 +29,7 @@ or if you would like to use additional `args` or `kwargs` of those components, t We will refer back to these three steps in the two examples below. -[^1]: You can easily check if your new component will be part of a discriminated union by consulting our [API reference on models](../API_reference/models.md). Check whether the relevant model field (e.g. `selectors` in [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter]) is described as a discriminated union (in this case the [`SelectorType`][vizro.models.types.SelectorType] is, but for example [`OptionsType`][vizro.models.types.OptionsType] is not). +[^1]: You can easily check if your new component will be part of a discriminated union by consulting our [API reference on models](../API-reference/models.md). Check whether the relevant model field (e.g. `selectors` in [`Filter`][vizro.models.Filter] or [`Parameter`][vizro.models.Parameter]) is described as a discriminated union (in this case the [`SelectorType`][vizro.models.types.SelectorType] is, but for example [`OptionsType`][vizro.models.types.OptionsType] is not). ## Extend an existing component diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 6578c5f5c..0870e2b4b 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -2,7 +2,7 @@ If you want to use the [`Table`][vizro.models.Table] model to and to create a custom [table](table.md) you can create your own custom table, e.g. when requiring computations that can be controlled by parameters. -For this, similar to how one would create a [custom chart](../user_guides/custom_charts.md), simply do the following: +For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: - define a function that returns a `dash_table.DataTable` object - decorate it with the `@capture("table")` decorator diff --git a/vizro-core/docs/pages/user-guides/selectors.md b/vizro-core/docs/pages/user-guides/selectors.md index 9d0a12e8f..14aef61fc 100644 --- a/vizro-core/docs/pages/user-guides/selectors.md +++ b/vizro-core/docs/pages/user-guides/selectors.md @@ -42,4 +42,4 @@ For more details, kindly refer to the documentation of the underlying dash compo To our knowledge, this is a current bug in the underlying [`dcc.Slider`](https://dash.plotly.com/dash-core-components/slider) and [`dcc.RangeSlider`](https://dash.plotly.com/dash-core-components/rangeslider) component, which you can circumvent by adapting the `step` size accordingly. -To enhance existing selectors, please see our How-to-guide on creating [custom components](custom_components.md) +To enhance existing selectors, please see our How-to-guide on creating [custom components](custom-components.md) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 3a41ffa70..aa5e17520 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -193,4 +193,4 @@ an example of a styled table where some conditional formatting is applied. There [Table2]: ../../assets/user_guides/table/styled_table.png -To enhance existing tables, please see our How-to-guide on creating [custom tables](custom_tables.md). +To enhance existing tables, please see our How-to-guide on creating [custom tables](custom-tables.md). diff --git a/vizro-core/docs/pages/user-guides/tabs.md b/vizro-core/docs/pages/user-guides/tabs.md index 01a082019..79a54d47f 100755 --- a/vizro-core/docs/pages/user-guides/tabs.md +++ b/vizro-core/docs/pages/user-guides/tabs.md @@ -2,7 +2,7 @@ [`Tabs`][vizro.models.Tabs] organize and separate groups of related content in a dashboard, letting users switch between different sections or views. They are essentially a way of putting multiple [`Containers`][vizro.models.Container] in the same screen space, and letting the user switch between them. -`Containers` enable the grouping of page components into sections and subsections; see our [user guide on `Containers`](container.md) for more information. +`Containers` enable the grouping of page components into sections and subsections; see our [user guide on `Containers`](./container.md) for more information.
![tabs](../../assets/user_guides/components/tabs-info.png){ width="400"} @@ -10,7 +10,7 @@ They are essentially a way of putting multiple [`Containers`][vizro.models.Conta
Both `Tabs` and `Containers` are a more advanced technique for customizing your page layout. If you simply want to arrange components on a page, -we recommend reading our [user guide on `Layout`](layout.md) first. +we recommend reading our [user guide on `Layout`](layouts.md) first. This guide shows you how to use tabs to organize your `Containers` into subsections inside the dashboard. diff --git a/vizro-core/src/vizro/models/types.py b/vizro-core/src/vizro/models/types.py index 7127568f3..6b79d373f 100644 --- a/vizro-core/src/vizro/models/types.py +++ b/vizro-core/src/vizro/models/types.py @@ -36,11 +36,11 @@ class CapturedCallable: `functools.partial`. Ready-to-use `CapturedCallable` instances are provided by Vizro. In this case refer to the [user guide on - Charts/Graph][graph], [Table][table] or [Actions][pre-defined-actions] to see available choices. + Charts/Graph](../user-guides/graph.md), [Table](../user-guides/table.md) or [Actions](../user-guides/actions.md) to see available choices. (Advanced) In case you would like to create your own `CapturedCallable`, please refer to the [user guide on - custom charts](../user_guides/custom_charts.md), [custom tables][custom-table] or - [custom actions][custom-actions]. + custom charts](../user-guides/custom-charts.md), [custom tables](../user-guides/custom-tables.md) or + [custom actions](../user-guides/custom-actions.md). """ def __init__(self, function, /, *args, **kwargs): @@ -231,11 +231,11 @@ class capture: >>> ... For further help on the use of `@capture("graph")`, you can refer to the guide on - [custom graphs](../user_guides/custom_charts.md). + [custom graphs](../user-guides/custom-charts.md). For further help on the use of `@capture("table")`, you can refer to the guide on - [custom tables](../user_guides/table#custom-table). + [custom tables](../user-guides/custom-tables.md). For further help on the use of `@capture("action")`, you can refer to the guide on - [custom actions](../user_guides/actions/#custom-actions). + [custom actions](../user-guides/custom-actions.md). """ From dd375f798bea59b964e38847aadcb3617f866ee0 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 11:51:35 +0000 Subject: [PATCH 027/128] Fix broken links Signed-off-by: Jo Stichbury --- vizro-ai/docs/index.md | 4 ++-- vizro-ai/docs/pages/explanation/disclaimer.md | 2 +- vizro-ai/docs/pages/tutorials/explore-vizro-ai.md | 8 ++++---- vizro-ai/docs/pages/tutorials/quickstart.md | 10 +++++----- vizro-ai/docs/pages/user-guides/install.md | 2 +- vizro-ai/docs/pages/user-guides/run-vizro-ai.md | 4 ++-- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/vizro-ai/docs/index.md b/vizro-ai/docs/index.md index 31f61a372..33fed5078 100644 --- a/vizro-ai/docs/index.md +++ b/vizro-ai/docs/index.md @@ -18,7 +18,7 @@ Vizro-AI leverages the power of [Plotly](https://plotly.com/python/) to produce before using the `vizro-ai` package. Since users must connect to Large Language Models (LLMs) in order to use Vizro-AI, - please also ensure that you review our guides on the [usage of LLMs](pages/explanation/safety_in_vizro_ai.md) + please also ensure that you review our guides on the [usage of LLMs](pages/explanation/safety-in-vizro-ai.md) and the required [safeguarding for dynamic code evaluation](pages/explanation/safeguard.md). @@ -36,7 +36,7 @@ Vizro-AI leverages the power of [Plotly](https://plotly.com/python/) to produce - +
User Guides
diff --git a/vizro-ai/docs/pages/explanation/disclaimer.md b/vizro-ai/docs/pages/explanation/disclaimer.md index 71473d98a..27b233f7e 100644 --- a/vizro-ai/docs/pages/explanation/disclaimer.md +++ b/vizro-ai/docs/pages/explanation/disclaimer.md @@ -1,6 +1,6 @@ # Disclaimer -Users must select one of the [supported Large Language Models (LLMs)](../user_guides/model_config.md) in order to use the `vizro_ai` package, +Users must select one of the [supported Large Language Models (LLMs)](../user-guides/model-config.md) in order to use the `vizro_ai` package, and are responsible for obtaining their own suitable API key for the relevant model. ## Third Party API diff --git a/vizro-ai/docs/pages/tutorials/explore-vizro-ai.md b/vizro-ai/docs/pages/tutorials/explore-vizro-ai.md index 96b2d0af1..5ae025d5a 100644 --- a/vizro-ai/docs/pages/tutorials/explore-vizro-ai.md +++ b/vizro-ai/docs/pages/tutorials/explore-vizro-ai.md @@ -5,19 +5,19 @@ By the end of this tutorial, you will have gained an understanding of different ## Let's get started! ### 1. Install Vizro-AI and get ready to run your code -Before proceeding, ensure the installation of the `vizro_ai` package by following the steps outlined in the [installation guide](../user_guides/install.md). Once installed, you can execute your code either by pasting it into a Jupyter notebook cell or running it from a Python script. +Before proceeding, ensure the installation of the `vizro_ai` package by following the steps outlined in the [installation guide](../user-guides/install.md). Once installed, you can execute your code either by pasting it into a Jupyter notebook cell or running it from a Python script. ??? tip "Beginners/Code novices" - For those new to Python or virtual environments, a user-friendly alternative is available in the [installation guide](../user_guides/install.md), offering a graphical user interface without the need for terminal commands. + For those new to Python or virtual environments, a user-friendly alternative is available in the [installation guide](../user-guides/install.md), offering a graphical user interface without the need for terminal commands. -A prerequisite for this tutorial is access to one of the supported large language models. Please refer to the [api setup](../user_guides/api_setup.md) for instructions on setting up the API. +A prerequisite for this tutorial is access to one of the supported large language models. Please refer to the [api setup](../user-guides/api-setup.md) for instructions on setting up the API. Upon successful setup, your API key should be ready in the environment when you import `vizro_ai`. If you would like to customize the `.env` file location and name, you can set it manually. You can override the default import of the `.env` file by specifying the path and name of your custom `.env` file. -Please refer to [API setup](../user_guides/api_setup.md) for instructions on customizing the `.env` file location and name. +Please refer to [API setup](../user-guides/api-setup.md) for instructions on customizing the `.env` file location and name. ### 2. Create your visualization using different languages diff --git a/vizro-ai/docs/pages/tutorials/quickstart.md b/vizro-ai/docs/pages/tutorials/quickstart.md index dcfad4b8d..d35e3800e 100644 --- a/vizro-ai/docs/pages/tutorials/quickstart.md +++ b/vizro-ai/docs/pages/tutorials/quickstart.md @@ -4,11 +4,11 @@ It is a step-by-step guide to help you experiment and create your initial Vizro ## Let's get started! ### 1. Install Vizro-AI and its dependencies -If you haven't already installed `vizro_ai` package, follow the [installation guide](../user_guides/install.md) +If you haven't already installed `vizro_ai` package, follow the [installation guide](../user-guides/install.md) to do so inside a virtual environment. ??? tip "Beginners/Code novices" - If you consider yourself a beginner to python and/or virtual environments, there is also a section in the [installation guide](../user_guides/install.md) that avoids any use of terminals and relies only upon a graphical user interface. + If you consider yourself a beginner to python and/or virtual environments, there is also a section in the [installation guide](../user-guides/install.md) that avoids any use of terminals and relies only upon a graphical user interface. ### 2. Set up jupyter notebook A good way to initially explore Vizro-AI is from a Jupyter notebook. @@ -26,7 +26,7 @@ jupyter notebook This opens a browser tab, and you can navigate to your preferred folder for this new project. Create a new notebook Python 3 (ipykernel) notebook from the "New" dropdown. Make sure that you select your environment as kernel. ??? tip "Beginners/Code novices" - If you followed the beginners steps in the [installation guide](../user_guides/install.md), you should already be set, and you can continue below. + If you followed the beginners steps in the [installation guide](../user-guides/install.md), you should already be set, and you can continue below. Confirm that `vizro_ai` is installed by typing the following into a jupyter cell in your notebook and running it. @@ -39,7 +39,7 @@ You should see a return output of the version. ### 3. Large Language Model (LLM) API KEY -A prerequisite to use Vizro-AI is access to one of the supported LLMs. Refer to the [user guide](../user_guides/api_setup.md) on how to set up the API. +A prerequisite to use Vizro-AI is access to one of the supported LLMs. Refer to the [user guide](../user-guides/api-setup.md) on how to set up the API. ### 4. Ask your first question using Vizro-AI @@ -87,4 +87,4 @@ Let's create another example and read through the additional information. Now, you have created your first charts with Vizro-AI and are ready to explore the documentation further. -A good place to start would be to go through the [model configuration](../user_guides/model_config.md) or different [run options](../user_guides/run_vizro_ai.md) including application integration. +A good place to start would be to go through the [model configuration](../user-guides/model-config.md) or different [run options](../user-guides/run-vizro-ai.md) including application integration. diff --git a/vizro-ai/docs/pages/user-guides/install.md b/vizro-ai/docs/pages/user-guides/install.md index 2dfd246f3..cb53dcecc 100644 --- a/vizro-ai/docs/pages/user-guides/install.md +++ b/vizro-ai/docs/pages/user-guides/install.md @@ -15,7 +15,7 @@ export OPENAI_API_KEY="..." ``` -More details can be found in the [environment setup guide](../user_guides/api_setup.md). +More details can be found in the [environment setup guide](../user-guides/api-setup.md). ??? tip "Beginners/Code novices" If you are a beginner or new to coding and wish to avoid using the terminal, you can follow these steps: diff --git a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md index 976bded98..d43dfa7fa 100644 --- a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md +++ b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md @@ -6,7 +6,7 @@ This guide offers insights into different options of running Vizro-AI, including To run Vizro-AI in jupyter, create a new cell and execute the code below to render the described visualization. You should see the chart as an output. ??? note "Note: API key" - Make sure you have followed the [environment setup guide](../user_guides/api_setup.md) and + Make sure you have followed the [environment setup guide](../user-guides/api-setup.md) and your api key is set up in a `.env` file in the same folder as your `ipynb` file. !!! example "Bar chart" @@ -31,7 +31,7 @@ Please note that the chart's appearance may not precisely resemble the one displ You can utilize Vizro-AI in any standard development environment by creating a `.py` file and executing the following code. As a result, the rendered chart will display in a browser window. ??? note "Note: API key" - Make sure you have followed [environment setup guide](../user_guides/api_setup.md) and + Make sure you have followed [environment setup guide](../user-guides/api-setup.md) and your API key is set up in the environment where your `.py` script is running with command as below: ``` export OPENAI_API_KEY="your api key" From db2d92d07c37cdcd2cbbcf4a6675f2f995b111d6 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 11:56:41 +0000 Subject: [PATCH 028/128] Add changelog Signed-off-by: Jo Stichbury --- ...ichbury_second_branch_docs_name_changes.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md diff --git a/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md new file mode 100644 index 000000000..04843dd23 --- /dev/null +++ b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md @@ -0,0 +1,48 @@ + + + + + + +### Changed + +- Changed filenames to replace underscores with hyphens and fix resulting internal link breaks ([#321](https://github.com/mckinsey/vizro/pull/321)) + + + + + From e355f551834db64a67baab21bc1fdc5eebf12628 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 20 Feb 2024 11:57:01 +0000 Subject: [PATCH 029/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- ...240220_115353_jo_stichbury_second_branch_docs_name_changes.md | 1 - 1 file changed, 1 deletion(-) diff --git a/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md index 04843dd23..b24f29a54 100644 --- a/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md +++ b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md @@ -27,7 +27,6 @@ Uncomment the section that is right (remove the HTML comment wrapper). - Changed filenames to replace underscores with hyphens and fix resulting internal link breaks ([#321](https://github.com/mckinsey/vizro/pull/321)) - + + + + + +### Changed + +Changed filenames to replace underscores with hyphens and fix resulting internal link breaks ([#321](https://github.com/mckinsey/vizro/pull/321)) + + + + From 695b6fd12a998d0da24281037fc64193730f8423 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 20 Feb 2024 15:24:12 +0100 Subject: [PATCH 041/128] Delete commented lines --- vizro-core/src/vizro/static/css/aggrid.css | 1 - vizro-core/src/vizro/tables/dash_aggrid.py | 1 - 2 files changed, 2 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index ee626c1e4..e4533d6b2 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -35,7 +35,6 @@ #page-container .ag-theme-quartz .ag-row { border-bottom: 1px solid var(--border-subtle-alpha-02); - /*height: 48px; !* This does not seem to set it to 48px due to element style *!*/ } #page-container .ag-theme-quartz .ag-cell { /*display: flex;*/ /*This breaks AGGrid side right alignment of numerical columns*/ diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 0cbed6355..0f461ea94 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -50,7 +50,6 @@ def dash_ag_grid(data_frame=None, **kwargs): """Implementation of `dash-ag-grid.AgGrid` with sensible defaults.""" defaults = { - # "className": "ag-theme-alpine vizro", "className": "ag-theme-quartz vizro", "columnDefs": [{"field": col, "flex": 1, "minWidth": 70} for col in data_frame.columns], "rowData": data_frame.apply( From 18d40383b245fef397bd6eb2a56ee708393fafd4 Mon Sep 17 00:00:00 2001 From: Jo Stichbury Date: Tue, 20 Feb 2024 15:36:47 +0000 Subject: [PATCH 042/128] Roll back content in changelog files Signed-off-by: Jo Stichbury --- ...20_115353_jo_stichbury_second_branch_docs_name_changes.md | 5 +++-- ...20_140357_jo_stichbury_second_branch_docs_name_changes.md | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md index b24f29a54..f1f65e73c 100644 --- a/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md +++ b/vizro-ai/changelog.d/20240220_115353_jo_stichbury_second_branch_docs_name_changes.md @@ -22,11 +22,12 @@ Uncomment the section that is right (remove the HTML comment wrapper). - A bullet item for the Added category with a link to the relevant PR at the end of your entry, e.g. Enable feature XXX ([#1](https://github.com/mckinsey/vizro/pull/1)) --> - + - + + + + + + + + + From 4d05653483797f5ae3e5b595b0cbafb9787adaff Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 08:49:39 +0000 Subject: [PATCH 054/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/src/vizro/static/css/aggrid.css | 43 +++++++++++++--------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 66256e774..5d5114c0c 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -5,29 +5,32 @@ --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; --ag-borders: none; - --ag-icon-font-family: agGridQuartz; + --ag-icon-font-family: aggridquartz; --ag-icon-size: 14px; --ag-row-height: 48px; /* This determines the row height, but not header */ --ag-header-height: 40px; } -/* General setting header row*/ + +/* General setting header row */ #page-container .ag-theme-quartz .ag-header-row { color: var(--text-secondary); - font-weight: normal; font-size: 14px; + font-weight: normal; } -/* Border at the bottom and padding*/ +/* Border at the bottom and padding */ #page-container .ag-theme-quartz .ag-header-cell { border-bottom: 1px solid var(--border-subtle-alpha-02); - padding-left: 0px; + padding-left: 0; padding-right: 12px; } -/*Border at bottom when hovering more solid*/ + +/* Border at bottom when hovering more solid */ #page-container .ag-theme-quartz .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); } -/*Border bolder when clicking on header, text now primary color*/ + +/* Border bolder when clicking on header, text now primary color */ #page-container .ag-theme-quartz .ag-header-cell:focus { border-bottom: 1px solid var(--border-selected); color: var(--text-primary); @@ -36,50 +39,56 @@ #page-container .ag-theme-quartz .ag-row { border-bottom: 1px solid var(--border-subtle-alpha-02); } + #page-container .ag-theme-quartz .ag-cell { - /*display: flex;*/ /*This breaks AGGrid side right alignment of numerical columns*/ + /* display: flex; */ + + /* This breaks AGGrid side right alignment of numerical columns */ align-items: center; - padding-left: 0px; - padding-right: 12px; font-size: 14px; + padding-left: 0; + padding-right: 12px; } -/*Filter menu styling*/ +/* Filter menu styling */ #page-container .ag-theme-quartz .ag-menu { background-color: var(--surfaces-bg-02); - color: var(--text-primary); border: 1px solid var(--border-subtle-alpha-02); + color: var(--text-primary); } -/*Selector for different filtering conditions*/ +/* Selector for different filtering conditions */ #page-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); border: 1px solid var(--border-subtle-alpha-01); } + #page-container .ag-select-list { background-color: var(--field-enabled); border: 1px solid var(--border-subtle-alpha-01); color: var(--text-primary); } -/*Text/number input*/ +/* Text/number input */ #page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); - color: var(--text-primary); border: 1px solid var(--border-enabled); + color: var(--text-primary); } -/*TODO: fix the color of the background images in the filter menu*/ +/* TODO: fix the color of the background images in the filter menu */ -/*Buttons*/ +/* Buttons */ #page-container .ag-theme-quartz .ag-standard-button { background-color: var(--state-overlays-selected); border: 1px solid var(--border-subtle-alpha-01); color: var(--text-primary); } + #page-container .ag-theme-quartz .ag-standard-button:hover { background-color: var(--state-overlays-selected-hover); } + #page-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); color: var(--text-primary); From 63f3e84091295e777eef16746a247e175366ad1f Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 10:08:48 +0100 Subject: [PATCH 055/128] Switch column sizing to defaults --- vizro-core/src/vizro/tables/dash_aggrid.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 251bb4b7b..62d49c4a2 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -51,7 +51,7 @@ def dash_ag_grid(data_frame, **kwargs): """Implementation of `dash_ag_grid.AgGrid` with sensible defaults.""" defaults = { "className": "ag-theme-quartz vizro", - "columnDefs": [{"field": col, "flex": 1, "minWidth": 70} for col in data_frame.columns], + "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( lambda x: ( x.dt.strftime("%Y-%m-%d") # set date columns to `dateString` for AG Grid filtering to function @@ -68,6 +68,8 @@ def dash_ag_grid(data_frame, **kwargs): "buttons": ["apply", "reset"], "closeOnApply": True, }, + "flex": 1, + "minWidth": 70 }, "dashGridOptions": { "dataTypeDefinitions": DATA_TYPE_DEFINITIONS, From a4ea384d234edfe9db6b997604a4a465e83694cb Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 09:09:13 +0000 Subject: [PATCH 056/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/src/vizro/static/css/aggrid.css | 2 +- vizro-core/src/vizro/tables/dash_aggrid.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 5d5114c0c..f3e5144ab 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -43,7 +43,7 @@ #page-container .ag-theme-quartz .ag-cell { /* display: flex; */ - /* This breaks AGGrid side right alignment of numerical columns */ + /* This breaks AGGrid side right alignment of numerical columns */ align-items: center; font-size: 14px; padding-left: 0; diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 62d49c4a2..0c2567d21 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -69,7 +69,7 @@ def dash_ag_grid(data_frame, **kwargs): "closeOnApply": True, }, "flex": 1, - "minWidth": 70 + "minWidth": 70, }, "dashGridOptions": { "dataTypeDefinitions": DATA_TYPE_DEFINITIONS, From dd7c3dfedba9205c3d48c13054fbda102f6d5b4e Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 13:10:33 +0100 Subject: [PATCH 057/128] Turn off pagination --- vizro-core/src/vizro/tables/dash_aggrid.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/dash_aggrid.py index 0c45c6df2..45af842af 100644 --- a/vizro-core/src/vizro/tables/dash_aggrid.py +++ b/vizro-core/src/vizro/tables/dash_aggrid.py @@ -71,7 +71,6 @@ def dash_ag_grid(data_frame, **kwargs): "dashGridOptions": { "dataTypeDefinitions": DATA_TYPE_DEFINITIONS, "animateRows": False, - "pagination": True, }, } kwargs = _set_defaults_nested(kwargs, defaults) From 842e68de1ddfca3090b5eec5bb810f906942d21c Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 14:37:23 +0100 Subject: [PATCH 058/128] Replace datatable placeholder with aggrid --- vizro-core/src/vizro/models/_components/aggrid.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/aggrid.py b/vizro-core/src/vizro/models/_components/aggrid.py index 1f176195e..d40f10757 100644 --- a/vizro-core/src/vizro/models/_components/aggrid.py +++ b/vizro-core/src/vizro/models/_components/aggrid.py @@ -1,8 +1,9 @@ import logging from typing import Dict, List, Literal +import dash_ag_grid as dag import pandas as pd -from dash import State, dash_table, dcc, html +from dash import State, dcc, html try: from pydantic.v1 import Field, PrivateAttr, validator @@ -110,9 +111,7 @@ def build(self): html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div( - dash_table.DataTable(**({"id": self._callable_object_id} if self.actions else {})), id=self.id - ), + html.Div(dag.AgGrid(**({"id": self._callable_object_id} if self.actions else {})), id=self.id), ], className="table-container", id=f"{self.id}_outer", From 3a5123f5c0b38c50ef56f82df59fbc140ca588c5 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 14:47:12 +0100 Subject: [PATCH 059/128] First PR comments --- vizro-core/docs/pages/user-guides/aggrid.md | 26 +++++++++---------- .../docs/pages/user-guides/custom-tables.md | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/aggrid.md b/vizro-core/docs/pages/user-guides/aggrid.md index 795e819a5..1cb196205 100644 --- a/vizro-core/docs/pages/user-guides/aggrid.md +++ b/vizro-core/docs/pages/user-guides/aggrid.md @@ -1,21 +1,21 @@ -# How to use AGGrids +# How to use AG Grids -This guide shows you how to use the [AG Grid](https://www.ag-grid.com/) to visualize your tabular data in the dashboard. +This guide shows you how to use [AG Grid](https://www.ag-grid.com/) to visualize your tabular data in the dashboard. It is an interactive table/grid component designed for viewing, editing, and exploring large datasets. and Vizro's recommended table implementation. -The Vizro [`AGGrid`][vizro.models.AGGrid] model is based on the [Dash AG Grid](https://dash.plotly.com/dash-ag-grid), which is in turn based the +The Vizro [`AgGrid`][vizro.models.AgGrid] model is based on the [Dash AG Grid](https://dash.plotly.com/dash-ag-grid), which is in turn based the original [Javascript implementation](https://www.ag-grid.com/). ## Basic usage -To add a [`AGGrid`][vizro.models.AGGrid] to your page, do the following: +To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: -- insert the [`AGGrid`][vizro.models.AGGrid] model into the `components` argument of the +- insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the [`Page`][vizro.models.Page] model - enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) -The Vizro version of this AGGrid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. +The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. This must be entered under the argument `data_frame`. All other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Note that some defaults are set for some of the arguments (e.g. for `columnDefs`) to help with styling and usability. @@ -81,7 +81,7 @@ columnDefs = [{"field": "", "cellDataType": "euro"}] In the below example we select and format some columns of the gapminder dataset. -??? example "AGGrid with formatted columns" +??? example "AG Grid with formatted columns" === "app.py" ```py import vizro.models as vm @@ -95,10 +95,10 @@ In the below example we select and format some columns of the gapminder dataset. {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] page = vm.Page( - title="Example of AGGrid with formatted columns", + title="Example of AG Grid with formatted columns", components=[ vm.Table( - title="AGGrid with formatted columns", + title="AG Grid with formatted columns", figure=dash_ag_grid( data_frame=df, columnDefs=columnDefs, @@ -129,9 +129,9 @@ In the below example we select and format some columns of the gapminder dataset. cellDataType: dollar - field: pop cellDataType: numeric - title: AGGrid with formatted columns + title: AG Grid with formatted columns type: table - title: Example of AGGrid with formatted columns + title: Example of AG Grid with formatted columns ``` === "Result" [![Table2]][Table2] @@ -140,8 +140,8 @@ In the below example we select and format some columns of the gapminder dataset. ### Dates -In order for the [`AGGrid`][vizro.models.AGGrid] model to sort and filter dates correctly, the date must either be of -string format `yyyy-mm-dd` (see [Dash AGGrid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) +In order for the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly, the date must either be of +string format `yyyy-mm-dd` (see [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) or a pandas datetime object. Any pandas datetime column will be transformed into the `yyyy-mm-dd` format automatically. ### Objects/Strings diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index e2ab9d7dd..f9b157f5e 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -1,7 +1,7 @@ -# How to create custom Dash Datatables and Dash AGGrids +# How to create custom Dash Datatables and Dash AG Grids -In case that the available arguments for the [`Table`][vizro.models.Table] or [`AGGrid`][vizro.models.AGGrid] models are not sufficient, -there is always the possibility to create a custom Dash DataTable or Dash AGGrid. +In case that the available arguments for the [`Table`][vizro.models.Table] or [`AgGrid`][vizro.models.AgGrid] models are not sufficient, +there is always the possibility to create a custom Dash DataTable or Dash AG Grid. One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see below example). From 1cab466a5e2cb4e52fa3a1ae088058d6f309c886 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 14:51:07 +0100 Subject: [PATCH 060/128] Further PR comments --- vizro-core/docs/pages/user-guides/aggrid.md | 2 +- vizro-core/docs/pages/user-guides/table.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/aggrid.md b/vizro-core/docs/pages/user-guides/aggrid.md index 1cb196205..ba6a157a8 100644 --- a/vizro-core/docs/pages/user-guides/aggrid.md +++ b/vizro-core/docs/pages/user-guides/aggrid.md @@ -146,7 +146,7 @@ or a pandas datetime object. Any pandas datetime column will be transformed into ### Objects/Strings -No specific formatting is available for custom objects and strings, however one can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) +No specific formatting is available for custom objects and strings, however you can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) in order to format e.g. displayed strings automatically. diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 2bfccdf15..0f1ba61ef 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -186,4 +186,4 @@ an example of a styled table where some conditional formatting is applied. There [Table2]: ../../assets/user_guides/table/styled_table.png -If the available arguments are not sufficient, there is always the possibility to create a [custom Dash DataTable](custom-tables.md). +If the available arguments are not sufficient, there is always the option to create a [custom Dash DataTable](custom-tables.md). From 7842fcec33f8382373b3d1ffbef891cb8573e19d Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 15:32:59 +0100 Subject: [PATCH 061/128] Further PR comments --- vizro-core/docs/pages/user-guides/aggrid.md | 32 ++-- vizro-core/examples/_dev/app.py | 162 +++++------------- .../examples/_dev/yaml_version/dashboard.yaml | 81 ++++----- 3 files changed, 99 insertions(+), 176 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/aggrid.md b/vizro-core/docs/pages/user-guides/aggrid.md index ba6a157a8..ee1fb01a0 100644 --- a/vizro-core/docs/pages/user-guides/aggrid.md +++ b/vizro-core/docs/pages/user-guides/aggrid.md @@ -2,7 +2,7 @@ This guide shows you how to use [AG Grid](https://www.ag-grid.com/) to visualize your tabular data in the dashboard. It is an interactive table/grid component designed for viewing, editing, and exploring large datasets. -and Vizro's recommended table implementation. +AG Grid is Vizro's recommended table implementation. The Vizro [`AgGrid`][vizro.models.AgGrid] model is based on the [Dash AG Grid](https://dash.plotly.com/dash-ag-grid), which is in turn based the original [Javascript implementation](https://www.ag-grid.com/). @@ -33,7 +33,7 @@ Note that some defaults are set for some of the arguments (e.g. for `columnDefs` page = vm.Page( title="Example of a Dash AG Grid", components=[ - vm.Grid(title="Dash AG Grid", figure=dash_ag_grid(data_frame=df)), + vm.AgGrid(title="Dash AG Grid", figure=dash_ag_grid(data_frame=df)), ], controls=[vm.Filter(column="continent")], ) @@ -51,7 +51,7 @@ Note that some defaults are set for some of the arguments (e.g. for `columnDefs` _target_: dash_ag_grid data_frame: gapminder title: Dash AG Grid - type: grid + type: ag_grid controls: - column: continent type: filter @@ -88,16 +88,16 @@ In the below example we select and format some columns of the gapminder dataset. import vizro.plotly.express as px from vizro import Vizro from vizro.tables import dash_ag_grid - + df = px.data.gapminder() - + columnDefs = [{"field": "country"}, {"field": "year"}, {"field": "lifeExp", "cellDataType": "numeric"}, {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] - + page = vm.Page( title="Example of AG Grid with formatted columns", components=[ - vm.Table( + vm.AgGrid( title="AG Grid with formatted columns", figure=dash_ag_grid( data_frame=df, @@ -106,9 +106,9 @@ In the below example we select and format some columns of the gapminder dataset. ) ], ) - + dashboard = vm.Dashboard(pages=[page]) - + Vizro().build(dashboard).run() ``` === "app.yaml" @@ -130,7 +130,7 @@ In the below example we select and format some columns of the gapminder dataset. - field: pop cellDataType: numeric title: AG Grid with formatted columns - type: table + type: ag_grid title: Example of AG Grid with formatted columns ``` === "Result" @@ -153,7 +153,7 @@ in order to format e.g. displayed strings automatically. ## Further styling and customization As mentioned above, all [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Below you can find -an example of a styled table where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. +an example of a styled AG Grid where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. There are many more ways to alter the grid beyond this showcase. ??? example "Styled and modified Dash AG Grid" @@ -209,7 +209,7 @@ There are many more ways to alter the grid beyond this showcase. page = vm.Page( title="Example of Modified Dash AG Grid", components=[ - vm.Table( + vm.AgGrid( title="Modified Dash AG Grid", figure=dash_ag_grid( data_frame=df, @@ -261,16 +261,12 @@ There are many more ways to alter the grid beyond this showcase. type: rightAligned valueFormatter: function: "d3.format(',.0f')(params.value)" - defaultColDef: + defaultColDef: resizable: false filter: false editable: true title: Dash AG Grid - id: table - type: table - controls: - - column: continent - type: filter + type: ag_grid title: Example of a Dash AG Grid ``` === "Result" diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index ef6649e12..7cba7ef14 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,132 +1,64 @@ -"""Example to show dashboard configuration.""" - -import numpy as np -import pandas as pd import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.actions import export_data, filter_interaction -from vizro.tables import dash_ag_grid, dash_data_table +from vizro.tables import dash_ag_grid df = px.data.gapminder() -df_mean = ( - df.groupby(by=["continent", "year"]).agg({"lifeExp": "mean", "pop": "mean", "gdpPercap": "mean"}).reset_index() -) -df_transformed = df.copy() -df_transformed["lifeExp"] = df.groupby(by=["continent", "year"])["lifeExp"].transform("mean") -df_transformed["gdpPercap"] = df.groupby(by=["continent", "year"])["gdpPercap"].transform("mean") -df_transformed["pop"] = df.groupby(by=["continent", "year"])["pop"].transform("sum") -df_concat = pd.concat([df_transformed.assign(color="Continent Avg."), df.assign(color="Country")], ignore_index=True) +cellStyle = { + "styleConditions": [ + { + "condition": "params.value < 1045", + "style": {"backgroundColor": "#ff9222"}, + }, + { + "condition": "params.value >= 1045 && params.value <= 4095", + "style": {"backgroundColor": "#de9e75"}, + }, + { + "condition": "params.value > 4095 && params.value <= 12695", + "style": {"backgroundColor": "#aaa9ba"}, + }, + { + "condition": "params.value > 12695", + "style": {"backgroundColor": "#00b4ff"}, + }, + ] +} +columnDefs = [ + {"field": "country"}, + {"field": "continent"}, + {"field": "year"}, + { + "field": "lifeExp", + "valueFormatter": {"function": "d3.format('.1f')(params.value)"}, + }, + { + "field": "gdpPercap", + "valueFormatter": {"function": "d3.format('$,.1f')(params.value)"}, + "cellStyle": cellStyle, + }, + { + "field": "pop", + "valueFormatter": {"function": "d3.format(',.0f')(params.value)"}, + }, +] -grid_interaction = vm.Page( - title="AG Grid and Table Interaction", +page = vm.Page( + title="Example of Modified Dash AG Grid", components=[ vm.AgGrid( - id="table_country_new", - title="Click on a cell", + title="Modified Dash AG Grid", figure=dash_ag_grid( - id="dash_ag_grid_1", - data_frame=px.data.gapminder(), - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Table( - id="table_country", - title="Click on a cell", - figure=dash_data_table( - id="dash_data_table_country", data_frame=df, - columns=[{"id": col, "name": col} for col in df.columns], - sort_action="native", - style_cell={"textAlign": "left"}, - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Graph( - id="line_country", - figure=px.line( - df_concat, - title="Country vs. Continent", - x="year", - y="gdpPercap", - color="color", - labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, - color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, - markers=True, - hover_name="country", - ), - ), - vm.Button( - text="Export data", - actions=[ - vm.Action( - function=export_data( - targets=["line_country"], - ) - ), - ], - ), - ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), - vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), - vm.Parameter( - targets=["line_country.y"], - selector=vm.Dropdown( - options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" - ), - ), - ], -) - - -df2 = px.data.stocks() -df2["date_as_datetime"] = pd.to_datetime(df2["date"]) -df2["date_str"] = df2["date"].astype("str") -df2["perc_from_float"] = np.random.rand(len(df2)) -df2["random"] = np.random.uniform(-100000.000, 100000.000, len(df2)) - -grid_standard = vm.Page( - title="AG Grid Default", - components=[ - vm.AgGrid( - figure=dash_ag_grid( - id="dash_ag_grid_2", - data_frame=df2, - ), - ), - ], -) - -grid_custom = vm.Page( - title="AG Grid Custom", - components=[ - vm.AgGrid( - figure=dash_ag_grid( - id="dash_ag_grid_3", - data_frame=df2, - columnDefs=[ - {"field": "AAPL", "headerName": "Format Dollar", "cellDataType": "dollar"}, - {"field": "AAPL", "headerName": "Format Euro", "cellDataType": "euro"}, - {"field": "random", "headerName": "Format Numeric", "cellDataType": "numeric"}, - {"field": "perc_from_float", "headerName": "Format Percent", "cellDataType": "percent"}, - { - "field": "perc_from_float", - "headerName": "custom format", - "valueFormatter": {"function": "d3.format('.^30')(params.value)"}, - }, - ], + columnDefs=columnDefs, + defaultColDef={"resizable": False, "filter": False, "editable": True}, ), - ), + ) ], ) +dashboard = vm.Dashboard(pages=[page]) -dashboard = vm.Dashboard( - pages=[grid_interaction, grid_standard, grid_custom], -) - -if __name__ == "__main__": - Vizro().build(dashboard).run() +Vizro().build(dashboard).run() \ No newline at end of file diff --git a/vizro-core/examples/_dev/yaml_version/dashboard.yaml b/vizro-core/examples/_dev/yaml_version/dashboard.yaml index c085a85ec..9b46c6017 100644 --- a/vizro-core/examples/_dev/yaml_version/dashboard.yaml +++ b/vizro-core/examples/_dev/yaml_version/dashboard.yaml @@ -1,47 +1,42 @@ +# Still requires a .py to register data connector in Data Manager and parse yaml configuration +# See from_yaml example pages: - components: - figure: - _target_: line + _target_: dash_ag_grid data_frame: gapminder - x: year - y: lifeExp - color: continent - title: Graph 1 - type: graph - - figure: - _target_: scatter - data_frame: gapminder - x: gdpPercap - y: lifeExp - size: pop - color: continent - title: Graph 2 - type: graph - - figure: - _target_: box - data_frame: gapminder - x: continent - y: lifeExp - color: continent - title: Graph 3 - type: graph - - figure: - _target_: line - data_frame: gapminder - x: year - y: lifeExp - color: continent - title: Graph 4 - type: graph - - figure: - _target_: scatter - data_frame: gapminder - x: gdpPercap - y: lifeExp - size: pop - color: continent - title: Graph 5 - type: graph - layout: - grid: [[0, 1, 3, 4], [2, 2, 3, 4]] - title: Custom Layout - Advanced Example + columnDefs: + - field: country + - field: continent + - field: year + - field: lifeExp + valueFormatter: + function: "d3.format('.1f')(params.value)" + - field: gdpPercap + valueFormatter: + function: "d3.format('$,.1f')(params.value)" + cellStyle: + styleConditions: + - condition: params.value < 1045 + style: + backgroundColor: "#ff9222" + - condition: params.value >= 1045 && params.value <= 4095 + style: + backgroundColor: "#de9e75" + - condition: params.value > 4095 && params.value <= 12695 + style: + backgroundColor: "#aaa9ba" + - condition: params.value > 12695 + style: + backgroundColor: "#00b4ff" + - field: pop + type: rightAligned + valueFormatter: + function: "d3.format(',.0f')(params.value)" + defaultColDef: + resizable: false + filter: false + editable: true + title: Dash AG Grid + type: ag_grid + title: Example of a Dash AG Grid \ No newline at end of file From 4d3ac345d1ce08afb30c27142e1906985350ac2b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 14:33:55 +0000 Subject: [PATCH 062/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/docs/pages/user-guides/aggrid.md | 10 +++++----- vizro-core/examples/_dev/app.py | 2 +- vizro-core/examples/_dev/yaml_version/dashboard.yaml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/aggrid.md b/vizro-core/docs/pages/user-guides/aggrid.md index ee1fb01a0..3f6b8da5f 100644 --- a/vizro-core/docs/pages/user-guides/aggrid.md +++ b/vizro-core/docs/pages/user-guides/aggrid.md @@ -88,12 +88,12 @@ In the below example we select and format some columns of the gapminder dataset. import vizro.plotly.express as px from vizro import Vizro from vizro.tables import dash_ag_grid - + df = px.data.gapminder() - + columnDefs = [{"field": "country"}, {"field": "year"}, {"field": "lifeExp", "cellDataType": "numeric"}, {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] - + page = vm.Page( title="Example of AG Grid with formatted columns", components=[ @@ -106,9 +106,9 @@ In the below example we select and format some columns of the gapminder dataset. ) ], ) - + dashboard = vm.Dashboard(pages=[page]) - + Vizro().build(dashboard).run() ``` === "app.yaml" diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 7cba7ef14..25b46c22f 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -61,4 +61,4 @@ dashboard = vm.Dashboard(pages=[page]) -Vizro().build(dashboard).run() \ No newline at end of file +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_dev/yaml_version/dashboard.yaml b/vizro-core/examples/_dev/yaml_version/dashboard.yaml index 9b46c6017..30ee4a829 100644 --- a/vizro-core/examples/_dev/yaml_version/dashboard.yaml +++ b/vizro-core/examples/_dev/yaml_version/dashboard.yaml @@ -39,4 +39,4 @@ pages: editable: true title: Dash AG Grid type: ag_grid - title: Example of a Dash AG Grid \ No newline at end of file + title: Example of a Dash AG Grid From 9ed2d4e8a75994728b98629cfd22f0e8a8adf9e4 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 17:35:38 +0100 Subject: [PATCH 063/128] COnsolidate Table guides into one page --- vizro-core/docs/pages/user-guides/aggrid.md | 277 ------------------ vizro-core/docs/pages/user-guides/table.md | 305 +++++++++++++++++++- vizro-core/mkdocs.yml | 1 - 3 files changed, 301 insertions(+), 282 deletions(-) delete mode 100644 vizro-core/docs/pages/user-guides/aggrid.md diff --git a/vizro-core/docs/pages/user-guides/aggrid.md b/vizro-core/docs/pages/user-guides/aggrid.md deleted file mode 100644 index 3f6b8da5f..000000000 --- a/vizro-core/docs/pages/user-guides/aggrid.md +++ /dev/null @@ -1,277 +0,0 @@ -# How to use AG Grids - -This guide shows you how to use [AG Grid](https://www.ag-grid.com/) to visualize your tabular data in the dashboard. -It is an interactive table/grid component designed for viewing, editing, and exploring large datasets. -AG Grid is Vizro's recommended table implementation. - -The Vizro [`AgGrid`][vizro.models.AgGrid] model is based on the [Dash AG Grid](https://dash.plotly.com/dash-ag-grid), which is in turn based the -original [Javascript implementation](https://www.ag-grid.com/). - -## Basic usage - -To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: - -- insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the -[`Page`][vizro.models.Page] model -- enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) - -The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. -This must be entered under the argument `data_frame`. All other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. -Note that some defaults are set for some of the arguments (e.g. for `columnDefs`) to help with styling and usability. - - -!!! example "Basic Dash AG Grid" - === "app.py" - ```py - import vizro.models as vm - import vizro.plotly.express as px - from vizro import Vizro - from vizro.tables import dash_ag_grid - - df = px.data.gapminder() - - page = vm.Page( - title="Example of a Dash AG Grid", - components=[ - vm.AgGrid(title="Dash AG Grid", figure=dash_ag_grid(data_frame=df)), - ], - controls=[vm.Filter(column="continent")], - ) - dashboard = vm.Dashboard(pages=[page]) - - Vizro().build(dashboard).run() - ``` - === "app.yaml" - ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration - # See from_yaml example - pages: - - components: - - figure: - _target_: dash_ag_grid - data_frame: gapminder - title: Dash AG Grid - type: ag_grid - controls: - - column: continent - type: filter - title: Example of a Dash AG Grid - ``` - === "Result" - [![Table]][Table] - - [Table]: ../../assets/user_guides/table/table.png - -## Formatting columns - -### Numbers - -One of the most common tasks when working with tables is to format the columns such that displayed numbers are more readable. -In order to do this, one can use the native functionality of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -or make use of the Vizro pre-defined [Custom Cell Data Types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. - -The available custom cell types for Vizro are `dollar`, `euro`, `percentage` and `numeric`. - -In order to use these, define your desired `` alongside the chosen `cellDataType` in -the `columnDefs` argument of your `dash_ag_grid` function: - -```py -columnDefs = [{"field": "", "cellDataType": "euro"}] -``` - -In the below example we select and format some columns of the gapminder dataset. - -??? example "AG Grid with formatted columns" - === "app.py" - ```py - import vizro.models as vm - import vizro.plotly.express as px - from vizro import Vizro - from vizro.tables import dash_ag_grid - - df = px.data.gapminder() - - columnDefs = [{"field": "country"}, {"field": "year"}, {"field": "lifeExp", "cellDataType": "numeric"}, - {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] - - page = vm.Page( - title="Example of AG Grid with formatted columns", - components=[ - vm.AgGrid( - title="AG Grid with formatted columns", - figure=dash_ag_grid( - data_frame=df, - columnDefs=columnDefs, - ), - ) - ], - ) - - dashboard = vm.Dashboard(pages=[page]) - - Vizro().build(dashboard).run() - ``` - === "app.yaml" - ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration - # See from_yaml example - pages: - - components: - - figure: - _target_: dash_ag_grid - data_frame: gapminder - columnDefs: - - field: country - - field: year - - field: lifeExp - cellDataType: numeric - - field: gdpPercap - cellDataType: dollar - - field: pop - cellDataType: numeric - title: AG Grid with formatted columns - type: ag_grid - title: Example of AG Grid with formatted columns - ``` - === "Result" - [![Table2]][Table2] - - [Table2]: ../../assets/user_guides/table/styled_table.png - -### Dates - -In order for the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly, the date must either be of -string format `yyyy-mm-dd` (see [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) -or a pandas datetime object. Any pandas datetime column will be transformed into the `yyyy-mm-dd` format automatically. - -### Objects/Strings - -No specific formatting is available for custom objects and strings, however you can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -in order to format e.g. displayed strings automatically. - - -## Further styling and customization - -As mentioned above, all [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Below you can find -an example of a styled AG Grid where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. -There are many more ways to alter the grid beyond this showcase. - -??? example "Styled and modified Dash AG Grid" - === "app.py" - ```py - import vizro.models as vm - import vizro.plotly.express as px - from vizro import Vizro - from vizro.tables import dash_ag_grid - - df = px.data.gapminder() - - cellStyle = { - "styleConditions": [ - { - "condition": "params.value < 1045", - "style": {"backgroundColor": "#ff9222"}, - }, - { - "condition": "params.value >= 1045 && params.value <= 4095", - "style": {"backgroundColor": "#de9e75"}, - }, - { - "condition": "params.value > 4095 && params.value <= 12695", - "style": {"backgroundColor": "#aaa9ba"}, - }, - { - "condition": "params.value > 12695", - "style": {"backgroundColor": "#00b4ff"}, - }, - ] - } - - columnDefs = [ - {"field": "country"}, - {"field": "continent"}, - {"field": "year"}, - { - "field": "lifeExp", - "valueFormatter": {"function": "d3.format('.1f')(params.value)"}, - }, - { - "field": "gdpPercap", - "valueFormatter": {"function": "d3.format('$,.1f')(params.value)"}, - "cellStyle": cellStyle, - }, - { - "field": "pop", - "valueFormatter": {"function": "d3.format(',.0f')(params.value)"}, - }, - ] - - page = vm.Page( - title="Example of Modified Dash AG Grid", - components=[ - vm.AgGrid( - title="Modified Dash AG Grid", - figure=dash_ag_grid( - data_frame=df, - columnDefs=columnDefs, - defaultColDef={"resizable": False, "filter": False, "editable": True}, - ), - ) - ], - ) - - dashboard = vm.Dashboard(pages=[page]) - - Vizro().build(dashboard).run() - ``` - === "app.yaml" - ```yaml - # Still requires a .py to register data connector in Data Manager and parse yaml configuration - # See from_yaml example - pages: - - components: - - figure: - _target_: dash_ag_grid - data_frame: gapminder - columnDefs: - - field: country - - field: continent - - field: year - - field: lifeExp - valueFormatter: - function: "d3.format('.1f')(params.value)" - - field: gdpPercap - valueFormatter: - function: "d3.format('$,.1f')(params.value)" - cellStyle: - styleConditions: - - condition: params.value < 1045 - style: - backgroundColor: "#ff9222" - - condition: params.value >= 1045 && params.value <= 4095 - style: - backgroundColor: "#de9e75" - - condition: params.value > 4095 && params.value <= 12695 - style: - backgroundColor: "#aaa9ba" - - condition: params.value > 12695 - style: - backgroundColor: "#00b4ff" - - field: pop - type: rightAligned - valueFormatter: - function: "d3.format(',.0f')(params.value)" - defaultColDef: - resizable: false - filter: false - editable: true - title: Dash AG Grid - type: ag_grid - title: Example of a Dash AG Grid - ``` - === "Result" - [![Table2]][Table2] - - [Table2]: ../../assets/user_guides/table/styled_table.png - -If the available arguments are not sufficient, there is always the possibility to create a [custom AG Grid callable](custom_tables.md). diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 0f1ba61ef..a171db08d 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -1,11 +1,308 @@ # How to use tables -This guide shows you how to use the [Dash DataTable](https://dash.plotly.com/datatable) to visualize your tabular data in the dashboard. It is an -interactive table component designed for viewing, editing, and exploring large datasets. +This guide shows you how to visualize tables in Vizro. + +There are two ways to visualize tables in Vizro, using either [AG Grid](#ag-grid) or [Dash DataTable](#dash-datatable). +In general, [AG Grid](#ag-grid) is Vizro's recommended table implementation, but in some cases it may make sense to use the [Dash DataTable](#dash-datatable) instead. + +## How to choose between AG Grid and Dash DataTable + +Vizro offers two models - the [`AgGrid`][vizro.models.AgGrid] model and the [`Table`][vizro.models.Table] model - for the above two approaches respectively. +They both visualize tabular data in similar ways. + +The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component +(which is in turn based on the [AG Grid](https://www.ag-grid.com/) library), +while the [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable) component. + +Both approaches have similar base features, and are configurable in similar ways. However, the AG Grid offers more advanced features out-of-the-box, is more customizable +and also ships a powerful enterprise version. This is why it is Vizro's recommended table implementation. At the same time, the Dash DataTable can be used if developers are +already familiar with it, or if some custom functionality is easier to implement using the Dash DataTable. + + +## AG Grid + +[AG Grid](https://www.ag-grid.com/) is an interactive table/grid component designed for viewing, editing, and exploring large datasets. It +is Vizro's recommended table implementation. + +The Vizro [`AgGrid`][vizro.models.AgGrid] model is based on the [Dash AG Grid](https://dash.plotly.com/dash-ag-grid), which is in turn based the +original [Javascript implementation](https://www.ag-grid.com/). + +### Basic usage + +To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: + +- insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the +[`Page`][vizro.models.Page] model +- enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) + +The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. +This must be entered under the argument `data_frame`. All other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. +Note that some defaults are set for some of the arguments (e.g. for `columnDefs`) to help with styling and usability. + + +!!! example "Basic Dash AG Grid" + === "app.py" + ```py + import vizro.models as vm + import vizro.plotly.express as px + from vizro import Vizro + from vizro.tables import dash_ag_grid + + df = px.data.gapminder() + + page = vm.Page( + title="Example of a Dash AG Grid", + components=[ + vm.AgGrid(title="Dash AG Grid", figure=dash_ag_grid(data_frame=df)), + ], + controls=[vm.Filter(column="continent")], + ) + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + ``` + === "app.yaml" + ```yaml + # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # See from_yaml example + pages: + - components: + - figure: + _target_: dash_ag_grid + data_frame: gapminder + title: Dash AG Grid + type: ag_grid + controls: + - column: continent + type: filter + title: Example of a Dash AG Grid + ``` + === "Result" + [![Table]][Table] + + [Table]: ../../assets/user_guides/table/table.png + +### Formatting columns + +#### Numbers + +One of the most common tasks when working with tables is to format the columns such that displayed numbers are more readable. +In order to do this, one can use the native functionality of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) +or make use of the Vizro pre-defined [Custom Cell Data Types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. + +The available custom cell types for Vizro are `dollar`, `euro`, `percentage` and `numeric`. + +In order to use these, define your desired `` alongside the chosen `cellDataType` in +the `columnDefs` argument of your `dash_ag_grid` function: + +```py +columnDefs = [{"field": "", "cellDataType": "euro"}] +``` + +In the below example we select and format some columns of the gapminder dataset. + +??? example "AG Grid with formatted columns" + === "app.py" + ```py + import vizro.models as vm + import vizro.plotly.express as px + from vizro import Vizro + from vizro.tables import dash_ag_grid + + df = px.data.gapminder() + + columnDefs = [{"field": "country"}, {"field": "year"}, {"field": "lifeExp", "cellDataType": "numeric"}, + {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] + + page = vm.Page( + title="Example of AG Grid with formatted columns", + components=[ + vm.AgGrid( + title="AG Grid with formatted columns", + figure=dash_ag_grid( + data_frame=df, + columnDefs=columnDefs, + ), + ) + ], + ) + + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + ``` + === "app.yaml" + ```yaml + # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # See from_yaml example + pages: + - components: + - figure: + _target_: dash_ag_grid + data_frame: gapminder + columnDefs: + - field: country + - field: year + - field: lifeExp + cellDataType: numeric + - field: gdpPercap + cellDataType: dollar + - field: pop + cellDataType: numeric + title: AG Grid with formatted columns + type: ag_grid + title: Example of AG Grid with formatted columns + ``` + === "Result" + [![Table2]][Table2] + + [Table2]: ../../assets/user_guides/table/styled_table.png + +#### Dates + +In order for the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly, the date must either be of +string format `yyyy-mm-dd` (see [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) +or a pandas datetime object. Any pandas datetime column will be transformed into the `yyyy-mm-dd` format automatically. + +#### Objects/Strings + +No specific formatting is available for custom objects and strings, however you can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) +in order to format e.g. displayed strings automatically. + + +### Styling/Modifying the AG Grid + +As mentioned above, all [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Below you can find +an example of a styled AG Grid where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. +There are many more ways to alter the grid beyond this showcase. + +??? example "Styled and modified Dash AG Grid" + === "app.py" + ```py + import vizro.models as vm + import vizro.plotly.express as px + from vizro import Vizro + from vizro.tables import dash_ag_grid + + df = px.data.gapminder() + + cellStyle = { + "styleConditions": [ + { + "condition": "params.value < 1045", + "style": {"backgroundColor": "#ff9222"}, + }, + { + "condition": "params.value >= 1045 && params.value <= 4095", + "style": {"backgroundColor": "#de9e75"}, + }, + { + "condition": "params.value > 4095 && params.value <= 12695", + "style": {"backgroundColor": "#aaa9ba"}, + }, + { + "condition": "params.value > 12695", + "style": {"backgroundColor": "#00b4ff"}, + }, + ] + } + + columnDefs = [ + {"field": "country"}, + {"field": "continent"}, + {"field": "year"}, + { + "field": "lifeExp", + "valueFormatter": {"function": "d3.format('.1f')(params.value)"}, + }, + { + "field": "gdpPercap", + "valueFormatter": {"function": "d3.format('$,.1f')(params.value)"}, + "cellStyle": cellStyle, + }, + { + "field": "pop", + "valueFormatter": {"function": "d3.format(',.0f')(params.value)"}, + }, + ] + + page = vm.Page( + title="Example of Modified Dash AG Grid", + components=[ + vm.AgGrid( + title="Modified Dash AG Grid", + figure=dash_ag_grid( + data_frame=df, + columnDefs=columnDefs, + defaultColDef={"resizable": False, "filter": False, "editable": True}, + ), + ) + ], + ) + + dashboard = vm.Dashboard(pages=[page]) + + Vizro().build(dashboard).run() + ``` + === "app.yaml" + ```yaml + # Still requires a .py to register data connector in Data Manager and parse yaml configuration + # See from_yaml example + pages: + - components: + - figure: + _target_: dash_ag_grid + data_frame: gapminder + columnDefs: + - field: country + - field: continent + - field: year + - field: lifeExp + valueFormatter: + function: "d3.format('.1f')(params.value)" + - field: gdpPercap + valueFormatter: + function: "d3.format('$,.1f')(params.value)" + cellStyle: + styleConditions: + - condition: params.value < 1045 + style: + backgroundColor: "#ff9222" + - condition: params.value >= 1045 && params.value <= 4095 + style: + backgroundColor: "#de9e75" + - condition: params.value > 4095 && params.value <= 12695 + style: + backgroundColor: "#aaa9ba" + - condition: params.value > 12695 + style: + backgroundColor: "#00b4ff" + - field: pop + type: rightAligned + valueFormatter: + function: "d3.format(',.0f')(params.value)" + defaultColDef: + resizable: false + filter: false + editable: true + title: Dash AG Grid + type: ag_grid + title: Example of a Dash AG Grid + ``` + === "Result" + [![Table2]][Table2] + + [Table2]: ../../assets/user_guides/table/styled_table.png + +If the available arguments are not sufficient, there is always the option to create a [custom AG Grid callable](custom_tables.md). + +## Dash DataTable + +Similar to AG Grid, the [Dash DataTable](https://dash.plotly.com/datatable) is an interactive table/grid component designed for viewing, editing, and exploring large datasets. The Vizro [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable). -## Basic usage +### Basic usage To add a [`Table`][vizro.models.Table] to your page, do the following: @@ -56,7 +353,7 @@ setting some defaults for some of the arguments to help with styling. [Table]: ../../assets/user_guides/table/table.png -## Styling/Modifying the Dash DataTable +### Styling/Modifying the Dash DataTable As mentioned above, all [parameters of the Dash DataTable](https://dash.plotly.com/datatable/reference) can be entered as keyword arguments. Below you can find an example of a styled table where some conditional formatting is applied. There are many more ways to alter the table beyond this showcase. diff --git a/vizro-core/mkdocs.yml b/vizro-core/mkdocs.yml index 3dd53570a..9e9e0b07a 100644 --- a/vizro-core/mkdocs.yml +++ b/vizro-core/mkdocs.yml @@ -14,7 +14,6 @@ nav: - COMPONENTS: - Overview: pages/user-guides/components.md - Graphs: pages/user-guides/graph.md - - AGGrids: pages/user-guides/aggrid.md - Tables: pages/user-guides/table.md - Cards & Buttons: pages/user-guides/card-button.md - Containers: pages/user-guides/container.md From 50dfae738655412f6cbd7580d75ff99f26b166f9 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:40:14 +0000 Subject: [PATCH 064/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/docs/pages/user-guides/table.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index a171db08d..a4b6db4fd 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -11,7 +11,7 @@ Vizro offers two models - the [`AgGrid`][vizro.models.AgGrid] model and the [`Ta They both visualize tabular data in similar ways. The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component -(which is in turn based on the [AG Grid](https://www.ag-grid.com/) library), +(which is in turn based on the [AG Grid](https://www.ag-grid.com/) library), while the [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable) component. Both approaches have similar base features, and are configurable in similar ways. However, the AG Grid offers more advanced features out-of-the-box, is more customizable @@ -108,12 +108,12 @@ In the below example we select and format some columns of the gapminder dataset. import vizro.plotly.express as px from vizro import Vizro from vizro.tables import dash_ag_grid - + df = px.data.gapminder() - + columnDefs = [{"field": "country"}, {"field": "year"}, {"field": "lifeExp", "cellDataType": "numeric"}, {"field": "gdpPercap", "cellDataType": "dollar"}, {"field": "pop", "cellDataType": "numeric"}] - + page = vm.Page( title="Example of AG Grid with formatted columns", components=[ @@ -126,9 +126,9 @@ In the below example we select and format some columns of the gapminder dataset. ) ], ) - + dashboard = vm.Dashboard(pages=[page]) - + Vizro().build(dashboard).run() ``` === "app.yaml" From fa53ea2b40088daeebdf73ab4e5695d18f82fa2c Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 22 Feb 2024 17:41:03 +0100 Subject: [PATCH 065/128] Remaining PR comments --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- vizro-core/docs/pages/user-guides/table.md | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index f9b157f5e..78fa3b806 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -8,7 +8,7 @@ One reason could be that you want to create a table/grid that requires computati For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: - define a function that returns a `dash_table.DataTable` or `dash_ag_grid.AgGrid` object -- decorate it with the `@capture("table")` or `@capture("grid")` decorator respectively +- decorate it with the `@capture("table")` or `@capture("ag_grid")` decorator respectively - the function must accept a `data_frame` argument (of type `pandas.DataFrame`) - the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index a4b6db4fd..960da77b8 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -86,9 +86,9 @@ Note that some defaults are set for some of the arguments (e.g. for `columnDefs` #### Numbers -One of the most common tasks when working with tables is to format the columns such that displayed numbers are more readable. -In order to do this, one can use the native functionality of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -or make use of the Vizro pre-defined [Custom Cell Data Types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. +One of the most common tasks when working with tables is to format the columns so that displayed numbers are more readable. +In order to do this, you can use the native functionality of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) +or the Vizro pre-defined [Custom Cell Data Types](https://dash.plotly.com/dash-ag-grid/cell-data-types#providing-custom-cell-data-types) as shown below. The available custom cell types for Vizro are `dollar`, `euro`, `percentage` and `numeric`. From 1ce60f6f789ade0bb627d7573bdb21d86410530a Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 10:41:38 +0100 Subject: [PATCH 066/128] PR comment on Errors --- vizro-core/src/vizro/actions/_actions_utils.py | 13 ++++++------- .../_callback_mapping/_callback_mapping_utils.py | 11 +++++++++-- vizro-core/src/vizro/models/_components/aggrid.py | 2 +- vizro-core/src/vizro/models/_components/graph.py | 2 +- vizro-core/src/vizro/models/_components/table.py | 2 +- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index e330ddb41..8b6105321 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -87,13 +87,12 @@ def _apply_filter_interaction( data_frame: pd.DataFrame, ctds_filter_interaction: List[Dict[str, CallbackTriggerDict]], target: str ) -> pd.DataFrame: for ctd_filter_interaction in ctds_filter_interaction: - if "modelID" in ctd_filter_interaction: - triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] - data_frame = triggered_model._filter_interaction( - data_frame=data_frame, - target=target, - ctd_filter_interaction=ctd_filter_interaction, - ) + triggered_model = model_manager[ctd_filter_interaction["modelID"]["id"]] + data_frame = triggered_model._filter_interaction( + data_frame=data_frame, + target=target, + ctd_filter_interaction=ctd_filter_interaction, + ) return data_frame diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index d28d45846..408f7280a 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -47,8 +47,15 @@ def _get_inputs_of_figure_interactions( inputs = [] for action in figure_interactions_on_page: triggered_model = model_manager._get_action_trigger(action_id=ModelID(str(action.id))) - if hasattr(triggered_model, "_figure_interaction_input"): - inputs.append(triggered_model._figure_interaction_input) + required_attributes = ["_filter_interaction_input", "_filter_interaction"] + for attribute in required_attributes: + if not hasattr(triggered_model, attribute): + raise ValueError(f"Model {triggered_model.id} does not have required attribute `{attribute}`.") + if "modelID" not in triggered_model._filter_interaction_input: + raise ValueError( + f"Model {triggered_model.id} does not have required State `modelID` in `_filter_interaction_input`." + ) + inputs.append(triggered_model._filter_interaction_input) return inputs diff --git a/vizro-core/src/vizro/models/_components/aggrid.py b/vizro-core/src/vizro/models/_components/aggrid.py index d40f10757..f811fe6af 100644 --- a/vizro-core/src/vizro/models/_components/aggrid.py +++ b/vizro-core/src/vizro/models/_components/aggrid.py @@ -62,7 +62,7 @@ def __getitem__(self, arg_name: str): # Interaction methods @property - def _figure_interaction_input(self): + def _filter_interaction_input(self): """Required properties when using pre-defined `filter_interaction`.""" return { "cellClicked": State(component_id=self._callable_object_id, component_property="cellClicked"), diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index aceaedf70..63ac4e2b8 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -77,7 +77,7 @@ def __getitem__(self, arg_name: str): # Interaction methods @property - def _figure_interaction_input(self): + def _filter_interaction_input(self): """Required properties when using pre-defined `filter_interaction`.""" return { "clickData": State(component_id=self.id, component_property="clickData"), diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 3b233fb2b..4c23d6f8a 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -62,7 +62,7 @@ def __getitem__(self, arg_name: str): # Interaction methods @property - def _figure_interaction_input(self): + def _filter_interaction_input(self): """Required properties when using pre-defined `filter_interaction`.""" return { "active_cell": State(component_id=self._callable_object_id, component_property="active_cell"), From 574e0f1d840e815b21b6164d6bf05d75732c0591 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 12:09:28 +0100 Subject: [PATCH 067/128] Fix unit tests after model refactoring --- .../test_get_action_callback_mapping.py | 6 ++++-- .../unit/vizro/actions/test_export_data_action.py | 10 ++++++++-- .../vizro/actions/test_filter_interaction_action.py | 8 +++++++- .../tests/unit/vizro/models/test_models_utils.py | 2 +- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py b/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py index f19c5d2a6..68e2fc542 100644 --- a/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py +++ b/vizro-core/tests/unit/vizro/actions/_callback_mapping/test_get_action_callback_mapping.py @@ -135,10 +135,11 @@ def action_callback_inputs_expected(): dash.State("parameter_table_row_selectable", "value"), ], "filter_interaction": [ - {"clickData": dash.State("scatter_chart", "clickData")}, + {"clickData": dash.State("scatter_chart", "clickData"), "modelID": dash.State("scatter_chart", "id")}, { "active_cell": dash.State("underlying_table_id", "active_cell"), "derived_viewport_data": dash.State("underlying_table_id", "derived_viewport_data"), + "modelID": dash.State("vizro_table", "id"), }, ], "theme_selector": dash.State("theme_selector", "checked"), @@ -162,10 +163,11 @@ def export_data_inputs_expected(): ], "parameters": [], "filter_interaction": [ - {"clickData": dash.State("scatter_chart", "clickData")}, + {"clickData": dash.State("scatter_chart", "clickData"), "modelID": dash.State("scatter_chart", "id")}, { "active_cell": dash.State("underlying_table_id", "active_cell"), "derived_viewport_data": dash.State("underlying_table_id", "derived_viewport_data"), + "modelID": dash.State("vizro_table", "id"), }, ], "theme_selector": [], diff --git a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py index 3101a4a55..773b8d4d9 100644 --- a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py @@ -51,8 +51,11 @@ def ctx_export_data(request): value={"points": [{"customdata": [continent_filter_interaction]}]}, str_id="box_chart", triggered=False, - ) - } + ), + "modelID": CallbackTriggerDict( + id="box_chart", property="id", value="box_chart", str_id="box_chart", triggered=False + ), + }, ) if country_table_filter_interaction: args_grouping_filter_interaction.append( @@ -74,6 +77,9 @@ def ctx_export_data(request): str_id="underlying_table_id", triggered=False, ), + "modelID": CallbackTriggerDict( + id="vizro_table", property="id", value="vizro_table", str_id="vizro_table", triggered=False + ), } ) mock_ctx = { diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index 595ac4d01..78b649596 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -23,7 +23,10 @@ def ctx_filter_interaction(request): value={"points": [{"customdata": [continent_filter_interaction]}]}, str_id="box_chart", triggered=False, - ) + ), + "modelID": CallbackTriggerDict( + id="box_chart", property="id", value="box_chart", str_id="box_chart", triggered=False + ), } ) if country_table_filter_interaction: @@ -46,6 +49,9 @@ def ctx_filter_interaction(request): str_id="underlying_table_id", triggered=False, ), + "modelID": CallbackTriggerDict( + id="vizro_table", property="id", value="vizro_table", str_id="vizro_table", triggered=False + ), } ) diff --git a/vizro-core/tests/unit/vizro/models/test_models_utils.py b/vizro-core/tests/unit/vizro/models/test_models_utils.py index f4e1310ea..395d99b9d 100644 --- a/vizro-core/tests/unit/vizro/models/test_models_utils.py +++ b/vizro-core/tests/unit/vizro/models/test_models_utils.py @@ -18,6 +18,6 @@ def test_set_components_validator(self, model_with_layout): def test_check_for_valid_component_types(self, model_with_layout): with pytest.raises( ValidationError, - match=re.escape("(allowed values: 'button', 'card', 'container', 'graph', 'table', 'tabs')"), + match=re.escape("(allowed values: 'ag_grid', 'button', 'card', 'container', 'graph', 'table', 'tabs')"), ): model_with_layout(title="Page Title", components=[vm.Checklist()]) From c019521380e81003bca08af78ed03f5e453b1445 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 12:10:28 +0100 Subject: [PATCH 068/128] Schema --- vizro-core/schemas/0.1.12.dev0.json | 44 ++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/vizro-core/schemas/0.1.12.dev0.json b/vizro-core/schemas/0.1.12.dev0.json index 68ea6e0ea..7057167e6 100644 --- a/vizro-core/schemas/0.1.12.dev0.json +++ b/vizro-core/schemas/0.1.12.dev0.json @@ -72,6 +72,40 @@ }, "additionalProperties": false }, + "AgGrid": { + "title": "AgGrid", + "description": "Wrapper for `dash-ag-grid.AgGrid` to visualize grids in dashboard.\n\nArgs:\n type (Literal[\"ag_grid\"]): Defaults to `\"ag_grid\"`.\n figure (CapturedCallable): AgGrid like object to be displayed. For more information see:\n [`dash-ag-grid.AgGrid`](https://dash.plotly.com/dash-ag-grid).\n title (str): Title of the table. Defaults to `\"\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", + "type": "object", + "properties": { + "id": { + "title": "Id", + "description": "ID to identify model. Must be unique throughout the whole dashboard.When no ID is chosen, ID will be automatically generated.", + "default": "", + "type": "string" + }, + "type": { + "title": "Type", + "default": "ag_grid", + "enum": ["ag_grid"], + "type": "string" + }, + "title": { + "title": "Title", + "description": "Title of the AgGrid", + "default": "", + "type": "string" + }, + "actions": { + "title": "Actions", + "default": [], + "type": "array", + "items": { + "$ref": "#/definitions/Action" + } + } + }, + "additionalProperties": false + }, "Button": { "title": "Button", "description": "Component provided to `Page` to trigger any defined `action` in `Page`.\n\nArgs:\n type (Literal[\"button\"]): Defaults to `\"button\"`.\n text (str): Text to be displayed on button. Defaults to `\"Click me!\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", @@ -168,7 +202,7 @@ }, "Table": { "title": "Table", - "description": "Wrapper for table components to visualize in dashboard.\n\nArgs:\n type (Literal[\"table\"]): Defaults to `\"table\"`.\n figure (CapturedCallable): Table like object to be displayed. Current choices include:\n [`dash_table.DataTable`](https://dash.plotly.com/datatable).\n title (str): Title of the table. Defaults to `\"\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", + "description": "Wrapper for `dash_table.DataTable` to visualize tables in dashboard.\n\nArgs:\n type (Literal[\"table\"]): Defaults to `\"table\"`.\n figure (CapturedCallable): Table like object to be displayed. For more information see:\n [`dash_table.DataTable`](https://dash.plotly.com/datatable).\n title (str): Title of the table. Defaults to `\"\"`.\n actions (List[Action]): See [`Action`][vizro.models.Action]. Defaults to `[]`.", "type": "object", "properties": { "id": { @@ -306,6 +340,7 @@ "discriminator": { "propertyName": "type", "mapping": { + "ag_grid": "#/definitions/AgGrid", "button": "#/definitions/Button", "card": "#/definitions/Card", "container": "#/definitions/Container", @@ -315,6 +350,9 @@ } }, "oneOf": [ + { + "$ref": "#/definitions/AgGrid" + }, { "$ref": "#/definitions/Button" }, @@ -969,6 +1007,7 @@ "discriminator": { "propertyName": "type", "mapping": { + "ag_grid": "#/definitions/AgGrid", "button": "#/definitions/Button", "card": "#/definitions/Card", "container": "#/definitions/Container", @@ -978,6 +1017,9 @@ } }, "oneOf": [ + { + "$ref": "#/definitions/AgGrid" + }, { "$ref": "#/definitions/Button" }, From 1d40cf3295be75cbe6f419bacd6575a0fa2621fe Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 12:19:07 +0100 Subject: [PATCH 069/128] Update requirements --- vizro-core/snyk/requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/vizro-core/snyk/requirements.txt b/vizro-core/snyk/requirements.txt index 240498883..9ff9de875 100644 --- a/vizro-core/snyk/requirements.txt +++ b/vizro-core/snyk/requirements.txt @@ -1,5 +1,6 @@ dash>=2.14.1 dash_bootstrap_components +dash-ag-grid>=31.0.0 pandas pydantic>=1.10.13 dash_mantine_components From 56e9f252f264db087ef5d404a8915cedd5e7cc0c Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 13:36:02 +0100 Subject: [PATCH 070/128] Fix tests and take over most tests from Table --- vizro-core/tests/unit/vizro/conftest.py | 12 +- .../unit/vizro/models/_components/conftest.py | 10 ++ .../vizro/models/_components/test_aggrid.py | 147 ++++++++++++++++++ .../vizro/models/_components/test_table.py | 6 - 4 files changed, 168 insertions(+), 7 deletions(-) create mode 100644 vizro-core/tests/unit/vizro/models/_components/conftest.py create mode 100644 vizro-core/tests/unit/vizro/models/_components/test_aggrid.py diff --git a/vizro-core/tests/unit/vizro/conftest.py b/vizro-core/tests/unit/vizro/conftest.py index 6ba8dae5b..72c215dc0 100644 --- a/vizro-core/tests/unit/vizro/conftest.py +++ b/vizro-core/tests/unit/vizro/conftest.py @@ -5,7 +5,7 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.tables import dash_data_table +from vizro.tables import dash_ag_grid, dash_data_table @pytest.fixture @@ -26,6 +26,16 @@ def standard_px_chart(gapminder): ) +@pytest.fixture +def standard_ag_grid(gapminder): + return dash_ag_grid(data_frame=gapminder) + + +@pytest.fixture +def ag_grid_with_id(gapminder): + return dash_ag_grid(id="underlying_ag_grid_id", data_frame=gapminder) + + @pytest.fixture def standard_dash_table(gapminder): return dash_data_table(data_frame=gapminder) diff --git a/vizro-core/tests/unit/vizro/models/_components/conftest.py b/vizro-core/tests/unit/vizro/models/_components/conftest.py new file mode 100644 index 000000000..97939f484 --- /dev/null +++ b/vizro-core/tests/unit/vizro/models/_components/conftest.py @@ -0,0 +1,10 @@ +"""Fixtures to be shared across several tests.""" + +import pytest +import vizro.models as vm +from vizro.actions import filter_interaction + + +@pytest.fixture +def filter_interaction_action(): + return vm.Action(function=filter_interaction()) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py b/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py new file mode 100644 index 000000000..23e1d095f --- /dev/null +++ b/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py @@ -0,0 +1,147 @@ +"""Unit tests for vizro.models.AgGrid.""" + +import dash_ag_grid as dag +import pytest +from asserts import assert_component_equal +from dash import dcc, html + +try: + from pydantic.v1 import ValidationError +except ImportError: # pragma: no cov + from pydantic import ValidationError + +import vizro.models as vm +import vizro.plotly.express as px +from vizro.managers import data_manager +from vizro.models._action._action import Action +from vizro.tables import dash_ag_grid + + +@pytest.fixture +def dash_ag_grid_with_arguments(): + return dash_ag_grid(data_frame=px.data.gapminder(), defaultColDef={"resizable": False, "sortable": False}) + + +@pytest.fixture +def dash_ag_grid_with_str_dataframe(): + return dash_ag_grid(data_frame="gapminder") + + +class TestDunderMethodsAgGrid: + def test_create_graph_mandatory_only(self, standard_ag_grid): + ag_grid = vm.AgGrid(figure=standard_ag_grid) + + assert hasattr(ag_grid, "id") + assert ag_grid.type == "ag_grid" + assert ag_grid.figure == standard_ag_grid + assert ag_grid.actions == [] + + @pytest.mark.parametrize("id", ["id_1", "id_2"]) + def test_create_ag_grid_mandatory_and_optional(self, standard_ag_grid, id): + ag_grid = vm.AgGrid(figure=standard_ag_grid, id=id, actions=[]) + + assert ag_grid.id == id + assert ag_grid.type == "ag_grid" + assert ag_grid.figure == standard_ag_grid + + def test_mandatory_figure_missing(self): + with pytest.raises(ValidationError, match="field required"): + vm.AgGrid() + + def test_failed_ag_grid_with_no_captured_callable(self, standard_go_chart): + with pytest.raises(ValidationError, match="must provide a valid CapturedCallable object"): + vm.AgGrid(figure=standard_go_chart) + + @pytest.mark.xfail(reason="This test is failing as we are not yet detecting different types of captured callables") + def test_failed_ag_grid_with_wrong_captured_callable(self, standard_px_chart): + with pytest.raises(ValidationError, match="must provide a valid ag_grid function vm.AgGrid"): + vm.AgGrid(figure=standard_px_chart) + + def test_getitem_known_args(self, dash_ag_grid_with_arguments): + ag_grid = vm.AgGrid(figure=dash_ag_grid_with_arguments) + assert ag_grid["defaultColDef"] == {"resizable": False, "sortable": False} + assert ag_grid["type"] == "ag_grid" + + def test_getitem_unknown_args(self, standard_ag_grid): + ag_grid = vm.AgGrid(figure=standard_ag_grid) + with pytest.raises(KeyError): + ag_grid["unknown_args"] + + def test_set_action_via_validator(self, standard_ag_grid, identity_action_function): + ag_grid = vm.AgGrid(figure=standard_ag_grid, actions=[Action(function=identity_action_function())]) + actions_chain = ag_grid.actions[0] + assert actions_chain.trigger.component_property == "cellClicked" + + +class TestProcessAgGridDataFrame: + def test_process_figure_data_frame_str_df(self, dash_ag_grid_with_str_dataframe, gapminder): + data_manager["gapminder"] = gapminder + ag_grid_with_str_df = vm.AgGrid(id="ag_grid", figure=dash_ag_grid_with_str_dataframe) + assert data_manager._get_component_data("ag_grid").equals(gapminder) + assert ag_grid_with_str_df["data_frame"] == "gapminder" + + def test_process_figure_data_frame_df(self, standard_ag_grid, gapminder): + ag_grid_with_str_df = vm.AgGrid(id="ag_grid", figure=standard_ag_grid) + assert data_manager._get_component_data("ag_grid").equals(gapminder) + with pytest.raises(KeyError, match="'data_frame'"): + ag_grid_with_str_df.figure["data_frame"] + + +class TestPreBuildAgGrid: + def test_pre_build_no_actions_no_underlying_ag_grid_id(self, standard_ag_grid): + ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) + ag_grid.pre_build() + + assert hasattr(ag_grid, "_callable_object_id") is False + + def test_pre_build_actions_no_underlying_ag_grid_id_exception(self, standard_ag_grid, filter_interaction_action): + ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid, actions=[filter_interaction_action]) + with pytest.raises(ValueError, match="Underlying `AgGrid` callable has no attribute 'id'"): + ag_grid.pre_build() + + def test_pre_build_actions_underlying_ag_grid_id(self, ag_grid_with_id, filter_interaction_action): + ag_grid = vm.AgGrid(id="text_ag_grid", figure=ag_grid_with_id, actions=[filter_interaction_action]) + ag_grid.pre_build() + + assert ag_grid._callable_object_id == "underlying_ag_grid_id" + + +class TestBuildAgGrid: + def test_ag_grid_build_mandatory_only(self, standard_ag_grid): + ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) + ag_grid.pre_build() + ag_grid = ag_grid.build() + expected_ag_grid = dcc.Loading( + html.Div( + [ + None, + html.Div(dag.AgGrid(), id="text_ag_grid"), + ], + className="table-container", + id="text_ag_grid_outer", + ), + color="grey", + parent_className="loading-container", + ) + + assert_component_equal(ag_grid, expected_ag_grid) + + def test_ag_grid_build_with_underlying_id(self, ag_grid_with_id, filter_interaction_action): + ag_grid = vm.AgGrid(id="text_ag_grid", figure=ag_grid_with_id, actions=[filter_interaction_action]) + ag_grid.pre_build() + ag_grid = ag_grid.build() + + expected_ag_grid = dcc.Loading( + html.Div( + [ + None, + html.Div(dag.AgGrid(id="underlying_ag_grid_id"), id="text_ag_grid"), + ], + className="table-container", + id="text_ag_grid_outer", + ), + color="grey", + parent_className="loading-container", + ) + + assert_component_equal(ag_grid, expected_ag_grid) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index 2a7d431db..cae963fc4 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -11,7 +11,6 @@ import vizro.models as vm import vizro.plotly.express as px -from vizro.actions import filter_interaction from vizro.managers import data_manager from vizro.models._action._action import Action from vizro.tables import dash_data_table @@ -27,11 +26,6 @@ def dash_table_with_str_dataframe(): return dash_data_table(data_frame="gapminder") -@pytest.fixture -def filter_interaction_action(): - return vm.Action(function=filter_interaction()) - - class TestDunderMethodsTable: def test_create_graph_mandatory_only(self, standard_dash_table): table = vm.Table(figure=standard_dash_table) From 3d9abc80b0654f092f07f3cf10c6fa4047a71a83 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 13:55:03 +0100 Subject: [PATCH 071/128] Add attribute tests for Graph, Table and AgGrid --- .../tests/unit/vizro/models/_components/test_aggrid.py | 9 +++++++++ .../tests/unit/vizro/models/_components/test_graph.py | 8 ++++++++ .../tests/unit/vizro/models/_components/test_table.py | 9 +++++++++ 3 files changed, 26 insertions(+) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py b/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py index 23e1d095f..215235e70 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py @@ -73,6 +73,15 @@ def test_set_action_via_validator(self, standard_ag_grid, identity_action_functi assert actions_chain.trigger.component_property == "cellClicked" +class TestAttributesAgGrid: + def test_ag_grid_filter_interaction_attributes(self, ag_grid_with_id): + ag_grid = vm.AgGrid(figure=ag_grid_with_id, title="Gapminder", actions=[]) + assert hasattr(ag_grid, "_filter_interaction") + ag_grid.pre_build() + assert hasattr(ag_grid, "_filter_interaction_input") + assert "modelID" in ag_grid._filter_interaction_input + + class TestProcessAgGridDataFrame: def test_process_figure_data_frame_str_df(self, dash_ag_grid_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder diff --git a/vizro-core/tests/unit/vizro/models/_components/test_graph.py b/vizro-core/tests/unit/vizro/models/_components/test_graph.py index 438bc79f6..1ecf66e1f 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_graph.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_graph.py @@ -107,6 +107,14 @@ def test_set_action_via_validator(self, standard_px_chart, identity_action_funct assert actions_chain.trigger.component_property == "clickData" +class TestAttributesGraph: + def test_graph_filter_interaction_attributes(self, standard_px_chart): + graph = vm.Graph(figure=standard_px_chart) + assert hasattr(graph, "_filter_interaction") + assert hasattr(graph, "_filter_interaction_input") + assert "modelID" in graph._filter_interaction_input + + class TestProcessFigureDataFrame: def test_process_figure_data_frame_str_df(self, standard_px_chart_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index cae963fc4..6fa03ea88 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -72,6 +72,15 @@ def test_set_action_via_validator(self, standard_dash_table, identity_action_fun assert actions_chain.trigger.component_property == "active_cell" +class TestAttributesTable: + def test_table_filter_interaction_attributes(self, dash_data_table_with_id): + table = vm.Table(figure=dash_data_table_with_id, title="Gapminder", actions=[]) + assert hasattr(table, "_filter_interaction") + table.pre_build() + assert hasattr(table, "_filter_interaction_input") + assert "modelID" in table._filter_interaction_input + + class TestProcessTableDataFrame: def test_process_figure_data_frame_str_df(self, dash_table_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder From 9c1751ffd6cc708d38df05125c535a6773a774ad Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 14:38:22 +0100 Subject: [PATCH 072/128] Introduce tests for dash_ag_grid function --- .../unit/vizro/tables/test_dash_ag_grid.py | 24 +++++++++++++++++++ .../unit/vizro/tables/test_dash_table.py | 21 ---------------- .../tests/unit/vizro/tables/test_utils.py | 21 ++++++++++++++++ 3 files changed, 45 insertions(+), 21 deletions(-) create mode 100644 vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py create mode 100644 vizro-core/tests/unit/vizro/tables/test_utils.py diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py new file mode 100644 index 000000000..f00b9725b --- /dev/null +++ b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py @@ -0,0 +1,24 @@ +import pandas as pd +from vizro.tables import dash_ag_grid + +data = pd.DataFrame( + { + "cat": ["a", "b", "c"], + "int": [4, 5, 6], + "float": [7.3, 8.2, 9.1], + "date": pd.to_datetime(["2021/01/01", "2021/01/02", "2021/01/03"]), + } +) + + +class TestDashAgGrid: + def test_dash_ag_grid(self): + grid = dash_ag_grid(data_frame=data)() + assert grid.columnDefs == [{"field": "cat"}, {"field": "int"}, {"field": "float"}, {"field": "date"}] + assert grid.rowData == [ + {"cat": "a", "int": 4, "float": 7.3, "date": "2021-01-01"}, + {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, + {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, + ] + # we could test other properties such as defaultColDef, + # but this would just test our chosen defaults, and no functionality really diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_table.py b/vizro-core/tests/unit/vizro/tables/test_dash_table.py index 4b0be62e1..e69de29bb 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_table.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_table.py @@ -1,21 +0,0 @@ -import pytest -from vizro.tables.dash_table import _set_defaults_nested - - -@pytest.fixture -def default_dictionary(): - return {"a": {"b": {"c": 1, "d": 2}}, "e": 3} - - -@pytest.mark.parametrize( - "input, expected", - [ - ({}, {"a": {"b": {"c": 1, "d": 2}}, "e": 3}), # nothing supplied - ({"e": 10}, {"a": {"b": {"c": 1, "d": 2}}, "e": 10}), # flat main key - ({"a": {"b": {"c": 11, "d": 12}}}, {"a": {"b": {"c": 11, "d": 12}}, "e": 3}), # updated multiple nested keys - ({"a": {"b": {"c": 1, "d": {"f": 42}}}}, {"a": {"b": {"c": 1, "d": {"f": 42}}}, "e": 3}), # add new dict - ({"a": {"b": {"c": 5}}}, {"a": {"b": {"c": 5, "d": 2}}, "e": 3}), # arbitrary nesting - ], -) -def test_set_defaults_nested(default_dictionary, input, expected): - assert _set_defaults_nested(input, default_dictionary) == expected diff --git a/vizro-core/tests/unit/vizro/tables/test_utils.py b/vizro-core/tests/unit/vizro/tables/test_utils.py new file mode 100644 index 000000000..cf06e5ef2 --- /dev/null +++ b/vizro-core/tests/unit/vizro/tables/test_utils.py @@ -0,0 +1,21 @@ +import pytest +from vizro.tables._utils import _set_defaults_nested + + +@pytest.fixture +def default_dictionary(): + return {"a": {"b": {"c": 1, "d": 2}}, "e": 3} + + +@pytest.mark.parametrize( + "input, expected", + [ + ({}, {"a": {"b": {"c": 1, "d": 2}}, "e": 3}), # nothing supplied + ({"e": 10}, {"a": {"b": {"c": 1, "d": 2}}, "e": 10}), # flat main key + ({"a": {"b": {"c": 11, "d": 12}}}, {"a": {"b": {"c": 11, "d": 12}}, "e": 3}), # updated multiple nested keys + ({"a": {"b": {"c": 1, "d": {"f": 42}}}}, {"a": {"b": {"c": 1, "d": {"f": 42}}}, "e": 3}), # add new dict + ({"a": {"b": {"c": 5}}}, {"a": {"b": {"c": 5, "d": 2}}, "e": 3}), # arbitrary nesting + ], +) +def test_set_defaults_nested(default_dictionary, input, expected): + assert _set_defaults_nested(input, default_dictionary) == expected From f427e27cabf9e3a4bce6710187e94f8967ce1ce1 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 15:36:36 +0100 Subject: [PATCH 073/128] Add test for filter interaction of ag_grid --- .../tests/unit/vizro/actions/conftest.py | 5 +- .../vizro/actions/test_export_data_action.py | 2 +- .../actions/test_filter_interaction_action.py | 97 ++++++++++++++----- 3 files changed, 77 insertions(+), 27 deletions(-) diff --git a/vizro-core/tests/unit/vizro/actions/conftest.py b/vizro-core/tests/unit/vizro/actions/conftest.py index 8c8549a39..f34fec6d8 100644 --- a/vizro-core/tests/unit/vizro/actions/conftest.py +++ b/vizro-core/tests/unit/vizro/actions/conftest.py @@ -59,7 +59,9 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart): @pytest.fixture -def managers_one_page_two_graphs_one_table_one_button(box_chart, scatter_chart, dash_data_table_with_id): +def managers_one_page_two_graphs_one_table_one_aggrid_one_button( + box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id +): """Instantiates a simple model_manager and data_manager with a page, two graph models and the button component.""" vm.Page( id="test_page", @@ -68,6 +70,7 @@ def managers_one_page_two_graphs_one_table_one_button(box_chart, scatter_chart, vm.Graph(id="box_chart", figure=box_chart), vm.Graph(id="scatter_chart", figure=scatter_chart), vm.Table(id="vizro_table", figure=dash_data_table_with_id), + vm.AgGrid(id="ag_grid", figure=ag_grid_with_id), vm.Button(id="button"), ], ) diff --git a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py index 773b8d4d9..f3a1f5585 100644 --- a/vizro-core/tests/unit/vizro/actions/test_export_data_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_export_data_action.py @@ -291,7 +291,7 @@ def test_multiple_targets_with_filter_and_filter_interaction( assert result == expected - @pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_button") + @pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_aggrid_one_button") @pytest.mark.parametrize( "ctx_export_data, target_scatter_filter_and_filter_interaction, target_box_filtered_pop", [ diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index 78b649596..a10b734ea 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -11,7 +11,7 @@ @pytest.fixture def ctx_filter_interaction(request): """Mock dash.ctx that represents a click on a continent data-point and table selected cell.""" - continent_filter_interaction, country_table_filter_interaction = request.param + continent_filter_interaction, country_table_filter_interaction, country_aggrid_filter_interaction = request.param args_grouping_filter_interaction = [] if continent_filter_interaction: @@ -54,7 +54,27 @@ def ctx_filter_interaction(request): ), } ) - + if country_aggrid_filter_interaction: + args_grouping_filter_interaction.append( + { + "cellClicked": CallbackTriggerDict( + id="underlying_ag_grid_id", + property="cellClicked", + value={ + "value": country_aggrid_filter_interaction, + "colId": "country", + "rowIndex": 0, + "rowId": "0", + "timestamp": 1708697920849, + }, + str_id="underlying_ag_grid_id", + triggered=False, + ), + "modelID": CallbackTriggerDict( + id="ag_grid", property="id", value="ag_grid", str_id="ag_grid", triggered=False + ), + } + ) mock_ctx = { "args_grouping": { "external": { @@ -97,9 +117,9 @@ def target_box_filtered_continent(request, gapminder_2007, box_params): return px.box(data, **box_params).update_layout(margin_t=24) -@pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_button") +@pytest.mark.usefixtures("managers_one_page_two_graphs_one_table_one_aggrid_one_button") class TestFilterInteraction: - @pytest.mark.parametrize("ctx_filter_interaction", [("Africa", None), ("Europe", None)], indirect=True) + @pytest.mark.parametrize("ctx_filter_interaction", [("Africa", None, None), ("Europe", None, None)], indirect=True) def test_filter_interaction_without_targets_temporary_behavior( # temporary fix, see below test self, ctx_filter_interaction ): @@ -116,9 +136,9 @@ def test_filter_interaction_without_targets_temporary_behavior( # temporary fix @pytest.mark.parametrize( "ctx_filter_interaction,target_scatter_filtered_continent,target_box_filtered_continent", [ - (("Africa", None), ("Africa", None), ("Africa", None)), - (("Europe", None), ("Europe", None), ("Europe", None)), - (("Americas", None), ("Americas", None), ("Americas", None)), + (("Africa", None, None), ("Africa", None), ("Africa", None)), + (("Europe", None, None), ("Europe", None), ("Europe", None)), + (("Americas", None, None), ("Americas", None), ("Americas", None)), ], indirect=True, ) @@ -137,9 +157,9 @@ def test_filter_interaction_without_targets_desired_behavior( @pytest.mark.parametrize( "ctx_filter_interaction,target_scatter_filtered_continent", [ - (("Africa", None), ("Africa", None)), - (("Europe", None), ("Europe", None)), - (("Americas", None), ("Americas", None)), + (("Africa", None, None), ("Africa", None)), + (("Europe", None, None), ("Europe", None)), + (("Americas", None, None), ("Americas", None)), ], indirect=True, ) @@ -158,9 +178,9 @@ def test_filter_interaction_with_one_target(self, ctx_filter_interaction, target @pytest.mark.parametrize( "ctx_filter_interaction,target_scatter_filtered_continent,target_box_filtered_continent", [ - (("Africa", None), ("Africa", None), ("Africa", None)), - (("Europe", None), ("Europe", None), ("Europe", None)), - (("Americas", None), ("Americas", None), ("Americas", None)), + (("Africa", None, None), ("Africa", None), ("Africa", None)), + (("Europe", None, None), ("Europe", None), ("Europe", None)), + (("Americas", None, None), ("Americas", None), ("Americas", None)), ], indirect=True, ) @@ -180,7 +200,7 @@ def test_filter_interaction_with_two_target( @pytest.mark.xfail # This (or similar code) should raise a Value/Validation error explaining next steps @pytest.mark.parametrize("target", ["scatter_chart", ["scatter_chart"]]) - @pytest.mark.parametrize("ctx_filter_interaction", [("Africa", None), ("Europe", None)], indirect=True) + @pytest.mark.parametrize("ctx_filter_interaction", [("Africa", None, None), ("Europe", None, None)], indirect=True) def test_filter_interaction_with_invalid_targets(self, target, ctx_filter_interaction): with pytest.raises(ValueError, match="Target invalid_target not found in model_manager."): # Add action to relevant component - here component[0] is the source_chart @@ -191,9 +211,9 @@ def test_filter_interaction_with_invalid_targets(self, target, ctx_filter_intera @pytest.mark.parametrize( "ctx_filter_interaction,target_scatter_filtered_continent", [ - ((None, "Algeria"), (None, "Algeria")), - ((None, "Albania"), (None, "Albania")), - ((None, "Argentina"), (None, "Argentina")), + ((None, "Algeria", None), (None, "Algeria")), + ((None, "Albania", None), (None, "Albania")), + ((None, "Argentina", None), (None, "Argentina")), ], indirect=True, ) @@ -214,9 +234,9 @@ def test_table_filter_interaction_with_one_target(self, ctx_filter_interaction, @pytest.mark.parametrize( "ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent", [ - ((None, "Algeria"), (None, "Algeria"), (None, "Algeria")), - ((None, "Albania"), (None, "Albania"), (None, "Albania")), - ((None, "Argentina"), (None, "Argentina"), (None, "Argentina")), + ((None, "Algeria", None), (None, "Algeria"), (None, "Algeria")), + ((None, "Albania", None), (None, "Albania"), (None, "Albania")), + ((None, "Argentina", None), (None, "Argentina"), (None, "Argentina")), ], indirect=True, ) @@ -224,11 +244,11 @@ def test_table_filter_interaction_with_two_targets( self, ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent ): model_manager["box_chart"].actions = [ - vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) + vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] model_manager["vizro_table"].actions = [ - vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) + vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] model_manager["vizro_table"].pre_build() @@ -241,9 +261,36 @@ def test_table_filter_interaction_with_two_targets( @pytest.mark.parametrize( "ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent", [ - (("Africa", "Algeria"), ("Africa", "Algeria"), ("Africa", "Algeria")), - (("Europe", "Albania"), ("Europe", "Albania"), ("Europe", "Albania")), - (("Americas", "Argentina"), ("Americas", "Argentina"), ("Americas", "Argentina")), + ((None, None, "Algeria"), (None, "Algeria"), (None, "Algeria")), + ((None, None, "Albania"), (None, "Albania"), (None, "Albania")), + ((None, None, "Argentina"), (None, "Argentina"), (None, "Argentina")), + ], + indirect=True, + ) + def test_aggrid_filter_interaction_with_two_targets( + self, ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent + ): + model_manager["box_chart"].actions = [ + vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + + model_manager["ag_grid"].actions = [ + vm.Action(id="test_action", function=filter_interaction(targets=["scatter_chart", "box_chart"])) + ] + model_manager["ag_grid"].pre_build() + + # Run action by picking the above added action function and executing it with () + result = model_manager["test_action"].function() + expected = {"scatter_chart": target_scatter_filtered_continent, "box_chart": target_box_filtered_continent} + + assert result == expected + + @pytest.mark.parametrize( + "ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent", + [ + (("Africa", "Algeria", None), ("Africa", "Algeria"), ("Africa", "Algeria")), + (("Europe", "Albania", None), ("Europe", "Albania"), ("Europe", "Albania")), + (("Americas", "Argentina", None), ("Americas", "Argentina"), ("Americas", "Argentina")), ], indirect=True, ) From a7e8e8cd13be32df23ad33846d60988484d322dc Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 16:29:35 +0100 Subject: [PATCH 074/128] Add tests for dash_data_table --- vizro-core/src/vizro/tables/dash_table.py | 2 +- .../actions/test_filter_interaction_action.py | 1 + .../unit/vizro/tables/test_dash_table.py | 29 +++++++++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/vizro-core/src/vizro/tables/dash_table.py b/vizro-core/src/vizro/tables/dash_table.py index 41cc43ded..bdcbb58ba 100644 --- a/vizro-core/src/vizro/tables/dash_table.py +++ b/vizro-core/src/vizro/tables/dash_table.py @@ -11,7 +11,7 @@ def dash_data_table(data_frame: pd.DataFrame, **kwargs): """Standard `dash_table.DataTable`.""" defaults = { - "columns": [{"name": i, "id": i} for i in data_frame.columns], + "columns": [{"name": col, "id": col} for col in data_frame.columns], "style_as_list_view": True, "style_data": {"border_bottom": "1px solid var(--border-subtle-alpha-01)", "height": "40px"}, "style_header": { diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index a10b734ea..a47f84911 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -270,6 +270,7 @@ def test_table_filter_interaction_with_two_targets( def test_aggrid_filter_interaction_with_two_targets( self, ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent ): + # to not overcrowd these tests with duplication, we use one general case here for the AG Grid model_manager["box_chart"].actions = [ vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_table.py b/vizro-core/tests/unit/vizro/tables/test_dash_table.py index e69de29bb..ffcd164c9 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_table.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_table.py @@ -0,0 +1,29 @@ +import pandas as pd +from vizro.tables import dash_data_table + +data = pd.DataFrame( + { + "cat": ["a", "b", "c"], + "int": [4, 5, 6], + "float": [7.3, 8.2, 9.1], + "date": pd.to_datetime(["2021/01/01", "2021/01/02", "2021/01/03"]), + } +) + + +class TestDashDataTable: + def test_dash_data_table(self): + table = dash_data_table(data_frame=data)() + assert table.columns == [ + {"id": "cat", "name": "cat"}, + {"id": "int", "name": "int"}, + {"id": "float", "name": "float"}, + {"id": "date", "name": "date"}, + ] + assert table.data == [ + {"cat": "a", "date": pd.Timestamp("2021-01-01 00:00:00"), "float": 7.3, "int": 4}, + {"cat": "b", "date": pd.Timestamp("2021-01-02 00:00:00"), "float": 8.2, "int": 5}, + {"cat": "c", "date": pd.Timestamp("2021-01-03 00:00:00"), "float": 9.1, "int": 6}, + ] + # we could test other properties such as style_header, + # but this would just test our chosen defaults, and no functionality really From 33dcb407bdf6b3c0b97ac062f1f1fe10115ea944 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 16:37:17 +0100 Subject: [PATCH 075/128] Rename aggrid.py file --- vizro-core/src/vizro/models/_components/__init__.py | 2 +- .../src/vizro/models/_components/{aggrid.py => ag_grid.py} | 0 .../tests/unit/vizro/actions/test_filter_interaction_action.py | 1 + 3 files changed, 2 insertions(+), 1 deletion(-) rename vizro-core/src/vizro/models/_components/{aggrid.py => ag_grid.py} (100%) diff --git a/vizro-core/src/vizro/models/_components/__init__.py b/vizro-core/src/vizro/models/_components/__init__.py index be0b8603b..07e95fabc 100644 --- a/vizro-core/src/vizro/models/_components/__init__.py +++ b/vizro-core/src/vizro/models/_components/__init__.py @@ -1,6 +1,6 @@ """Components that are placed according to the `Layout` of the `Page`.""" -from vizro.models._components.aggrid import AgGrid +from vizro.models._components.ag_grid import AgGrid from vizro.models._components.button import Button from vizro.models._components.card import Card from vizro.models._components.container import Container diff --git a/vizro-core/src/vizro/models/_components/aggrid.py b/vizro-core/src/vizro/models/_components/ag_grid.py similarity index 100% rename from vizro-core/src/vizro/models/_components/aggrid.py rename to vizro-core/src/vizro/models/_components/ag_grid.py diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index a47f84911..cd5638724 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -271,6 +271,7 @@ def test_aggrid_filter_interaction_with_two_targets( self, ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent ): # to not overcrowd these tests with duplication, we use one general case here for the AG Grid + # Functionality should be similar enough to the Dash Datatable that this should suffice model_manager["box_chart"].actions = [ vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] From c3ffc76d9cda863b85637fdbd79d99e434073ce5 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 16:54:53 +0100 Subject: [PATCH 076/128] Rename _dash_files files --- vizro-core/src/vizro/tables/__init__.py | 4 ++-- .../src/vizro/tables/{dash_aggrid.py => _dash_ag_grid.py} | 0 vizro-core/src/vizro/tables/{dash_table.py => _dash_table.py} | 0 .../models/_components/{test_aggrid.py => test_ag_grid.py} | 0 4 files changed, 2 insertions(+), 2 deletions(-) rename vizro-core/src/vizro/tables/{dash_aggrid.py => _dash_ag_grid.py} (100%) rename vizro-core/src/vizro/tables/{dash_table.py => _dash_table.py} (100%) rename vizro-core/tests/unit/vizro/models/_components/{test_aggrid.py => test_ag_grid.py} (100%) diff --git a/vizro-core/src/vizro/tables/__init__.py b/vizro-core/src/vizro/tables/__init__.py index 0c91e3346..3ec9ce4d2 100644 --- a/vizro-core/src/vizro/tables/__init__.py +++ b/vizro-core/src/vizro/tables/__init__.py @@ -1,5 +1,5 @@ -from vizro.tables.dash_aggrid import dash_ag_grid -from vizro.tables.dash_table import dash_data_table +from vizro.tables._dash_ag_grid import dash_ag_grid +from vizro.tables._dash_table import dash_data_table # Please keep alphabetically ordered __all__ = ["dash_ag_grid", "dash_data_table"] diff --git a/vizro-core/src/vizro/tables/dash_aggrid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py similarity index 100% rename from vizro-core/src/vizro/tables/dash_aggrid.py rename to vizro-core/src/vizro/tables/_dash_ag_grid.py diff --git a/vizro-core/src/vizro/tables/dash_table.py b/vizro-core/src/vizro/tables/_dash_table.py similarity index 100% rename from vizro-core/src/vizro/tables/dash_table.py rename to vizro-core/src/vizro/tables/_dash_table.py diff --git a/vizro-core/tests/unit/vizro/models/_components/test_aggrid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py similarity index 100% rename from vizro-core/tests/unit/vizro/models/_components/test_aggrid.py rename to vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py From 11c9cf56e021ace6295c651c6df604d0b0267558 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 16:57:01 +0100 Subject: [PATCH 077/128] Refactor constant names --- vizro-core/src/vizro/tables/_dash_ag_grid.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 45af842af..38ca9276d 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -6,7 +6,7 @@ from vizro.models.types import capture from vizro.tables._utils import _set_defaults_nested -FORMAT_CURRENCY_EU = """d3.formatLocale({ +_FORMAT_CURRENCY_EU = """d3.formatLocale({ "decimal": ",", "thousands": "\u00a0", "grouping": [3], @@ -15,7 +15,7 @@ "nan": "" })""" -DATA_TYPE_DEFINITIONS = { +_DATA_TYPE_DEFINITIONS = { "number": { "baseDataType": "number", "extendsDataType": "number", @@ -31,7 +31,7 @@ "euro": { "baseDataType": "number", "extendsDataType": "number", - "valueFormatter": {"function": f"{FORMAT_CURRENCY_EU}.format('$,.2f')(params.value)"}, + "valueFormatter": {"function": f"{_FORMAT_CURRENCY_EU}.format('$,.2f')(params.value)"}, }, "percent": { "baseDataType": "number", @@ -69,7 +69,7 @@ def dash_ag_grid(data_frame, **kwargs): }, }, "dashGridOptions": { - "dataTypeDefinitions": DATA_TYPE_DEFINITIONS, + "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, }, } From 602d0fe8371f2d6394a79ef9bb0c4b443375dfcf Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 18:05:51 +0100 Subject: [PATCH 078/128] Delete unnecessary assert in attribute tests --- vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py | 2 +- vizro-core/tests/unit/vizro/models/_components/test_graph.py | 2 +- vizro-core/tests/unit/vizro/models/_components/test_table.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index 215235e70..3f2828b32 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -74,9 +74,9 @@ def test_set_action_via_validator(self, standard_ag_grid, identity_action_functi class TestAttributesAgGrid: + # Testing at this low implementation level as mocking callback contexts skips checking for creation of these objects def test_ag_grid_filter_interaction_attributes(self, ag_grid_with_id): ag_grid = vm.AgGrid(figure=ag_grid_with_id, title="Gapminder", actions=[]) - assert hasattr(ag_grid, "_filter_interaction") ag_grid.pre_build() assert hasattr(ag_grid, "_filter_interaction_input") assert "modelID" in ag_grid._filter_interaction_input diff --git a/vizro-core/tests/unit/vizro/models/_components/test_graph.py b/vizro-core/tests/unit/vizro/models/_components/test_graph.py index 1ecf66e1f..e8e91b8be 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_graph.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_graph.py @@ -108,9 +108,9 @@ def test_set_action_via_validator(self, standard_px_chart, identity_action_funct class TestAttributesGraph: + # Testing at this low implementation level as mocking callback contexts skips checking for creation of these objects def test_graph_filter_interaction_attributes(self, standard_px_chart): graph = vm.Graph(figure=standard_px_chart) - assert hasattr(graph, "_filter_interaction") assert hasattr(graph, "_filter_interaction_input") assert "modelID" in graph._filter_interaction_input diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index 6fa03ea88..53699274a 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -75,13 +75,13 @@ def test_set_action_via_validator(self, standard_dash_table, identity_action_fun class TestAttributesTable: def test_table_filter_interaction_attributes(self, dash_data_table_with_id): table = vm.Table(figure=dash_data_table_with_id, title="Gapminder", actions=[]) - assert hasattr(table, "_filter_interaction") table.pre_build() assert hasattr(table, "_filter_interaction_input") assert "modelID" in table._filter_interaction_input class TestProcessTableDataFrame: + # Testing at this low implementation level as mocking callback contexts skips checking for creation of these objects def test_process_figure_data_frame_str_df(self, dash_table_with_str_dataframe, gapminder): data_manager["gapminder"] = gapminder table_with_str_df = vm.Table(id="table", figure=dash_table_with_str_dataframe) From 634b1e8edc7e0773c6026025866aa4326d07bf54 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 18:15:58 +0100 Subject: [PATCH 079/128] Use assert_component_equal in _dash_* tests --- .../unit/vizro/tables/test_dash_ag_grid.py | 21 +++++++++---- .../unit/vizro/tables/test_dash_table.py | 31 ++++++++++++------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py index f00b9725b..bcec1167a 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py @@ -1,4 +1,6 @@ +import dash_ag_grid as dag import pandas as pd +from asserts import assert_component_equal from vizro.tables import dash_ag_grid data = pd.DataFrame( @@ -14,11 +16,18 @@ class TestDashAgGrid: def test_dash_ag_grid(self): grid = dash_ag_grid(data_frame=data)() - assert grid.columnDefs == [{"field": "cat"}, {"field": "int"}, {"field": "float"}, {"field": "date"}] - assert grid.rowData == [ - {"cat": "a", "int": 4, "float": 7.3, "date": "2021-01-01"}, - {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, - {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, - ] + assert_component_equal( + grid, + dag.AgGrid( + columnDefs=[{"field": "cat"}, {"field": "int"}, {"field": "float"}, {"field": "date"}], + rowData=[ + {"cat": "a", "int": 4, "float": 7.3, "date": "2021-01-01"}, + {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, + {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, + ], + # defaultColDef={"resizable": True, "sortable": True}, + ), + keys_to_strip={"defaultColDef", "dashGridOptions"}, + ) # we could test other properties such as defaultColDef, # but this would just test our chosen defaults, and no functionality really diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_table.py b/vizro-core/tests/unit/vizro/tables/test_dash_table.py index ffcd164c9..82bec2abf 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_table.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_table.py @@ -1,4 +1,6 @@ import pandas as pd +from asserts import assert_component_equal +from dash import dash_table from vizro.tables import dash_data_table data = pd.DataFrame( @@ -14,16 +16,23 @@ class TestDashDataTable: def test_dash_data_table(self): table = dash_data_table(data_frame=data)() - assert table.columns == [ - {"id": "cat", "name": "cat"}, - {"id": "int", "name": "int"}, - {"id": "float", "name": "float"}, - {"id": "date", "name": "date"}, - ] - assert table.data == [ - {"cat": "a", "date": pd.Timestamp("2021-01-01 00:00:00"), "float": 7.3, "int": 4}, - {"cat": "b", "date": pd.Timestamp("2021-01-02 00:00:00"), "float": 8.2, "int": 5}, - {"cat": "c", "date": pd.Timestamp("2021-01-03 00:00:00"), "float": 9.1, "int": 6}, - ] + assert_component_equal( + table, + dash_table.DataTable( + columns=[ + {"id": "cat", "name": "cat"}, + {"id": "int", "name": "int"}, + {"id": "float", "name": "float"}, + {"id": "date", "name": "date"}, + ], + data=[ + {"cat": "a", "int": 4, "float": 7.3, "date": pd.Timestamp("2021-01-01 00:00:00")}, + {"cat": "b", "int": 5, "float": 8.2, "date": pd.Timestamp("2021-01-02 00:00:00")}, + {"cat": "c", "int": 6, "float": 9.1, "date": pd.Timestamp("2021-01-03 00:00:00")}, + ], + ), + keys_to_strip={"style_as_list_view", "style_data", "style_header"}, + ) + # we could test other properties such as style_header, # but this would just test our chosen defaults, and no functionality really From cabdafae2626a50a5dc348281d8e9dde41c4f09b Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 18:25:32 +0100 Subject: [PATCH 080/128] Reshuffle tests to make the category clearer --- .../vizro/models/_components/test_ag_grid.py | 16 +++++++++------- .../unit/vizro/models/_components/test_table.py | 16 +++++++++------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index 3f2828b32..b5cf058df 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -27,7 +27,7 @@ def dash_ag_grid_with_str_dataframe(): return dash_ag_grid(data_frame="gapminder") -class TestDunderMethodsAgGrid: +class TestAgGridInstantiation: def test_create_graph_mandatory_only(self, standard_ag_grid): ag_grid = vm.AgGrid(figure=standard_ag_grid) @@ -57,6 +57,13 @@ def test_failed_ag_grid_with_wrong_captured_callable(self, standard_px_chart): with pytest.raises(ValidationError, match="must provide a valid ag_grid function vm.AgGrid"): vm.AgGrid(figure=standard_px_chart) + def test_set_action_via_validator(self, standard_ag_grid, identity_action_function): + ag_grid = vm.AgGrid(figure=standard_ag_grid, actions=[Action(function=identity_action_function())]) + actions_chain = ag_grid.actions[0] + assert actions_chain.trigger.component_property == "cellClicked" + + +class TestDunderMethodsAgGrid: def test_getitem_known_args(self, dash_ag_grid_with_arguments): ag_grid = vm.AgGrid(figure=dash_ag_grid_with_arguments) assert ag_grid["defaultColDef"] == {"resizable": False, "sortable": False} @@ -67,11 +74,6 @@ def test_getitem_unknown_args(self, standard_ag_grid): with pytest.raises(KeyError): ag_grid["unknown_args"] - def test_set_action_via_validator(self, standard_ag_grid, identity_action_function): - ag_grid = vm.AgGrid(figure=standard_ag_grid, actions=[Action(function=identity_action_function())]) - actions_chain = ag_grid.actions[0] - assert actions_chain.trigger.component_property == "cellClicked" - class TestAttributesAgGrid: # Testing at this low implementation level as mocking callback contexts skips checking for creation of these objects @@ -101,7 +103,7 @@ def test_pre_build_no_actions_no_underlying_ag_grid_id(self, standard_ag_grid): ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) ag_grid.pre_build() - assert hasattr(ag_grid, "_callable_object_id") is False + assert not hasattr(ag_grid, "_callable_object_id") def test_pre_build_actions_no_underlying_ag_grid_id_exception(self, standard_ag_grid, filter_interaction_action): ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid, actions=[filter_interaction_action]) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index 53699274a..17429685e 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -26,7 +26,7 @@ def dash_table_with_str_dataframe(): return dash_data_table(data_frame="gapminder") -class TestDunderMethodsTable: +class TestTableInstantiation: def test_create_graph_mandatory_only(self, standard_dash_table): table = vm.Table(figure=standard_dash_table) @@ -56,6 +56,13 @@ def test_failed_table_with_wrong_captured_callable(self, standard_px_chart): with pytest.raises(ValidationError, match="must provide a valid table function vm.Table"): vm.Table(figure=standard_px_chart) + def test_set_action_via_validator(self, standard_dash_table, identity_action_function): + table = vm.Table(figure=standard_dash_table, actions=[Action(function=identity_action_function())]) + actions_chain = table.actions[0] + assert actions_chain.trigger.component_property == "active_cell" + + +class TestDunderMethodsTable: def test_getitem_known_args(self, dash_table_with_arguments): table = vm.Table(figure=dash_table_with_arguments) assert table["style_header"] == {"border": "1px solid green"} @@ -66,11 +73,6 @@ def test_getitem_unknown_args(self, standard_dash_table): with pytest.raises(KeyError): table["unknown_args"] - def test_set_action_via_validator(self, standard_dash_table, identity_action_function): - table = vm.Table(figure=standard_dash_table, actions=[Action(function=identity_action_function())]) - actions_chain = table.actions[0] - assert actions_chain.trigger.component_property == "active_cell" - class TestAttributesTable: def test_table_filter_interaction_attributes(self, dash_data_table_with_id): @@ -100,7 +102,7 @@ def test_pre_build_no_actions_no_underlying_table_id(self, standard_dash_table): table = vm.Table(id="text_table", figure=standard_dash_table) table.pre_build() - assert hasattr(table, "_callable_object_id") is False + assert not hasattr(table, "_callable_object_id") def test_pre_build_actions_no_underlying_table_id_exception(self, standard_dash_table, filter_interaction_action): table = vm.Table(id="text_table", figure=standard_dash_table, actions=[filter_interaction_action]) From fb6f2066bef10ac0745d51dce01282e53dc3b288 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 23 Feb 2024 18:48:47 +0100 Subject: [PATCH 081/128] Remaining PR comments --- vizro-core/src/vizro/models/_components/ag_grid.py | 1 + vizro-core/src/vizro/models/_components/graph.py | 1 + vizro-core/src/vizro/models/_components/table.py | 1 + 3 files changed, 3 insertions(+) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index f811fe6af..5ff91f08f 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -73,6 +73,7 @@ def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: """Function to be carried out for pre-defined `filter_interaction`.""" + # data_frame is the DF of the target, ie the data to be filtered, hence we cannot get the DF from this model ctd_cellClicked = ctd_filter_interaction["cellClicked"] if not ctd_cellClicked["value"]: return data_frame diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index 63ac4e2b8..0a7cc46bf 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -88,6 +88,7 @@ def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: """Function to be carried out for pre-defined `filter_interaction`.""" + # data_frame is the DF of the target, ie the data to be filtered, hence we cannot get the DF from this model ctd_click_data = ctd_filter_interaction["clickData"] if not ctd_click_data["value"]: return data_frame diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 4c23d6f8a..6a2194a19 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -77,6 +77,7 @@ def _filter_interaction( self, data_frame: pd.DataFrame, target: str, ctd_filter_interaction: Dict[str, CallbackTriggerDict] ) -> pd.DataFrame: """Function to be carried out for pre-defined `filter_interaction`.""" + # data_frame is the DF of the target, ie the data to be filtered, hence we cannot get the DF from this model ctd_active_cell = ctd_filter_interaction["active_cell"] ctd_derived_viewport_data = ctd_filter_interaction["derived_viewport_data"] if not ctd_active_cell["value"] or not ctd_derived_viewport_data["value"]: From 24e0e846c769f970cf7a45233a06b89529cf6d55 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 11:01:26 +0100 Subject: [PATCH 082/128] PR comments Petar --- .../src/vizro/models/_components/ag_grid.py | 23 ++++++++-------- .../src/vizro/models/_components/table.py | 27 +++++++++---------- .../tests/unit/vizro/actions/conftest.py | 2 +- .../actions/test_filter_interaction_action.py | 4 +-- .../unit/vizro/tables/test_dash_ag_grid.py | 1 - 5 files changed, 26 insertions(+), 31 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 5ff91f08f..5916722e1 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -92,27 +92,26 @@ def _filter_interaction( @_log_call def pre_build(self): - if self.actions: - kwargs = self.figure._arguments.copy() + kwargs = self.figure._arguments.copy() - # taken from table implementation - see there for details - kwargs["data_frame"] = pd.DataFrame() - underlying_aggrid_object = self.figure._function(**kwargs) + # taken from table implementation - see there for details + kwargs["data_frame"] = pd.DataFrame() + underlying_aggrid_object = self.figure._function(**kwargs) - if not hasattr(underlying_aggrid_object, "id"): - raise ValueError( - "Underlying `AgGrid` callable has no attribute 'id'. To enable actions triggered by the `AgGrid`" - " a valid 'id' has to be provided to the `AgGrid` callable." - ) + if not hasattr(underlying_aggrid_object, "id"): + raise ValueError( + "Underlying `AgGrid` callable has no attribute 'id'. To enable actions triggered by the `AgGrid`" + " a valid 'id' has to be provided to the `AgGrid` callable." + ) - self._callable_object_id = underlying_aggrid_object.id + self._callable_object_id = underlying_aggrid_object.id def build(self): return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(dag.AgGrid(**({"id": self._callable_object_id} if self.actions else {})), id=self.id), + html.Div(dag.AgGrid(id=self._callable_object_id), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 6a2194a19..88a2b1ca0 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -98,31 +98,28 @@ def _filter_interaction( @_log_call def pre_build(self): - if self.actions: - kwargs = self.figure._arguments.copy() + kwargs = self.figure._arguments.copy() - # This workaround is needed because the underlying table object requires a data_frame - kwargs["data_frame"] = pd.DataFrame() + # This workaround is needed because the underlying table object requires a data_frame + kwargs["data_frame"] = pd.DataFrame() - # The underlying table object is pre-built, so we can fetch its ID. - underlying_table_object = self.figure._function(**kwargs) + # The underlying table object is pre-built, so we can fetch its ID. + underlying_table_object = self.figure._function(**kwargs) - if not hasattr(underlying_table_object, "id"): - raise ValueError( - "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" - " a valid 'id' has to be provided to the `Table` callable." - ) + if not hasattr(underlying_table_object, "id"): + raise ValueError( + "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" + " a valid 'id' has to be provided to the `Table` callable." + ) - self._callable_object_id = underlying_table_object.id + self._callable_object_id = underlying_table_object.id def build(self): return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div( - dash_table.DataTable(**({"id": self._callable_object_id} if self.actions else {})), id=self.id - ), + html.Div(dash_table.DataTable(id=self._callable_object_id), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/tests/unit/vizro/actions/conftest.py b/vizro-core/tests/unit/vizro/actions/conftest.py index f34fec6d8..fb570eb79 100644 --- a/vizro-core/tests/unit/vizro/actions/conftest.py +++ b/vizro-core/tests/unit/vizro/actions/conftest.py @@ -62,7 +62,7 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart): def managers_one_page_two_graphs_one_table_one_aggrid_one_button( box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id ): - """Instantiates a simple model_manager and data_manager with a page, two graph models and the button component.""" + """Instantiates a simple model_manager and data_manager with: page, two graph, table, aggrid and a button component.""" vm.Page( id="test_page", title="My first dashboard", diff --git a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py index cd5638724..b57131ad5 100644 --- a/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py +++ b/vizro-core/tests/unit/vizro/actions/test_filter_interaction_action.py @@ -270,8 +270,8 @@ def test_table_filter_interaction_with_two_targets( def test_aggrid_filter_interaction_with_two_targets( self, ctx_filter_interaction, target_scatter_filtered_continent, target_box_filtered_continent ): - # to not overcrowd these tests with duplication, we use one general case here for the AG Grid - # Functionality should be similar enough to the Dash Datatable that this should suffice + # To not overcrowd these tests with duplication, we use one general case here for the AG Grid + # Functionality should be similar enough to the Dash Datatable that this suffices model_manager["box_chart"].actions = [ vm.Action(function=filter_interaction(targets=["scatter_chart", "box_chart"])) ] diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py index bcec1167a..481d038df 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py @@ -25,7 +25,6 @@ def test_dash_ag_grid(self): {"cat": "b", "int": 5, "float": 8.2, "date": "2021-01-02"}, {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, ], - # defaultColDef={"resizable": True, "sortable": True}, ), keys_to_strip={"defaultColDef", "dashGridOptions"}, ) From c0b9fc91629e6f5a7adc44e72397af88383277a1 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 15:11:56 +0100 Subject: [PATCH 083/128] First round of Li PR comments --- vizro-core/examples/_dev/app.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index ef6649e12..0980e2a57 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -1,5 +1,8 @@ """Example to show dashboard configuration.""" +import random +import string + import numpy as np import pandas as pd import vizro.models as vm @@ -118,14 +121,31 @@ "valueFormatter": {"function": "d3.format('.^30')(params.value)"}, }, ], + defaultColDef={"editable": True}, + # dashGridOptions = {"pagination": False}, ), ), ], ) +num_rows = 10 +num_columns = 20 +column_names = ["Column_" + str(i) for i in range(num_columns)] +data = {} +for column in column_names: + data[column] = ["".join(random.choices(string.ascii_letters, k=random.randint(5, 15))) for _ in range(num_rows)] +df_long = pd.DataFrame(data) + +grid_long = vm.Page( + title="AG Grid Long", + components=[ + vm.AgGrid(figure=dash_ag_grid(id="dash_ag_grid_4", data_frame=df_long)), + ], +) + dashboard = vm.Dashboard( - pages=[grid_interaction, grid_standard, grid_custom], + pages=[grid_interaction, grid_standard, grid_custom, grid_long], ) if __name__ == "__main__": From 5e9ee30f99dcee60f367eb6075f8a60dc43502a7 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 15:12:12 +0100 Subject: [PATCH 084/128] First round of Li PR comments (2) --- vizro-core/src/vizro/static/css/aggrid.css | 69 +++++++++++-------- vizro-core/src/vizro/tables/_dash_ag_grid.py | 2 + .../tests/unit/vizro/actions/conftest.py | 2 +- 3 files changed, 45 insertions(+), 28 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index f3e5144ab..4881fffc9 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -2,52 +2,67 @@ --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); + --ag-header-foreground-color: var( + --text-secondary + ); /* Color of text and icons in the header */ + + --ag-data-color: var(--text-primary); /* Color of text in grid cells */ --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; - --ag-borders: none; --ag-icon-font-family: aggridquartz; - --ag-icon-size: 14px; - --ag-row-height: 48px; /* This determines the row height, but not header */ + --ag-icon-size: var(--text-size-02); + --ag-row-height: 48px; --ag-header-height: 40px; + + /* Borders */ + --ag-borders: none; + --ag-row-border-style: solid; + --ag-row-border-color: var(--border-subtle-alpha-01); + --ag-row-border-width: 1px; + + /* Bg Color */ + --ag-selected-row-background-color: var(--state-overlays-selected); + --ag-active-color: var(--state-overlays-selected-hover); } -/* General setting header row */ +/* Header */ #page-container .ag-theme-quartz .ag-header-row { - color: var(--text-secondary); - font-size: 14px; - font-weight: normal; + align-items: flex-start; + border-bottom: 1px solid var(--border-subtle-alpha-02); + display: flex; + font-weight: 400; + padding: var(--spacing-03) 0 0 0; } -/* Border at the bottom and padding */ #page-container .ag-theme-quartz .ag-header-cell { - border-bottom: 1px solid var(--border-subtle-alpha-02); - padding-left: 0; - padding-right: 12px; + align-items: center; + display: flex; + padding: 0 var(--spacing-03); } -/* Border at bottom when hovering more solid */ -#page-container .ag-theme-quartz .ag-header-cell:hover { - border-bottom: 1px solid var(--border-hover); +#page-container .ag-theme-quartz .ag-header-cell-text { + letter-spacing: -0.112px; + line-height: var(--spacing-04); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } -/* Border bolder when clicking on header, text now primary color */ -#page-container .ag-theme-quartz .ag-header-cell:focus { - border-bottom: 1px solid var(--border-selected); +#page-container .ag-theme-quartz .ag-header-cell:hover { + border-bottom: 1px solid var(--border-hover); color: var(--text-primary); } -#page-container .ag-theme-quartz .ag-row { - border-bottom: 1px solid var(--border-subtle-alpha-02); -} - +/* Rows */ #page-container .ag-theme-quartz .ag-cell { - /* display: flex; */ + padding-left: 0 var(--spacing-03); +} - /* This breaks AGGrid side right alignment of numerical columns */ - align-items: center; - font-size: 14px; - padding-left: 0; - padding-right: 12px; +/* Eliminates border on cell click */ +#page-container + .ag-theme-quartz + .ag-cell-focus:not(.ag-cell-range-selected):focus-within { + border-color: transparent; } /* Filter menu styling */ diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index a69fb9345..7db35a8ca 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -74,7 +74,9 @@ def dash_ag_grid(data_frame, **kwargs): "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, + "domLayout": "autoHeight", }, + "style": {"height": None}, } kwargs = _set_defaults_nested(kwargs, defaults) return dag.AgGrid(**kwargs) diff --git a/vizro-core/tests/unit/vizro/actions/conftest.py b/vizro-core/tests/unit/vizro/actions/conftest.py index fb570eb79..413fb36eb 100644 --- a/vizro-core/tests/unit/vizro/actions/conftest.py +++ b/vizro-core/tests/unit/vizro/actions/conftest.py @@ -62,7 +62,7 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart): def managers_one_page_two_graphs_one_table_one_aggrid_one_button( box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id ): - """Instantiates a simple model_manager and data_manager with: page, two graph, table, aggrid and a button component.""" + """Instantiates a simple model_manager and data_manager with: page, graphs, table, aggrid and button component.""" vm.Page( id="test_page", title="My first dashboard", From 0bc453589a27fb4468035699897d761e7ea62c9d Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 16:47:47 +0100 Subject: [PATCH 085/128] Styling of fly-out menu after PR comments --- vizro-core/examples/_dev/app.py | 4 +- vizro-core/src/vizro/static/css/aggrid.css | 68 +++++++++++++++++++--- 2 files changed, 61 insertions(+), 11 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 0980e2a57..d6a2e169d 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -77,9 +77,7 @@ vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), vm.Parameter( targets=["line_country.y"], - selector=vm.Dropdown( - options=["lifeExp", "gdpPercap", "pop"], multi=False, value="gdpPercap", title="Choose y-axis" - ), + selector=vm.RadioItems(options=["lifeExp", "gdpPercap", "pop"], value="gdpPercap", title="Choose y-axis"), ), ], ) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 4881fffc9..4c017dc9b 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -69,42 +69,94 @@ #page-container .ag-theme-quartz .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); + border-radius: 0; color: var(--text-primary); } /* Selector for different filtering conditions */ #page-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); - border: 1px solid var(--border-subtle-alpha-01); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); + font-size: var(--text-size-02); } #page-container .ag-select-list { background-color: var(--field-enabled); - border: 1px solid var(--border-subtle-alpha-01); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); color: var(--text-primary); + font-size: var(--text-size-02); + line-height: var(--text-size-05); +} + +#page-container .ag-select-list-item.ag-active-item { + background-color: var(--state-overlays-hover); } /* Text/number input */ #page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); - border: 1px solid var(--border-enabled); - color: var(--text-primary); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); + display: flex; + flex-direction: column; + font-size: var(--text-size-02); + font-weight: var(--text-weight-regular); + letter-spacing: var(--letter-spacing-body-ui-02); + line-height: var(--text-size-03); + text-overflow: ellipsis; } /* TODO: fix the color of the background images in the filter menu */ +/* TODO: hover over filter items */ + /* Buttons */ #page-container .ag-theme-quartz .ag-standard-button { - background-color: var(--state-overlays-selected); - border: 1px solid var(--border-subtle-alpha-01); - color: var(--text-primary); + background: var(--fill-active); + border: none; + border-radius: 0; + box-shadow: var(--box-shadow-elevation-0); + color: var(--text-contrast-primary); + font-size: var(--text-size-02); + font-weight: var(--text-weight-semibold); + height: 32px; + letter-spacing: var(--letter-spacing-body-link-02); + line-height: var(--text-size-05); + padding: var(--spacing-01) var(--spacing-03); + text-transform: none; } #page-container .ag-theme-quartz .ag-standard-button:hover { - background-color: var(--state-overlays-selected-hover); + background: linear-gradient( + var(--state-overlays-contrast-hover), + var(--state-overlays-contrast-hover) + ), + var(--fill-active); + color: var(--text-contrast-primary); + text-decoration-line: underline; } +#page-container .ag-theme-quartz .ag-standard-button:active { + background: var(--fill-active); + color: var(--text-contrast-primary); +} + +#page-container .ag-theme-quartz .ag-standard-button:visited { + background: var(--fill-active); + color: var(--text-contrast-primary); +} + +/* TODO: radio button remove blue */ #page-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); color: var(--text-primary); } + +#page-container .ag-radio-button-input-wrapper:focus-within { + box-shadow: none; +} From ec85e0c0dfcd5693d576e5fcc531f8bef88c3ca2 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 17:13:49 +0100 Subject: [PATCH 086/128] Fix bug for missing ID when no actions but ID are defined --- vizro-core/src/vizro/models/_components/ag_grid.py | 11 +++++++---- vizro-core/src/vizro/models/_components/table.py | 10 ++++++---- vizro-core/tests/unit/vizro/actions/conftest.py | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 5916722e1..7782473e2 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -96,22 +96,25 @@ def pre_build(self): # taken from table implementation - see there for details kwargs["data_frame"] = pd.DataFrame() + underlying_aggrid_object = self.figure._function(**kwargs) - if not hasattr(underlying_aggrid_object, "id"): + if hasattr(underlying_aggrid_object, "id"): + self._callable_object_id = underlying_aggrid_object.id + + if self.actions and not hasattr(self,"_callable_object_id"): raise ValueError( "Underlying `AgGrid` callable has no attribute 'id'. To enable actions triggered by the `AgGrid`" " a valid 'id' has to be provided to the `AgGrid` callable." ) - self._callable_object_id = underlying_aggrid_object.id - def build(self): + dash_ag_grid_conf = {"id": self._callable_object_id} if hasattr(self, "_callable_object_id") else {} return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(dag.AgGrid(id=self._callable_object_id), id=self.id), + html.Div(dag.AgGrid(**dash_ag_grid_conf), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 88a2b1ca0..e8bd376c5 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -106,20 +106,22 @@ def pre_build(self): # The underlying table object is pre-built, so we can fetch its ID. underlying_table_object = self.figure._function(**kwargs) - if not hasattr(underlying_table_object, "id"): + if hasattr(underlying_table_object, "id"): + self._callable_object_id = underlying_table_object.id + + if self.actions and not hasattr(self,"_callable_object_id"): raise ValueError( "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" " a valid 'id' has to be provided to the `Table` callable." ) - self._callable_object_id = underlying_table_object.id - def build(self): + dash_table_conf = {"id": self._callable_object_id} if hasattr(self,"_callable_object_id") else {} return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(dash_table.DataTable(id=self._callable_object_id), id=self.id), + html.Div(dash_table.DataTable(**dash_table_conf), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/tests/unit/vizro/actions/conftest.py b/vizro-core/tests/unit/vizro/actions/conftest.py index fb570eb79..413fb36eb 100644 --- a/vizro-core/tests/unit/vizro/actions/conftest.py +++ b/vizro-core/tests/unit/vizro/actions/conftest.py @@ -62,7 +62,7 @@ def managers_one_page_two_graphs_one_button(box_chart, scatter_chart): def managers_one_page_two_graphs_one_table_one_aggrid_one_button( box_chart, scatter_chart, dash_data_table_with_id, ag_grid_with_id ): - """Instantiates a simple model_manager and data_manager with: page, two graph, table, aggrid and a button component.""" + """Instantiates a simple model_manager and data_manager with: page, graphs, table, aggrid and button component.""" vm.Page( id="test_page", title="My first dashboard", From 65f3f0873f5f99bdad1d531bea39ce49d7ecbbc4 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 20:32:03 +0100 Subject: [PATCH 087/128] Final CSS changes --- vizro-core/examples/_dev/app.py | 9 ++++++++- .../src/vizro/models/_components/ag_grid.py | 2 +- vizro-core/src/vizro/models/_components/table.py | 4 ++-- vizro-core/src/vizro/static/css/aggrid.css | 16 +++------------- .../tests/unit/vizro/tables/test_dash_ag_grid.py | 2 +- 5 files changed, 15 insertions(+), 18 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index d6a2e169d..555d5509a 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -141,9 +141,16 @@ ], ) +table_long = vm.Page( + title="Table Long", + components=[ + vm.Table(figure=dash_data_table(id="dash_table_5", data_frame=df_long)), + ], +) + dashboard = vm.Dashboard( - pages=[grid_interaction, grid_standard, grid_custom, grid_long], + pages=[grid_interaction, grid_standard, grid_custom, grid_long, table_long], ) if __name__ == "__main__": diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 7782473e2..8c055d185 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -102,7 +102,7 @@ def pre_build(self): if hasattr(underlying_aggrid_object, "id"): self._callable_object_id = underlying_aggrid_object.id - if self.actions and not hasattr(self,"_callable_object_id"): + if self.actions and not hasattr(self, "_callable_object_id"): raise ValueError( "Underlying `AgGrid` callable has no attribute 'id'. To enable actions triggered by the `AgGrid`" " a valid 'id' has to be provided to the `AgGrid` callable." diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index e8bd376c5..3c90d2119 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -109,14 +109,14 @@ def pre_build(self): if hasattr(underlying_table_object, "id"): self._callable_object_id = underlying_table_object.id - if self.actions and not hasattr(self,"_callable_object_id"): + if self.actions and not hasattr(self, "_callable_object_id"): raise ValueError( "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" " a valid 'id' has to be provided to the `Table` callable." ) def build(self): - dash_table_conf = {"id": self._callable_object_id} if hasattr(self,"_callable_object_id") else {} + dash_table_conf = {"id": self._callable_object_id} if hasattr(self, "_callable_object_id") else {} return dcc.Loading( html.Div( [ diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 4c017dc9b..a49a71e49 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -111,9 +111,9 @@ text-overflow: ellipsis; } -/* TODO: fix the color of the background images in the filter menu */ +/* TODO: fix the color+positioning of the background images in the filter menu */ -/* TODO: hover over filter items */ +/* TODO: change background color when hovering over filter menu */ /* Buttons */ #page-container .ag-theme-quartz .ag-standard-button { @@ -141,20 +141,10 @@ text-decoration-line: underline; } -#page-container .ag-theme-quartz .ag-standard-button:active { - background: var(--fill-active); - color: var(--text-contrast-primary); -} - -#page-container .ag-theme-quartz .ag-standard-button:visited { - background: var(--fill-active); - color: var(--text-contrast-primary); -} - /* TODO: radio button remove blue */ + #page-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); - color: var(--text-primary); } #page-container .ag-radio-button-input-wrapper:focus-within { diff --git a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py index 481d038df..151f47d99 100644 --- a/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py +++ b/vizro-core/tests/unit/vizro/tables/test_dash_ag_grid.py @@ -26,7 +26,7 @@ def test_dash_ag_grid(self): {"cat": "c", "int": 6, "float": 9.1, "date": "2021-01-03"}, ], ), - keys_to_strip={"defaultColDef", "dashGridOptions"}, + keys_to_strip={"className", "defaultColDef", "dashGridOptions", "style"}, ) # we could test other properties such as defaultColDef, # but this would just test our chosen defaults, and no functionality really From 1c206ffe84e195abba4f11bb4b22686e1aff49e4 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Tue, 27 Feb 2024 22:22:52 +0100 Subject: [PATCH 088/128] PR comments docs --- vizro-core/docs/pages/user-guides/custom-tables.md | 10 +++++----- vizro-core/docs/pages/user-guides/table.md | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 78fa3b806..7aac7ee02 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -1,14 +1,14 @@ -# How to create custom Dash Datatables and Dash AG Grids +# How to create custom Dash AG Grids and Dash DataTables -In case that the available arguments for the [`Table`][vizro.models.Table] or [`AgGrid`][vizro.models.AgGrid] models are not sufficient, -there is always the possibility to create a custom Dash DataTable or Dash AG Grid. +In case that the available arguments for the [`AgGrid`][vizro.models.AgGrid] or [`Table`][vizro.models.Table] models are not sufficient, +there is always the possibility to create a custom Dash AG Grid or Dash DataTable. One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see below example). For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: -- define a function that returns a `dash_table.DataTable` or `dash_ag_grid.AgGrid` object -- decorate it with the `@capture("table")` or `@capture("ag_grid")` decorator respectively +- define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object +- decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively - the function must accept a `data_frame` argument (of type `pandas.DataFrame`) - the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 960da77b8..ab555728b 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -10,8 +10,7 @@ In general, [AG Grid](#ag-grid) is Vizro's recommended table implementation, but Vizro offers two models - the [`AgGrid`][vizro.models.AgGrid] model and the [`Table`][vizro.models.Table] model - for the above two approaches respectively. They both visualize tabular data in similar ways. -The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component -(which is in turn based on the [AG Grid](https://www.ag-grid.com/) library), +The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component, while the [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable) component. Both approaches have similar base features, and are configurable in similar ways. However, the AG Grid offers more advanced features out-of-the-box, is more customizable @@ -294,12 +293,14 @@ There are many more ways to alter the grid beyond this showcase. [Table2]: ../../assets/user_guides/table/styled_table.png -If the available arguments are not sufficient, there is always the option to create a [custom AG Grid callable](custom_tables.md). +If the available arguments are not sufficient, there is always the option to create a [custom AG Grid callable](custom-tables.md). ## Dash DataTable Similar to AG Grid, the [Dash DataTable](https://dash.plotly.com/datatable) is an interactive table/grid component designed for viewing, editing, and exploring large datasets. +In general, we recommend using [AG Grid](#ag-grid) for tables unless you have a particular reason to prefer Dash DataTable. + The Vizro [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable). ### Basic usage From b7c81ff743ca490c91c4db04961a6b0a126bbe3a Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 28 Feb 2024 09:37:46 +0100 Subject: [PATCH 089/128] Modify demo and feature dashboards --- vizro-core/examples/demo/app.py | 73 ++++++++----------- vizro-core/examples/features/app.py | 17 ++++- .../features/yaml_version/dashboard.yaml | 11 +++ 3 files changed, 56 insertions(+), 45 deletions(-) diff --git a/vizro-core/examples/demo/app.py b/vizro-core/examples/demo/app.py index 4d2d169b6..5fa97b690 100644 --- a/vizro-core/examples/demo/app.py +++ b/vizro-core/examples/demo/app.py @@ -8,7 +8,7 @@ from vizro import Vizro from vizro.actions import export_data, filter_interaction from vizro.models.types import capture -from vizro.tables import dash_data_table +from vizro.tables import dash_ag_grid gapminder = px.data.gapminder() gapminder_mean = ( @@ -401,14 +401,34 @@ def create_continent_summary(): def create_benchmark_analysis(): """Function returns a page to perform analysis on country level.""" - # Apply formatting to table columns - columns = [ - {"id": "country", "name": "country"}, - {"id": "continent", "name": "continent"}, - {"id": "year", "name": "year"}, - {"id": "lifeExp", "name": "lifeExp", "type": "numeric", "format": {"specifier": ",.1f"}}, - {"id": "gdpPercap", "name": "gdpPercap", "type": "numeric", "format": {"specifier": "$,.2f"}}, - {"id": "pop", "name": "pop", "type": "numeric", "format": {"specifier": ",d"}}, + # Apply formatting to grid columns + cellStyle = { + "styleConditions": [ + { + "condition": "params.value < 1045", + "style": {"backgroundColor": "#ff9222"}, + }, + { + "condition": "params.value >= 1045 && params.value <= 4095", + "style": {"backgroundColor": "#de9e75"}, + }, + { + "condition": "params.value > 4095 && params.value <= 12695", + "style": {"backgroundColor": "#aaa9ba"}, + }, + { + "condition": "params.value > 12695", + "style": {"backgroundColor": "#00b4ff"}, + }, + ] + } + columnsDefs = [ + {"field": "country"}, + {"field": "continent"}, + {"field": "year"}, + {"field": "lifeExp", "cellDataType": "numeric"}, + {"field": "gdpPercap", "cellDataType": "dollar", "cellStyle": cellStyle}, + {"field": "pop"}, ] page_country = vm.Page( @@ -416,40 +436,9 @@ def create_benchmark_analysis(): description="Discovering how the metrics differ for each country and export data for further investigation", layout=vm.Layout(grid=[[0, 1]] * 5 + [[2, -1]], col_gap="32px", row_gap="60px"), components=[ - vm.Table( + vm.AgGrid( title="Click on a cell in country column:", - figure=dash_data_table( - id="dash_data_table_country", - data_frame=gapminder, - columns=columns, - page_size=10, - style_data_conditional=[ - { - "if": {"filter_query": "{gdpPercap} < 1045", "column_id": "gdpPercap"}, - "backgroundColor": "#ff9222", - }, - { - "if": { - "filter_query": "{gdpPercap} >= 1045 && {gdpPercap} <= 4095", - "column_id": "gdpPercap", - }, - "backgroundColor": "#de9e75", - }, - { - "if": { - "filter_query": "{gdpPercap} > 4095 && {gdpPercap} <= 12695", - "column_id": "gdpPercap", - }, - "backgroundColor": "#aaa9ba", - }, - { - "if": {"filter_query": "{gdpPercap} > 12695", "column_id": "gdpPercap"}, - "backgroundColor": "#00b4ff", - }, - ], - sort_action="native", - style_cell={"textAlign": "left"}, - ), + figure=dash_ag_grid(id="dash_ag_grid_country", data_frame=gapminder, columnDefs=columnsDefs), actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), vm.Graph( diff --git a/vizro-core/examples/features/app.py b/vizro-core/examples/features/app.py index e7203b0c3..8dd392108 100644 --- a/vizro-core/examples/features/app.py +++ b/vizro-core/examples/features/app.py @@ -11,7 +11,7 @@ from vizro import Vizro from vizro.actions import export_data, filter_interaction from vizro.models.types import capture -from vizro.tables import dash_data_table +from vizro.tables import dash_ag_grid, dash_data_table iris = px.data.iris() gapminder_2007 = px.data.gapminder().query("year == 2007") @@ -90,6 +90,17 @@ controls=[vm.Filter(column="species", selector=vm.Dropdown(title="Species"))], ) +ag_grid = vm.Page( + title="AG Grid", + components=[ + vm.AgGrid( + title="Dash AG Grid", + figure=dash_ag_grid(data_frame=gapminder_2007), + ) + ], + controls=[vm.Filter(column="continent")], +) + table = vm.Page( title="Table", components=[ @@ -570,7 +581,7 @@ def my_custom_action(t: int): ) # DASHBOARD ------------------------------------------------------------------- -components = [graphs, table, cards, button, containers, tabs] +components = [graphs, ag_grid, table, cards, button, containers, tabs] controls = [filters, parameters] actions = [export_data_action, chart_interaction] extensions = [custom_charts, custom_tables, custom_components, custom_actions] @@ -585,7 +596,7 @@ def my_custom_action(t: int): vm.NavLink( label="Features", pages={ - "Components": ["Graphs", "Table", "Cards", "Button", "Containers", "Tabs"], + "Components": ["Graphs", "AG Grid", "Table", "Cards", "Button", "Containers", "Tabs"], "Controls": ["Filters", "Parameters"], "Actions": ["Export data", "Chart interaction"], "Extensions": ["Custom Charts", "Custom Tables", "Custom Components", "Custom Actions"], diff --git a/vizro-core/examples/features/yaml_version/dashboard.yaml b/vizro-core/examples/features/yaml_version/dashboard.yaml index ebc6bdd74..8a8ec4e70 100644 --- a/vizro-core/examples/features/yaml_version/dashboard.yaml +++ b/vizro-core/examples/features/yaml_version/dashboard.yaml @@ -63,6 +63,16 @@ pages: - column: continent type: filter title: Table + - components: + - figure: + _target_: dash_ag_grid + data_frame: gapminder_2007 + title: Dash AG Grid + type: ag_grid + controls: + - column: continent + type: filter + title: AG Grid - components: - text: | # Header level 1

@@ -352,6 +362,7 @@ navigation: pages: Components: - Graphs + - AG Grid - Table - Cards - Button From 1240151d65a87ffe83f738a44e1b6e447836dd5f Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 28 Feb 2024 10:18:24 +0100 Subject: [PATCH 090/128] Extend column length of example --- vizro-core/examples/_dev/app.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 555d5509a..efbe287b2 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -126,7 +126,7 @@ ], ) -num_rows = 10 +num_rows = 100 num_columns = 20 column_names = ["Column_" + str(i) for i in range(num_columns)] data = {} From b0240aa03cf25ee20d357ea67ffc16e0c05a88fe Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:20:42 +0100 Subject: [PATCH 091/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 7aac7ee02..03519b336 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -1,6 +1,6 @@ # How to create custom Dash AG Grids and Dash DataTables -In case that the available arguments for the [`AgGrid`][vizro.models.AgGrid] or [`Table`][vizro.models.Table] models are not sufficient, +In cases where the available arguments for the [`AgGrid`][vizro.models.AgGrid] or [`Table`][vizro.models.Table] models are not sufficient, there is always the possibility to create a custom Dash AG Grid or Dash DataTable. One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see below example). From 828ec18fd8e0b9d4c8a05c2ebff0a92b49f9a605 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:21:03 +0100 Subject: [PATCH 092/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 03519b336..c1c2b90a8 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -1,7 +1,7 @@ # How to create custom Dash AG Grids and Dash DataTables In cases where the available arguments for the [`AgGrid`][vizro.models.AgGrid] or [`Table`][vizro.models.Table] models are not sufficient, -there is always the possibility to create a custom Dash AG Grid or Dash DataTable. +you can create a custom Dash AG Grid or Dash DataTable. One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see below example). From 7fcd8eba8a714b8252507ad2aabc7bfdce799999 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:22:21 +0100 Subject: [PATCH 093/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index c1c2b90a8..f6ca3ccb5 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -7,7 +7,7 @@ One reason could be that you want to create a table/grid that requires computati For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: -- define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object +- Define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object. - decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively - the function must accept a `data_frame` argument (of type `pandas.DataFrame`) - the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) From 48d9b8605f000a6ca78bf17dc2d2c762b63d1ce4 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:22:57 +0100 Subject: [PATCH 094/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index f6ca3ccb5..65ea19241 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -8,7 +8,7 @@ One reason could be that you want to create a table/grid that requires computati For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: - Define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object. -- decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively +- Decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively. - the function must accept a `data_frame` argument (of type `pandas.DataFrame`) - the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) From 08ee157a9df80e62464f7ff965f960bd1a63d626 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:23:10 +0100 Subject: [PATCH 095/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 65ea19241..c06fae206 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -9,7 +9,7 @@ For this, similar to how one would create a [custom chart](../user-guides/custom - Define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object. - Decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively. -- the function must accept a `data_frame` argument (of type `pandas.DataFrame`) +- The function must accept a `data_frame` argument (of type `pandas.DataFrame`). - the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) The following example shows a possible version of a custom table. In this case the argument `chosen_columns` was added, which you can control with a parameter: From ec65d048a4b202910ce58b15bca03c4e24477d44 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:23:39 +0100 Subject: [PATCH 096/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index c06fae206..781b12faf 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -10,7 +10,7 @@ For this, similar to how one would create a [custom chart](../user-guides/custom - Define a function that returns a `dash_ag_grid.AgGrid` or `dash_table.DataTable` object. - Decorate it with the `@capture("ag_grid")` or `@capture("table")` decorator respectively. - The function must accept a `data_frame` argument (of type `pandas.DataFrame`). -- the table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`) +- The table should be derived from and require only one `pandas.DataFrame` (e.g. any further dataframes added through other arguments will not react to dashboard components such as `Filter`). The following example shows a possible version of a custom table. In this case the argument `chosen_columns` was added, which you can control with a parameter: From dc175c0442c713b8d2abcce0ea90657040e1c15e Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:23:47 +0100 Subject: [PATCH 097/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index ab555728b..02a2ff39e 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -10,7 +10,7 @@ In general, [AG Grid](#ag-grid) is Vizro's recommended table implementation, but Vizro offers two models - the [`AgGrid`][vizro.models.AgGrid] model and the [`Table`][vizro.models.Table] model - for the above two approaches respectively. They both visualize tabular data in similar ways. -The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component, +The main difference between the two is that the [`AgGrid`][vizro.models.AgGrid] model is based on Plotly's [Dash AG Grid](https://dash.plotly.com/dash-ag-grid) component, while the [`Table`][vizro.models.Table] model is based on the [Dash DataTable](https://dash.plotly.com/datatable) component. Both approaches have similar base features, and are configurable in similar ways. However, the AG Grid offers more advanced features out-of-the-box, is more customizable From 83dbbaa01ac1d13c51e3d8c4aa8997f39c2305cb Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:23:58 +0100 Subject: [PATCH 098/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 02a2ff39e..5829128db 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -30,7 +30,7 @@ original [Javascript implementation](https://www.ag-grid.com/). To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: -- insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the +- Insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the [`Page`][vizro.models.Page] model - enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) From 8a3763e5d527527df4345566a46fc6b32ff3703b Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:24:09 +0100 Subject: [PATCH 099/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 5829128db..b387788de 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -31,7 +31,7 @@ original [Javascript implementation](https://www.ag-grid.com/). To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: - Insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the -[`Page`][vizro.models.Page] model +[`Page`][vizro.models.Page] model. - enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. From 8f1adaa280c809781212a2c6d691d749364a8dbd Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:24:19 +0100 Subject: [PATCH 100/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index b387788de..c0187dd80 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -98,7 +98,7 @@ the `columnDefs` argument of your `dash_ag_grid` function: columnDefs = [{"field": "", "cellDataType": "euro"}] ``` -In the below example we select and format some columns of the gapminder dataset. +In the example below we select and format some columns of the gapminder dataset. ??? example "AG Grid with formatted columns" === "app.py" From 7514eac18052c848fdfc9ee620eba0dbfb8b8775 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:24:39 +0100 Subject: [PATCH 101/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index c0187dd80..28dec6e82 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -166,7 +166,7 @@ or a pandas datetime object. Any pandas datetime column will be transformed into #### Objects/Strings No specific formatting is available for custom objects and strings, however you can make use of [Value Formatters](https://dash.plotly.com/dash-ag-grid/value-formatters) -in order to format e.g. displayed strings automatically. +to format e.g. displayed strings automatically. ### Styling/Modifying the AG Grid From 9f179eee89697c80e391a68086b19fb200c6f635 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:24:49 +0100 Subject: [PATCH 102/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 28dec6e82..43d8d24cf 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -159,7 +159,7 @@ In the example below we select and format some columns of the gapminder dataset. #### Dates -In order for the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly, the date must either be of +For the [`AgGrid`][vizro.models.AgGrid] model to sort and filter dates correctly, the date must either be of string format `yyyy-mm-dd` (see [Dash AG Grid docs](https://dash.plotly.com/dash-ag-grid/date-filters#example:-date-filter)) or a pandas datetime object. Any pandas datetime column will be transformed into the `yyyy-mm-dd` format automatically. From de2686c3fb00e98a3fc3877f669882eb33374924 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:24:57 +0100 Subject: [PATCH 103/128] Update vizro-core/docs/pages/user-guides/table.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 43d8d24cf..8fa69e5d3 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -32,7 +32,7 @@ To add a [`AgGrid`][vizro.models.AgGrid] to your page, do the following: - Insert the [`AgGrid`][vizro.models.AgGrid] model into the `components` argument of the [`Page`][vizro.models.Page] model. -- enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`) +- Enter the `dash_ag_grid` function under the `figure` argument (imported via `from vizro.tables import dash_ag_grid`). The Vizro version of this AG Grid differs in one way from the original Dash AG Grid: it requires the user to provide a pandas dataframe as source of data. This must be entered under the argument `data_frame`. All other [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. From 0a79f35010314291c8d5b0d3ec29d2d02f088497 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> Date: Wed, 28 Feb 2024 10:25:15 +0100 Subject: [PATCH 104/128] Update vizro-core/docs/pages/user-guides/custom-tables.md Co-authored-by: Jo Stichbury Signed-off-by: Maximilian Schulz <83698606+maxschulz-COL@users.noreply.github.com> --- vizro-core/docs/pages/user-guides/custom-tables.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vizro-core/docs/pages/user-guides/custom-tables.md b/vizro-core/docs/pages/user-guides/custom-tables.md index 781b12faf..c409c7c43 100644 --- a/vizro-core/docs/pages/user-guides/custom-tables.md +++ b/vizro-core/docs/pages/user-guides/custom-tables.md @@ -3,7 +3,7 @@ In cases where the available arguments for the [`AgGrid`][vizro.models.AgGrid] or [`Table`][vizro.models.Table] models are not sufficient, you can create a custom Dash AG Grid or Dash DataTable. -One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see below example). +One reason could be that you want to create a table/grid that requires computations that can be controlled by parameters (see the example below). For this, similar to how one would create a [custom chart](../user-guides/custom-charts.md), simply do the following: From b029733f6f10b6c9a2b4cef085d7ab0373f79676 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Wed, 28 Feb 2024 10:29:14 +0100 Subject: [PATCH 105/128] Small stylistic change for docs --- vizro-core/docs/pages/user-guides/table.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 8fa69e5d3..d0cc45d31 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -307,9 +307,9 @@ The Vizro [`Table`][vizro.models.Table] model is based on the [Dash DataTable](h To add a [`Table`][vizro.models.Table] to your page, do the following: -- insert the [`Table`][vizro.models.Table] model into the `components` argument of the -[`Page`][vizro.models.Page] model -- enter the `dash_data_table` function under the `figure` argument (imported via `from vizro.tables import dash_data_table`) +- Insert the [`Table`][vizro.models.Table] model into the `components` argument of the +[`Page`][vizro.models.Page] model. +- Enter the `dash_data_table` function under the `figure` argument (imported via `from vizro.tables import dash_data_table`). The Vizro version of this table differs in one way from the original table: it requires the user to provide a pandas dataframe as source of data. This must be entered under the argument `data_frame`. From 1e2f3e07c79d9a5e29f8e95a63f9af070aa5c92b Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 29 Feb 2024 09:55:41 +0100 Subject: [PATCH 106/128] Fix table size and scrolling behaviour --- vizro-core/src/vizro/models/_components/ag_grid.py | 2 +- vizro-core/src/vizro/static/css/aggrid.css | 8 ++++++++ vizro-core/src/vizro/tables/_dash_ag_grid.py | 6 ++---- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 8ac8f3724..7b1948304 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -118,7 +118,7 @@ def build(self): html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id), + html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id,className="ag-subcontainer") ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index a49a71e49..b5edafb18 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -150,3 +150,11 @@ #page-container .ag-radio-button-input-wrapper:focus-within { box-shadow: none; } + +.ag-subcontainer { + display: flex; + flex-direction: column; + height: 100%; + overflow: auto; + width: 100%; +} \ No newline at end of file diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 7db35a8ca..a6085d137 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -68,15 +68,13 @@ def dash_ag_grid(data_frame, **kwargs): "buttons": ["apply", "reset"], "closeOnApply": True, }, - "flex": 1, - "minWidth": 70, + "columnSize":"responsiveSizeToFit", }, "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, - "domLayout": "autoHeight", }, - "style": {"height": None}, + "style": {"height": "100%"}, } kwargs = _set_defaults_nested(kwargs, defaults) return dag.AgGrid(**kwargs) From 1cd54e0b64bf83ae5faf4fbb7028cd2bb4c886fc Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 08:56:02 +0000 Subject: [PATCH 107/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- vizro-core/src/vizro/models/_components/ag_grid.py | 2 +- vizro-core/src/vizro/static/css/aggrid.css | 2 +- vizro-core/src/vizro/tables/_dash_ag_grid.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index 7b1948304..ac22d90ab 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -118,7 +118,7 @@ def build(self): html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id,className="ag-subcontainer") + html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id, className="ag-subcontainer"), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index b5edafb18..17d303088 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -157,4 +157,4 @@ height: 100%; overflow: auto; width: 100%; -} \ No newline at end of file +} diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index a6085d137..853088c8d 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -68,7 +68,7 @@ def dash_ag_grid(data_frame, **kwargs): "buttons": ["apply", "reset"], "closeOnApply": True, }, - "columnSize":"responsiveSizeToFit", + "columnSize": "responsiveSizeToFit", }, "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, From 91933bedb4e245615030c4bd4b44cc957a8a54ff Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 12:33:10 +0100 Subject: [PATCH 108/128] Change to dark theme temporarily --- vizro-core/src/vizro/static/css/aggrid.css | 22 ++++++++++---------- vizro-core/src/vizro/tables/_dash_ag_grid.py | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 17d303088..dc023a3b6 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,4 +1,4 @@ -.ag-theme-quartz.vizro { +.ag-theme-quartz-dark.vizro { --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); @@ -26,7 +26,7 @@ } /* Header */ -#page-container .ag-theme-quartz .ag-header-row { +#page-container .ag-theme-quartz-dark .ag-header-row { align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); display: flex; @@ -34,13 +34,13 @@ padding: var(--spacing-03) 0 0 0; } -#page-container .ag-theme-quartz .ag-header-cell { +#page-container .ag-theme-quartz-dark .ag-header-cell { align-items: center; display: flex; padding: 0 var(--spacing-03); } -#page-container .ag-theme-quartz .ag-header-cell-text { +#page-container .ag-theme-quartz-dark .ag-header-cell-text { letter-spacing: -0.112px; line-height: var(--spacing-04); overflow: hidden; @@ -48,25 +48,25 @@ white-space: nowrap; } -#page-container .ag-theme-quartz .ag-header-cell:hover { +#page-container .ag-theme-quartz-dark .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); color: var(--text-primary); } /* Rows */ -#page-container .ag-theme-quartz .ag-cell { +#page-container .ag-theme-quartz-dark .ag-cell { padding-left: 0 var(--spacing-03); } /* Eliminates border on cell click */ #page-container - .ag-theme-quartz + .ag-theme-quartz-dark .ag-cell-focus:not(.ag-cell-range-selected):focus-within { border-color: transparent; } /* Filter menu styling */ -#page-container .ag-theme-quartz .ag-menu { +#page-container .ag-theme-quartz-dark .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); border-radius: 0; @@ -97,7 +97,7 @@ } /* Text/number input */ -#page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input { +#page-container .ag-theme-quartz-dark .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); border: none; border-radius: 0; @@ -116,7 +116,7 @@ /* TODO: change background color when hovering over filter menu */ /* Buttons */ -#page-container .ag-theme-quartz .ag-standard-button { +#page-container .ag-theme-quartz-dark .ag-standard-button { background: var(--fill-active); border: none; border-radius: 0; @@ -131,7 +131,7 @@ text-transform: none; } -#page-container .ag-theme-quartz .ag-standard-button:hover { +#page-container .ag-theme-quartz-dark .ag-standard-button:hover { background: linear-gradient( var(--state-overlays-contrast-hover), var(--state-overlays-contrast-hover) diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 853088c8d..0bf0b8e5a 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -50,7 +50,7 @@ def dash_ag_grid(data_frame, **kwargs): """Implementation of `dash_ag_grid.AgGrid` with sensible defaults.""" defaults = { - "className": "ag-theme-quartz vizro", + "className": "ag-theme-quartz-dark vizro", "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( lambda x: ( From cea4fc02c17e33a530e60d05e2e47b011f3b1264 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 13:05:51 +0100 Subject: [PATCH 109/128] Remove redundant container and add titles --- vizro-core/examples/_dev/app.py | 4 +++- vizro-core/src/vizro/models/_components/ag_grid.py | 13 +++++-------- vizro-core/src/vizro/static/css/aggrid.css | 8 -------- vizro-core/src/vizro/tables/_dash_ag_grid.py | 4 ++-- 4 files changed, 10 insertions(+), 19 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index efbe287b2..bd91cc5cf 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -93,6 +93,7 @@ title="AG Grid Default", components=[ vm.AgGrid( + title="AG Grid - Default", figure=dash_ag_grid( id="dash_ag_grid_2", data_frame=df2, @@ -105,6 +106,7 @@ title="AG Grid Custom", components=[ vm.AgGrid( + title="Custom AG Grid", figure=dash_ag_grid( id="dash_ag_grid_3", data_frame=df2, @@ -137,7 +139,7 @@ grid_long = vm.Page( title="AG Grid Long", components=[ - vm.AgGrid(figure=dash_ag_grid(id="dash_ag_grid_4", data_frame=df_long)), + vm.AgGrid(figure=dash_ag_grid(id="dash_ag_grid_4", data_frame=df_long), title="AG Grid - long"), ], ) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index ac22d90ab..e2557cce6 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -115,14 +115,11 @@ def build(self): if hasattr(self, "_callable_object_id"): dash_ag_grid_conf["id"] = self._callable_object_id return dcc.Loading( - html.Div( - [ - html.H3(self.title, className="table-title") if self.title else None, - html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id, className="ag-subcontainer"), - ], - className="table-container", - id=f"{self.id}_outer", - ), + [ + html.H3(self.title, className="table-title") if self.title else None, + html.Div(self.figure._function(**dash_ag_grid_conf), id=self.id, className="table-container"), + ], + id=f"{self.id}_outer", color="grey", parent_className="loading-container", ) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index dc023a3b6..f0f73a5f6 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -150,11 +150,3 @@ #page-container .ag-radio-button-input-wrapper:focus-within { box-shadow: none; } - -.ag-subcontainer { - display: flex; - flex-direction: column; - height: 100%; - overflow: auto; - width: 100%; -} diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 0bf0b8e5a..319292e58 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -68,13 +68,13 @@ def dash_ag_grid(data_frame, **kwargs): "buttons": ["apply", "reset"], "closeOnApply": True, }, - "columnSize": "responsiveSizeToFit", + "flex": 1, + "minWidth": 70, }, "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, }, - "style": {"height": "100%"}, } kwargs = _set_defaults_nested(kwargs, defaults) return dag.AgGrid(**kwargs) From f6db92a6ec39ed0c33e5d41c8439f6e7a290bfea Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 13:17:58 +0100 Subject: [PATCH 110/128] Fix CSS --- vizro-core/src/vizro/static/css/aggrid.css | 2 +- vizro-core/src/vizro/tables/_dash_ag_grid.py | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index f0f73a5f6..66a8c9d12 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -55,7 +55,7 @@ /* Rows */ #page-container .ag-theme-quartz-dark .ag-cell { - padding-left: 0 var(--spacing-03); + padding: 0 var(--spacing-03); } /* Eliminates border on cell click */ diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 319292e58..8c801d5f0 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -75,6 +75,7 @@ def dash_ag_grid(data_frame, **kwargs): "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, }, + "style": {"height": "100%"}, } kwargs = _set_defaults_nested(kwargs, defaults) return dag.AgGrid(**kwargs) From 81effefe3b36f53634c729e2e5f4a4c7a0d425c8 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 29 Feb 2024 13:22:36 +0100 Subject: [PATCH 111/128] Implement quartz-dark theme --- .../src/vizro/models/_components/ag_grid.py | 10 ++++-- vizro-core/src/vizro/static/css/aggrid.css | 33 +++++++++++++------ .../vizro/static/js/clientside_functions.js | 2 ++ .../src/vizro/static/js/models/dashboard.js | 4 +++ 4 files changed, 37 insertions(+), 12 deletions(-) diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index ac22d90ab..066879330 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -8,6 +8,7 @@ from pydantic.v1 import Field, PrivateAttr, validator except ImportError: # pragma: no cov from pydantic import Field, PrivateAttr, validator +from dash import ClientsideFunction, Input, Output, clientside_callback import vizro.tables as vt from vizro.actions._actions_utils import CallbackTriggerDict, _get_component_actions, _get_parent_vizro_model @@ -112,8 +113,13 @@ def build(self): # setting as the object that is built on-page-load and rendered finally. dash_ag_grid_conf = self.figure._arguments.copy() dash_ag_grid_conf["data_frame"] = pd.DataFrame() - if hasattr(self, "_callable_object_id"): - dash_ag_grid_conf["id"] = self._callable_object_id + + clientside_callback( + ClientsideFunction(namespace="clientside", function_name="update_ag_grid_theme"), + Output(self._callable_object_id, "className"), + Input("theme_selector", "checked"), + ) + return dcc.Loading( html.Div( [ diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 17d303088..a781f0b3f 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,4 +1,5 @@ -.ag-theme-quartz.vizro { +.ag-theme-quartz.vizro, +.ag-theme-quartz-dark.vizro { --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); @@ -26,7 +27,8 @@ } /* Header */ -#page-container .ag-theme-quartz .ag-header-row { +#page-container .ag-theme-quartz .ag-header-row, +#page-container .ag-theme-quartz-dark .ag-header-row { align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); display: flex; @@ -34,13 +36,15 @@ padding: var(--spacing-03) 0 0 0; } -#page-container .ag-theme-quartz .ag-header-cell { +#page-container .ag-theme-quartz .ag-header-cell, +#page-container .ag-theme-quartz-dark .ag-header-cell { align-items: center; display: flex; padding: 0 var(--spacing-03); } -#page-container .ag-theme-quartz .ag-header-cell-text { +#page-container .ag-theme-quartz .ag-header-cell-text, +#page-container .ag-theme-quartz-dark .ag-header-cell-text { letter-spacing: -0.112px; line-height: var(--spacing-04); overflow: hidden; @@ -48,25 +52,31 @@ white-space: nowrap; } -#page-container .ag-theme-quartz .ag-header-cell:hover { +#page-container .ag-theme-quartz .ag-header-cell:hover, +#page-container .ag-theme-quartz-dark .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); color: var(--text-primary); } /* Rows */ -#page-container .ag-theme-quartz .ag-cell { +#page-container .ag-theme-quartz .ag-cell, +#page-container .ag-theme-quartz-dark .ag-cell { padding-left: 0 var(--spacing-03); } /* Eliminates border on cell click */ #page-container .ag-theme-quartz + .ag-cell-focus:not(.ag-cell-range-selected):focus-within, +#page-container + .ag-theme-quartz-dark .ag-cell-focus:not(.ag-cell-range-selected):focus-within { border-color: transparent; } /* Filter menu styling */ -#page-container .ag-theme-quartz .ag-menu { +#page-container .ag-theme-quartz .ag-menu, +#page-container .ag-theme-quartz-dark .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); border-radius: 0; @@ -97,7 +107,8 @@ } /* Text/number input */ -#page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input { +#page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input, +#page-container .ag-theme-quartz-dark .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); border: none; border-radius: 0; @@ -116,7 +127,8 @@ /* TODO: change background color when hovering over filter menu */ /* Buttons */ -#page-container .ag-theme-quartz .ag-standard-button { +#page-container .ag-theme-quartz .ag-standard-button, +#page-container .ag-theme-quartz-dark .ag-standard-button { background: var(--fill-active); border: none; border-radius: 0; @@ -131,7 +143,8 @@ text-transform: none; } -#page-container .ag-theme-quartz .ag-standard-button:hover { +#page-container .ag-theme-quartz .ag-standard-button:hover, +#page-container .ag-theme-quartz-dark .ag-standard-button:hover { background: linear-gradient( var(--state-overlays-contrast-hover), var(--state-overlays-contrast-hover) diff --git a/vizro-core/src/vizro/static/js/clientside_functions.js b/vizro-core/src/vizro/static/js/clientside_functions.js index 57ae17412..b5bb8424b 100644 --- a/vizro-core/src/vizro/static/js/clientside_functions.js +++ b/vizro-core/src/vizro/static/js/clientside_functions.js @@ -1,6 +1,7 @@ import { _update_dashboard_theme, _collapse_nav_panel, + _update_ag_grid_theme, } from "./models/dashboard.js"; import { _update_range_slider_values } from "./models/range_slider.js"; import { _update_slider_values } from "./models/slider.js"; @@ -19,5 +20,6 @@ window.dash_clientside = Object.assign({}, window.dash_clientside, { gateway: _gateway, after_action_cycle_breaker: _after_action_cycle_breaker, collapse_nav_panel: _collapse_nav_panel, + update_ag_grid_theme: _update_ag_grid_theme, }, }); diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index d50f9f782..01bbb51b5 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -6,6 +6,10 @@ export function _update_dashboard_theme(checked) { return checked ? "vizro_light" : "vizro_dark"; } +export function _update_ag_grid_theme(checked) { + return checked ? "ag-theme-quartz vizro" : "ag-theme-quartz-dark vizro"; +} + export function _collapse_nav_panel(n_clicks, is_open) { if (!n_clicks) { /* Automatically collapses left-side if xs and s-devices are detected*/ From 72998087342fc58a6925eb32ace75d7a86ec183d Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 13:35:06 +0100 Subject: [PATCH 112/128] Refactor CSS for buttons --- vizro-core/src/vizro/static/css/aggrid.css | 41 +++++----------------- vizro-core/src/vizro/static/css/button.css | 4 +-- 2 files changed, 10 insertions(+), 35 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 75383ef78..ebf4df786 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -3,11 +3,9 @@ --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); - --ag-header-foreground-color: var( - --text-secondary - ); /* Color of text and icons in the header */ - - --ag-data-color: var(--text-primary); /* Color of text in grid cells */ + /* Text color for header */ + --ag-header-foreground-color: var(--text-secondary); + --ag-data-color: var(--text-primary); --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; --ag-icon-font-family: aggridquartz; @@ -126,34 +124,6 @@ /* TODO: change background color when hovering over filter menu */ -/* Buttons */ -#page-container .ag-theme-quartz .ag-standard-button, -#page-container .ag-theme-quartz-dark .ag-standard-button { - background: var(--fill-active); - border: none; - border-radius: 0; - box-shadow: var(--box-shadow-elevation-0); - color: var(--text-contrast-primary); - font-size: var(--text-size-02); - font-weight: var(--text-weight-semibold); - height: 32px; - letter-spacing: var(--letter-spacing-body-link-02); - line-height: var(--text-size-05); - padding: var(--spacing-01) var(--spacing-03); - text-transform: none; -} - -#page-container .ag-theme-quartz .ag-standard-button:hover, -#page-container .ag-theme-quartz-dark .ag-standard-button:hover { - background: linear-gradient( - var(--state-overlays-contrast-hover), - var(--state-overlays-contrast-hover) - ), - var(--fill-active); - color: var(--text-contrast-primary); - text-decoration-line: underline; -} - /* TODO: radio button remove blue */ #page-container .ag-radio-button-input-wrapper { @@ -163,3 +133,8 @@ #page-container .ag-radio-button-input-wrapper:focus-within { box-shadow: none; } + + +#page-container .ag-text-field-input::placeholder { + color: var(--text-secondary); +} diff --git a/vizro-core/src/vizro/static/css/button.css b/vizro-core/src/vizro/static/css/button.css index dbbf6c3e4..dff6be5bc 100644 --- a/vizro-core/src/vizro/static/css/button.css +++ b/vizro-core/src/vizro/static/css/button.css @@ -1,4 +1,4 @@ -#dashboard-container .button_primary { +#dashboard-container .button_primary, #dashboard-container .ag-standard-button { background: var(--fill-active); border: none; border-radius: 0; @@ -13,7 +13,7 @@ text-transform: none; } -#dashboard-container .button_primary:hover { +#dashboard-container .button_primary:hover, #dashboard-container .ag-standard-button:hover { background: linear-gradient( var(--state-overlays-contrast-hover), var(--state-overlays-contrast-hover) From 7ed6b8982c3f10a3c2f1fe261e97307c51cdd4f5 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 14:04:52 +0100 Subject: [PATCH 113/128] Refactor and fix alignment --- vizro-core/examples/_dev/app.py | 34 ------------------ vizro-core/src/vizro/static/css/aggrid.css | 42 +++++++++------------- vizro-core/src/vizro/static/css/button.css | 1 - 3 files changed, 17 insertions(+), 60 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index bd91cc5cf..db83fa095 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -33,7 +33,6 @@ id="dash_ag_grid_1", data_frame=px.data.gapminder(), ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), vm.Table( id="table_country", @@ -45,39 +44,6 @@ sort_action="native", style_cell={"textAlign": "left"}, ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Graph( - id="line_country", - figure=px.line( - df_concat, - title="Country vs. Continent", - x="year", - y="gdpPercap", - color="color", - labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, - color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, - markers=True, - hover_name="country", - ), - ), - vm.Button( - text="Export data", - actions=[ - vm.Action( - function=export_data( - targets=["line_country"], - ) - ), - ], - ), - ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), - vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), - vm.Parameter( - targets=["line_country.y"], - selector=vm.RadioItems(options=["lifeExp", "gdpPercap", "pop"], value="gdpPercap", title="Choose y-axis"), ), ], ) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index ebf4df786..b528e80c6 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,5 +1,6 @@ .ag-theme-quartz.vizro, .ag-theme-quartz-dark.vizro { + --ag-active-color: var(--state-overlays-selected-hover); --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); @@ -9,6 +10,7 @@ --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; --ag-icon-font-family: aggridquartz; + --ag-quartz-icon-hover-color: blue; --ag-icon-size: var(--text-size-02); --ag-row-height: 48px; --ag-header-height: 40px; @@ -21,12 +23,11 @@ /* Bg Color */ --ag-selected-row-background-color: var(--state-overlays-selected); - --ag-active-color: var(--state-overlays-selected-hover); + } /* Header */ -#page-container .ag-theme-quartz .ag-header-row, -#page-container .ag-theme-quartz-dark .ag-header-row { +#page-container .ag-header-row { align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); display: flex; @@ -34,15 +35,13 @@ padding: var(--spacing-03) 0 0 0; } -#page-container .ag-theme-quartz .ag-header-cell, -#page-container .ag-theme-quartz-dark .ag-header-cell { +#page-container .ag-header-cell { align-items: center; display: flex; padding: 0 var(--spacing-03); } -#page-container .ag-theme-quartz .ag-header-cell-text, -#page-container .ag-theme-quartz-dark .ag-header-cell-text { +#page-container .ag-header-cell-text { letter-spacing: -0.112px; line-height: var(--spacing-04); overflow: hidden; @@ -50,31 +49,22 @@ white-space: nowrap; } -#page-container .ag-theme-quartz .ag-header-cell:hover, -#page-container .ag-theme-quartz-dark .ag-header-cell:hover { +#page-container .ag-header-cell:hover { border-bottom: 1px solid var(--border-hover); color: var(--text-primary); } /* Rows */ -#page-container .ag-theme-quartz .ag-cell, -#page-container .ag-theme-quartz-dark .ag-cell { +#page-container .ag-cell { padding: 0 var(--spacing-03); } /* Eliminates border on cell click */ -#page-container - .ag-theme-quartz - .ag-cell-focus:not(.ag-cell-range-selected):focus-within, -#page-container - .ag-theme-quartz-dark - .ag-cell-focus:not(.ag-cell-range-selected):focus-within { +#page-container .ag-cell-focus:not(.ag-cell-range-selected):focus-within { border-color: transparent; } -/* Filter menu styling */ -#page-container .ag-theme-quartz .ag-menu, -#page-container .ag-theme-quartz-dark .ag-menu { +#page-container .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); border-radius: 0; @@ -105,14 +95,12 @@ } /* Text/number input */ -#page-container .ag-theme-quartz .ag-ltr .ag-filter-filter input, -#page-container .ag-theme-quartz-dark .ag-ltr .ag-filter-filter input { +#page-container .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); border: none; border-radius: 0; box-shadow: var(--box-shadow-elevation-0); display: flex; - flex-direction: column; font-size: var(--text-size-02); font-weight: var(--text-weight-regular); letter-spacing: var(--letter-spacing-body-ui-02); @@ -120,12 +108,12 @@ text-overflow: ellipsis; } -/* TODO: fix the color+positioning of the background images in the filter menu */ /* TODO: change background color when hovering over filter menu */ /* TODO: radio button remove blue */ +/* Pop up menu*/ #page-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); } @@ -134,7 +122,11 @@ box-shadow: none; } - #page-container .ag-text-field-input::placeholder { color: var(--text-secondary); } + +#page-container .ag-filter-apply-panel { + justify-content: flex-start; + padding: var(--spacing-02) 0 var(--spacing-03) 0; +} diff --git a/vizro-core/src/vizro/static/css/button.css b/vizro-core/src/vizro/static/css/button.css index dff6be5bc..41b984545 100644 --- a/vizro-core/src/vizro/static/css/button.css +++ b/vizro-core/src/vizro/static/css/button.css @@ -20,7 +20,6 @@ ), var(--fill-active); color: var(--text-contrast-primary); - margin: 0; text-decoration-line: underline; } From 6a96695a3bace49877f9c48c314d72662ba48c36 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 14:35:36 +0100 Subject: [PATCH 114/128] Apply correct color to radio-item and tidy --- vizro-core/examples/_dev/app.py | 1 - vizro-core/src/vizro/static/css/aggrid.css | 65 +++++++++++----------- vizro-core/src/vizro/static/css/button.css | 6 +- 3 files changed, 38 insertions(+), 34 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index db83fa095..ce0e2be79 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -8,7 +8,6 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.actions import export_data, filter_interaction from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index b528e80c6..0d723ecc4 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -4,8 +4,10 @@ --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); - /* Text color for header */ - --ag-header-foreground-color: var(--text-secondary); + --ag-header-foreground-color: var( + --text-secondary + ); /* Text color for header */ + --ag-data-color: var(--text-primary); --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; @@ -14,19 +16,17 @@ --ag-icon-size: var(--text-size-02); --ag-row-height: 48px; --ag-header-height: 40px; - - /* Borders */ --ag-borders: none; --ag-row-border-style: solid; --ag-row-border-color: var(--border-subtle-alpha-01); --ag-row-border-width: 1px; - - /* Bg Color */ --ag-selected-row-background-color: var(--state-overlays-selected); - + --ag-checkbox-checked-color: var( + --text-primary + ); /* Has no effect - not sure why */ } -/* Header */ +/* Header ------- */ #page-container .ag-header-row { align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); @@ -54,16 +54,17 @@ color: var(--text-primary); } -/* Rows */ +/* Rows ------- */ #page-container .ag-cell { padding: 0 var(--spacing-03); } -/* Eliminates border on cell click */ #page-container .ag-cell-focus:not(.ag-cell-range-selected):focus-within { + /* Eliminates border on cell click */ border-color: transparent; } +/* Pop up menu ----- */ #page-container .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); @@ -71,49 +72,41 @@ color: var(--text-primary); } -/* Selector for different filtering conditions */ -#page-container .ag-filter-select .ag-picker-field-wrapper { +#page-container .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); border: none; border-radius: 0; box-shadow: var(--box-shadow-elevation-0); + display: flex; font-size: var(--text-size-02); + font-weight: var(--text-weight-regular); + letter-spacing: var(--letter-spacing-body-ui-02); + line-height: var(--text-size-03); + text-overflow: ellipsis; } -#page-container .ag-select-list { +#page-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); border: none; border-radius: 0; box-shadow: var(--box-shadow-elevation-0); - color: var(--text-primary); font-size: var(--text-size-02); - line-height: var(--text-size-05); } -#page-container .ag-select-list-item.ag-active-item { - background-color: var(--state-overlays-hover); -} - -/* Text/number input */ -#page-container .ag-ltr .ag-filter-filter input { +#page-container .ag-select-list { background-color: var(--field-enabled); border: none; border-radius: 0; box-shadow: var(--box-shadow-elevation-0); - display: flex; + color: var(--text-primary); font-size: var(--text-size-02); - font-weight: var(--text-weight-regular); - letter-spacing: var(--letter-spacing-body-ui-02); - line-height: var(--text-size-03); - text-overflow: ellipsis; + line-height: var(--text-size-05); } +#page-container .ag-select-list-item.ag-active-item { + background-color: var(--state-overlays-hover); +} -/* TODO: change background color when hovering over filter menu */ - -/* TODO: radio button remove blue */ - -/* Pop up menu*/ #page-container .ag-radio-button-input-wrapper { background-color: var(--field-enabled); } @@ -130,3 +123,13 @@ justify-content: flex-start; padding: var(--spacing-02) 0 var(--spacing-03) 0; } + +#page-container .ag-filter-condition { + justify-content: flex-start; + padding: var(--spacing-02) var(--spacing-01); +} + +/* Can be controlled via --ag-checkbox-checked-color, but somehow the line below is also required */ +#page-container .ag-radio-button-input-wrapper.ag-checked::after { + color: var(--text-primary); +} diff --git a/vizro-core/src/vizro/static/css/button.css b/vizro-core/src/vizro/static/css/button.css index 41b984545..aa7b6abb4 100644 --- a/vizro-core/src/vizro/static/css/button.css +++ b/vizro-core/src/vizro/static/css/button.css @@ -1,4 +1,5 @@ -#dashboard-container .button_primary, #dashboard-container .ag-standard-button { +#dashboard-container .button_primary, +#dashboard-container .ag-standard-button { background: var(--fill-active); border: none; border-radius: 0; @@ -13,7 +14,8 @@ text-transform: none; } -#dashboard-container .button_primary:hover, #dashboard-container .ag-standard-button:hover { +#dashboard-container .button_primary:hover, +#dashboard-container .ag-standard-button:hover { background: linear-gradient( var(--state-overlays-contrast-hover), var(--state-overlays-contrast-hover) From b91793f2988cc4293cceb8ceeb240b1dd1dc4bb2 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 14:35:57 +0100 Subject: [PATCH 115/128] Add changelog --- ...9_143548_huong_li_nguyen_aggrid_styling.md | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 vizro-core/changelog.d/20240229_143548_huong_li_nguyen_aggrid_styling.md diff --git a/vizro-core/changelog.d/20240229_143548_huong_li_nguyen_aggrid_styling.md b/vizro-core/changelog.d/20240229_143548_huong_li_nguyen_aggrid_styling.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240229_143548_huong_li_nguyen_aggrid_styling.md @@ -0,0 +1,48 @@ + + + + + + + + + From 88b7fc3f9a7e9fe482de344a7eb780588f16696e Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 14:46:01 +0100 Subject: [PATCH 116/128] Fix tests --- .../vizro/models/_components/test_ag_grid.py | 37 ++++++++----------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index 59f0a812d..a614c579a 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -123,14 +123,11 @@ def test_ag_grid_build_mandatory_only(self, standard_ag_grid): ag_grid.pre_build() ag_grid = ag_grid.build() expected_ag_grid = dcc.Loading( - html.Div( - [ - None, - html.Div(dash_ag_grid(data_frame=pd.DataFrame())(), id="text_ag_grid"), - ], - className="table-container", - id="text_ag_grid_outer", - ), + [ + None, + html.Div(dash_ag_grid(data_frame=pd.DataFrame())(), id="text_ag_grid", className="table-container"), + ], + id="text_ag_grid_outer", color="grey", parent_className="loading-container", ) @@ -143,19 +140,17 @@ def test_ag_grid_build_with_underlying_id(self, ag_grid_with_id_and_conf, filter ag_grid = ag_grid.build() expected_ag_grid = dcc.Loading( - html.Div( - [ - None, - html.Div( - dash_ag_grid( - id="underlying_ag_grid_id", data_frame=pd.DataFrame(), dashGridOptions={"pagination": True} - )(), - id="text_ag_grid", - ), - ], - className="table-container", - id="text_ag_grid_outer", - ), + [ + None, + html.Div( + dash_ag_grid( + data_frame=pd.DataFrame(), id="underlying_ag_grid_id", dashGridOptions={"pagination": True} + )(), + id="text_ag_grid", + className="table-container", + ), + ], + id="text_ag_grid_outer", color="grey", parent_className="loading-container", ) From 6759f9f3392d3b9cc75c74361df45836f6b7b5fe Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 15:46:51 +0100 Subject: [PATCH 117/128] Turn on pagination by default --- vizro-core/src/vizro/tables/_dash_ag_grid.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 8c801d5f0..46672433c 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -74,6 +74,7 @@ def dash_ag_grid(data_frame, **kwargs): "dashGridOptions": { "dataTypeDefinitions": _DATA_TYPE_DEFINITIONS, "animateRows": False, + "pagination": True, }, "style": {"height": "100%"}, } From 3c8963c6895f7cf3ed220fa486a631dd7dfc8fef Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 17:12:44 +0100 Subject: [PATCH 118/128] Improve scroll bar --- vizro-core/src/vizro/static/css/scroll_bar.css | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/vizro-core/src/vizro/static/css/scroll_bar.css b/vizro-core/src/vizro/static/css/scroll_bar.css index b626267bf..a151505d8 100644 --- a/vizro-core/src/vizro/static/css/scroll_bar.css +++ b/vizro-core/src/vizro/static/css/scroll_bar.css @@ -31,3 +31,12 @@ .table-container::-webkit-scrollbar-thumb { border-color: var(--main-container-bg-color); } + +/* Can't control thumb color so hover is turned off */ +.ag-root::-webkit-scrollbar { + width: 4px; +} + +.ag-root::-webkit-scrollbar-thumb:hover { + background: var(--field-enabled); +} From 882ba2828b87697ef5b2d2419b08eb71270ffa65 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 17:12:56 +0100 Subject: [PATCH 119/128] Tidy and fix pagination --- vizro-core/src/vizro/static/css/aggrid.css | 25 ++++++++++++---------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 0d723ecc4..caa01d698 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,29 +1,27 @@ .ag-theme-quartz.vizro, .ag-theme-quartz-dark.vizro { + /* All variables with current important tag have no effect - need to understand why */ + --ag-quartz-icon-hover-color: var(--main-container-bg-color) !important; --ag-active-color: var(--state-overlays-selected-hover); --ag-background-color: var(--main-container-bg-color); --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); - --ag-header-foreground-color: var( - --text-secondary - ); /* Text color for header */ - + --ag-header-foreground-color: var(--text-secondary); --ag-data-color: var(--text-primary); --ag-header-background-color: var(--main-container-bg-color); --ag-header-column-resize-handle-display: none; --ag-icon-font-family: aggridquartz; - --ag-quartz-icon-hover-color: blue; --ag-icon-size: var(--text-size-02); --ag-row-height: 48px; --ag-header-height: 40px; --ag-borders: none; + --ag-border-radius: 0 !important; + --ag-border-color: transparent; --ag-row-border-style: solid; --ag-row-border-color: var(--border-subtle-alpha-01); --ag-row-border-width: 1px; --ag-selected-row-background-color: var(--state-overlays-selected); - --ag-checkbox-checked-color: var( - --text-primary - ); /* Has no effect - not sure why */ + --ag-checkbox-checked-color: var(--text-primary) !important; } /* Header ------- */ @@ -85,14 +83,19 @@ text-overflow: ellipsis; } -#page-container .ag-filter-select .ag-picker-field-wrapper { - background-color: var(--field-enabled); +/* Affects pagination */ +#page-container .ag-picker-field-wrapper { border: none; border-radius: 0; - box-shadow: var(--box-shadow-elevation-0); + box-shadow: none; font-size: var(--text-size-02); } +#page-container .ag-filter-select .ag-picker-field-wrapper { + background-color: var(--field-enabled); + box-shadow: var(--box-shadow-elevation-0); +} + #page-container .ag-select-list { background-color: var(--field-enabled); border: none; From 821a5bfa080b0a3b38fa09b5585638818314b1c1 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Thu, 29 Feb 2024 17:14:14 +0100 Subject: [PATCH 120/128] Fix scrollbars --- vizro-core/src/vizro/static/css/scroll_bar.css | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vizro-core/src/vizro/static/css/scroll_bar.css b/vizro-core/src/vizro/static/css/scroll_bar.css index a151505d8..86c243a91 100644 --- a/vizro-core/src/vizro/static/css/scroll_bar.css +++ b/vizro-core/src/vizro/static/css/scroll_bar.css @@ -33,10 +33,10 @@ } /* Can't control thumb color so hover is turned off */ -.ag-root::-webkit-scrollbar { +.ag-root ::-webkit-scrollbar { width: 4px; } -.ag-root::-webkit-scrollbar-thumb:hover { +.ag-root ::-webkit-scrollbar-thumb:hover { background: var(--field-enabled); } From 47fa6b678e6a032fba451b7f6766d9bb7c238164 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 29 Feb 2024 20:56:25 +0100 Subject: [PATCH 121/128] Remove requirement for ID in dash_data_table and dash_ag_grid --- vizro-core/examples/_dev/app.py | 37 ++++++++++++++++++- .../src/vizro/models/_components/ag_grid.py | 20 +++------- .../src/vizro/models/_components/table.py | 23 +++--------- .../vizro/models/_components/test_ag_grid.py | 14 +++---- .../vizro/models/_components/test_table.py | 9 +---- 5 files changed, 54 insertions(+), 49 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index ce0e2be79..6316d8bf2 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -8,6 +8,7 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro +from vizro.actions import export_data, filter_interaction from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() @@ -32,6 +33,7 @@ id="dash_ag_grid_1", data_frame=px.data.gapminder(), ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], ), vm.Table( id="table_country", @@ -43,6 +45,39 @@ sort_action="native", style_cell={"textAlign": "left"}, ), + actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], + ), + vm.Graph( + id="line_country", + figure=px.line( + df_concat, + title="Country vs. Continent", + x="year", + y="gdpPercap", + color="color", + labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, + color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, + markers=True, + hover_name="country", + ), + ), + vm.Button( + text="Export data", + actions=[ + vm.Action( + function=export_data( + targets=["line_country"], + ) + ), + ], + ), + ], + controls=[ + vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), + vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), + vm.Parameter( + targets=["line_country.y"], + selector=vm.RadioItems(options=["lifeExp", "gdpPercap", "pop"], value="gdpPercap", title="Choose y-axis"), ), ], ) @@ -60,7 +95,7 @@ vm.AgGrid( title="AG Grid - Default", figure=dash_ag_grid( - id="dash_ag_grid_2", + # id="dash_ag_grid_2", data_frame=df2, ), ), diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index c9522cbfb..a2463a317 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -51,7 +51,9 @@ class AgGrid(VizroBaseModel): # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) - return self.figure(**kwargs) + figure = self.figure(**kwargs) + figure.id = self._callable_object_id + return figure # Convenience wrapper/syntactic sugar. def __getitem__(self, arg_name: str): @@ -93,26 +95,14 @@ def _filter_interaction( @_log_call def pre_build(self): kwargs = self.figure._arguments.copy() - - # taken from table implementation - see there for details - kwargs["data_frame"] = pd.DataFrame() - - underlying_aggrid_object = self.figure._function(**kwargs) - - if hasattr(underlying_aggrid_object, "id"): - self._callable_object_id = underlying_aggrid_object.id - - if self.actions and not hasattr(self, "_callable_object_id"): - raise ValueError( - "Underlying `AgGrid` callable has no attribute 'id'. To enable actions triggered by the `AgGrid`" - " a valid 'id' has to be provided to the `AgGrid` callable." - ) + self._callable_object_id = kwargs["id"] if "id" in kwargs else self.id + "_figure_callable" def build(self): # The pagination setting (and potentially others) only work when the initially built AgGrid has the same # setting as the object that is built on-page-load and rendered finally. dash_ag_grid_conf = self.figure._arguments.copy() dash_ag_grid_conf["data_frame"] = pd.DataFrame() + dash_ag_grid_conf["id"] = self._callable_object_id clientside_callback( ClientsideFunction(namespace="clientside", function_name="update_ag_grid_theme"), diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 3c90d2119..6b84ccef0 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -50,7 +50,9 @@ class Table(VizroBaseModel): # Convenience wrapper/syntactic sugar. def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) - return self.figure(**kwargs) + figure = self.figure(**kwargs) + figure.id = self._callable_object_id + return figure # Convenience wrapper/syntactic sugar. def __getitem__(self, arg_name: str): @@ -99,29 +101,14 @@ def _filter_interaction( @_log_call def pre_build(self): kwargs = self.figure._arguments.copy() - - # This workaround is needed because the underlying table object requires a data_frame - kwargs["data_frame"] = pd.DataFrame() - - # The underlying table object is pre-built, so we can fetch its ID. - underlying_table_object = self.figure._function(**kwargs) - - if hasattr(underlying_table_object, "id"): - self._callable_object_id = underlying_table_object.id - - if self.actions and not hasattr(self, "_callable_object_id"): - raise ValueError( - "Underlying `Table` callable has no attribute 'id'. To enable actions triggered by the `Table`" - " a valid 'id' has to be provided to the `Table` callable." - ) + self._callable_object_id = kwargs["id"] if "id" in kwargs else self.id + "_figure_callable" def build(self): - dash_table_conf = {"id": self._callable_object_id} if hasattr(self, "_callable_object_id") else {} return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(dash_table.DataTable(**dash_table_conf), id=self.id), + html.Div(dash_table.DataTable(**{"id": self._callable_object_id}), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index a614c579a..18af5e0d8 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -103,17 +103,11 @@ def test_pre_build_no_actions_no_underlying_ag_grid_id(self, standard_ag_grid): ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) ag_grid.pre_build() - assert not hasattr(ag_grid, "_callable_object_id") - - def test_pre_build_actions_no_underlying_ag_grid_id_exception(self, standard_ag_grid, filter_interaction_action): - ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid, actions=[filter_interaction_action]) - with pytest.raises(ValueError, match="Underlying `AgGrid` callable has no attribute 'id'"): - ag_grid.pre_build() + assert ag_grid._callable_object_id == "text_ag_grid_figure_callable" def test_pre_build_actions_underlying_ag_grid_id(self, ag_grid_with_id, filter_interaction_action): ag_grid = vm.AgGrid(id="text_ag_grid", figure=ag_grid_with_id, actions=[filter_interaction_action]) ag_grid.pre_build() - assert ag_grid._callable_object_id == "underlying_ag_grid_id" @@ -125,7 +119,11 @@ def test_ag_grid_build_mandatory_only(self, standard_ag_grid): expected_ag_grid = dcc.Loading( [ None, - html.Div(dash_ag_grid(data_frame=pd.DataFrame())(), id="text_ag_grid", className="table-container"), + html.Div( + dash_ag_grid(data_frame=pd.DataFrame(), id="text_ag_grid_figure_callable")(), + id="text_ag_grid", + className="table-container", + ), ], id="text_ag_grid_outer", color="grey", diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index 17429685e..00487e8de 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -102,12 +102,7 @@ def test_pre_build_no_actions_no_underlying_table_id(self, standard_dash_table): table = vm.Table(id="text_table", figure=standard_dash_table) table.pre_build() - assert not hasattr(table, "_callable_object_id") - - def test_pre_build_actions_no_underlying_table_id_exception(self, standard_dash_table, filter_interaction_action): - table = vm.Table(id="text_table", figure=standard_dash_table, actions=[filter_interaction_action]) - with pytest.raises(ValueError, match="Underlying `Table` callable has no attribute 'id'"): - table.pre_build() + assert table._callable_object_id == "text_table_figure_callable" def test_pre_build_actions_underlying_table_id(self, dash_data_table_with_id, filter_interaction_action): table = vm.Table(id="text_table", figure=dash_data_table_with_id, actions=[filter_interaction_action]) @@ -125,7 +120,7 @@ def test_table_build_mandatory_only(self, standard_dash_table): html.Div( [ None, - html.Div(dash_table.DataTable(), id="text_table"), + html.Div(dash_table.DataTable(id="text_table_figure_callable"), id="text_table"), ], className="table-container", id="text_table_outer", From d8abbd261d2e0712ca0b6fff2721beb3ca62b26e Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Thu, 29 Feb 2024 21:39:22 +0100 Subject: [PATCH 122/128] Renaming of variables --- .../_action_loop/_build_action_loop_callbacks.py | 4 ++-- vizro-core/src/vizro/actions/_actions_utils.py | 4 ++-- .../_callback_mapping/_callback_mapping_utils.py | 2 +- .../src/vizro/models/_components/ag_grid.py | 15 +++++++-------- vizro-core/src/vizro/models/_components/graph.py | 2 +- vizro-core/src/vizro/models/_components/table.py | 15 +++++++-------- .../unit/vizro/models/_components/test_ag_grid.py | 6 +++--- .../unit/vizro/models/_components/test_table.py | 6 +++--- 8 files changed, 26 insertions(+), 28 deletions(-) diff --git a/vizro-core/src/vizro/actions/_action_loop/_build_action_loop_callbacks.py b/vizro-core/src/vizro/actions/_action_loop/_build_action_loop_callbacks.py index 7d471ee59..d285a7c97 100644 --- a/vizro-core/src/vizro/actions/_action_loop/_build_action_loop_callbacks.py +++ b/vizro-core/src/vizro/actions/_action_loop/_build_action_loop_callbacks.py @@ -30,8 +30,8 @@ def _build_action_loop_callbacks() -> None: try: actions_chain_trigger_component = model_manager[ModelID(str(actions_chain_trigger_component_id))] # Use underlying callable object as a trigger component. - if hasattr(actions_chain_trigger_component, "_callable_object_id"): - actions_chain_trigger_component_id = actions_chain_trigger_component._callable_object_id + if hasattr(actions_chain_trigger_component, "_input_component_id"): + actions_chain_trigger_component_id = actions_chain_trigger_component._input_component_id # Not all action_chain_trigger_components are included in model_manager e.g. on_page_load_action_trigger except KeyError: pass diff --git a/vizro-core/src/vizro/actions/_actions_utils.py b/vizro-core/src/vizro/actions/_actions_utils.py index 8b6105321..9eb153997 100644 --- a/vizro-core/src/vizro/actions/_actions_utils.py +++ b/vizro-core/src/vizro/actions/_actions_utils.py @@ -74,8 +74,8 @@ def _get_parent_vizro_model(_underlying_callable_object_id: str) -> VizroBaseMod for _, vizro_base_model in model_manager._items_with_type(VizroBaseModel): if ( - hasattr(vizro_base_model, "_callable_object_id") - and vizro_base_model._callable_object_id == _underlying_callable_object_id + hasattr(vizro_base_model, "_input_component_id") + and vizro_base_model._input_component_id == _underlying_callable_object_id ): return vizro_base_model raise KeyError( diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index 408f7280a..b745bcf4f 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -106,7 +106,7 @@ def _get_action_callback_outputs(action_id: ModelID) -> Dict[str, Output]: return { target: Output( - component_id=target, component_property=model_manager[target]._output_property, allow_duplicate=True + component_id=target, component_property=model_manager[target]._output_component_property, allow_duplicate=True ) for target in targets } diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index a2463a317..ada8d61c4 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -39,10 +39,10 @@ class AgGrid(VizroBaseModel): title: str = Field("", description="Title of the AgGrid") actions: List[Action] = [] - _callable_object_id: str = PrivateAttr() + _input_component_id: str = PrivateAttr() # Component properties for actions and interactions - _output_property: str = PrivateAttr("children") + _output_component_property: str = PrivateAttr("children") # validator set_actions = _action_validator_factory("cellClicked") @@ -52,7 +52,7 @@ class AgGrid(VizroBaseModel): def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) figure = self.figure(**kwargs) - figure.id = self._callable_object_id + figure.id = self._input_component_id return figure # Convenience wrapper/syntactic sugar. @@ -67,7 +67,7 @@ def __getitem__(self, arg_name: str): def _filter_interaction_input(self): """Required properties when using pre-defined `filter_interaction`.""" return { - "cellClicked": State(component_id=self._callable_object_id, component_property="cellClicked"), + "cellClicked": State(component_id=self._input_component_id, component_property="cellClicked"), "modelID": State(component_id=self.id, component_property="id"), # required, to determine triggered model } @@ -94,19 +94,18 @@ def _filter_interaction( @_log_call def pre_build(self): - kwargs = self.figure._arguments.copy() - self._callable_object_id = kwargs["id"] if "id" in kwargs else self.id + "_figure_callable" + self._input_component_id = self.figure._arguments.get("id",f"__input_{self.id}") def build(self): # The pagination setting (and potentially others) only work when the initially built AgGrid has the same # setting as the object that is built on-page-load and rendered finally. dash_ag_grid_conf = self.figure._arguments.copy() dash_ag_grid_conf["data_frame"] = pd.DataFrame() - dash_ag_grid_conf["id"] = self._callable_object_id + dash_ag_grid_conf["id"] = self._input_component_id clientside_callback( ClientsideFunction(namespace="clientside", function_name="update_ag_grid_theme"), - Output(self._callable_object_id, "className"), + Output(self._input_component_id, "className"), Input("theme_selector", "checked"), ) diff --git a/vizro-core/src/vizro/models/_components/graph.py b/vizro-core/src/vizro/models/_components/graph.py index 0a7cc46bf..00dabcfbe 100644 --- a/vizro-core/src/vizro/models/_components/graph.py +++ b/vizro-core/src/vizro/models/_components/graph.py @@ -41,7 +41,7 @@ class Graph(VizroBaseModel): actions: List[Action] = [] # Component properties for actions and interactions - _output_property: str = PrivateAttr("figure") + _output_component_property: str = PrivateAttr("figure") # Re-used validators _set_actions = _action_validator_factory("clickData") diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 6b84ccef0..91b7c9dcf 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -38,10 +38,10 @@ class Table(VizroBaseModel): title: str = Field("", description="Title of the table") actions: List[Action] = [] - _callable_object_id: str = PrivateAttr() + _input_component_id: str = PrivateAttr() # Component properties for actions and interactions - _output_property: str = PrivateAttr("children") + _output_component_property: str = PrivateAttr("children") # validator set_actions = _action_validator_factory("active_cell") @@ -51,7 +51,7 @@ class Table(VizroBaseModel): def __call__(self, **kwargs): kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) figure = self.figure(**kwargs) - figure.id = self._callable_object_id + figure.id = self._input_component_id return figure # Convenience wrapper/syntactic sugar. @@ -67,9 +67,9 @@ def __getitem__(self, arg_name: str): def _filter_interaction_input(self): """Required properties when using pre-defined `filter_interaction`.""" return { - "active_cell": State(component_id=self._callable_object_id, component_property="active_cell"), + "active_cell": State(component_id=self._input_component_id, component_property="active_cell"), "derived_viewport_data": State( - component_id=self._callable_object_id, + component_id=self._input_component_id, component_property="derived_viewport_data", ), "modelID": State(component_id=self.id, component_property="id"), # required, to determine triggered model @@ -100,15 +100,14 @@ def _filter_interaction( @_log_call def pre_build(self): - kwargs = self.figure._arguments.copy() - self._callable_object_id = kwargs["id"] if "id" in kwargs else self.id + "_figure_callable" + self._input_component_id = self.figure._arguments.get("id",f"__input_{self.id}") def build(self): return dcc.Loading( html.Div( [ html.H3(self.title, className="table-title") if self.title else None, - html.Div(dash_table.DataTable(**{"id": self._callable_object_id}), id=self.id), + html.Div(dash_table.DataTable(**{"id": self._input_component_id}), id=self.id), ], className="table-container", id=f"{self.id}_outer", diff --git a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py index 18af5e0d8..fafd25a66 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_ag_grid.py @@ -103,12 +103,12 @@ def test_pre_build_no_actions_no_underlying_ag_grid_id(self, standard_ag_grid): ag_grid = vm.AgGrid(id="text_ag_grid", figure=standard_ag_grid) ag_grid.pre_build() - assert ag_grid._callable_object_id == "text_ag_grid_figure_callable" + assert ag_grid._input_component_id == "__input_text_ag_grid" def test_pre_build_actions_underlying_ag_grid_id(self, ag_grid_with_id, filter_interaction_action): ag_grid = vm.AgGrid(id="text_ag_grid", figure=ag_grid_with_id, actions=[filter_interaction_action]) ag_grid.pre_build() - assert ag_grid._callable_object_id == "underlying_ag_grid_id" + assert ag_grid._input_component_id == "underlying_ag_grid_id" class TestBuildAgGrid: @@ -120,7 +120,7 @@ def test_ag_grid_build_mandatory_only(self, standard_ag_grid): [ None, html.Div( - dash_ag_grid(data_frame=pd.DataFrame(), id="text_ag_grid_figure_callable")(), + dash_ag_grid(data_frame=pd.DataFrame(), id="__input_text_ag_grid")(), id="text_ag_grid", className="table-container", ), diff --git a/vizro-core/tests/unit/vizro/models/_components/test_table.py b/vizro-core/tests/unit/vizro/models/_components/test_table.py index 00487e8de..3aca5b93f 100644 --- a/vizro-core/tests/unit/vizro/models/_components/test_table.py +++ b/vizro-core/tests/unit/vizro/models/_components/test_table.py @@ -102,13 +102,13 @@ def test_pre_build_no_actions_no_underlying_table_id(self, standard_dash_table): table = vm.Table(id="text_table", figure=standard_dash_table) table.pre_build() - assert table._callable_object_id == "text_table_figure_callable" + assert table._input_component_id == "__input_text_table" def test_pre_build_actions_underlying_table_id(self, dash_data_table_with_id, filter_interaction_action): table = vm.Table(id="text_table", figure=dash_data_table_with_id, actions=[filter_interaction_action]) table.pre_build() - assert table._callable_object_id == "underlying_table_id" + assert table._input_component_id == "underlying_table_id" class TestBuildTable: @@ -120,7 +120,7 @@ def test_table_build_mandatory_only(self, standard_dash_table): html.Div( [ None, - html.Div(dash_table.DataTable(id="text_table_figure_callable"), id="text_table"), + html.Div(dash_table.DataTable(id="__input_text_table"), id="text_table"), ], className="table-container", id="text_table_outer", From 99fe5f8a44f7195c255bccf6a41aba2c98a321c2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 20:39:46 +0000 Subject: [PATCH 123/128] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .../actions/_callback_mapping/_callback_mapping_utils.py | 4 +++- vizro-core/src/vizro/models/_components/ag_grid.py | 2 +- vizro-core/src/vizro/models/_components/table.py | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py index b745bcf4f..97bcfe664 100644 --- a/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py +++ b/vizro-core/src/vizro/actions/_callback_mapping/_callback_mapping_utils.py @@ -106,7 +106,9 @@ def _get_action_callback_outputs(action_id: ModelID) -> Dict[str, Output]: return { target: Output( - component_id=target, component_property=model_manager[target]._output_component_property, allow_duplicate=True + component_id=target, + component_property=model_manager[target]._output_component_property, + allow_duplicate=True, ) for target in targets } diff --git a/vizro-core/src/vizro/models/_components/ag_grid.py b/vizro-core/src/vizro/models/_components/ag_grid.py index ada8d61c4..f6fa6cce3 100644 --- a/vizro-core/src/vizro/models/_components/ag_grid.py +++ b/vizro-core/src/vizro/models/_components/ag_grid.py @@ -94,7 +94,7 @@ def _filter_interaction( @_log_call def pre_build(self): - self._input_component_id = self.figure._arguments.get("id",f"__input_{self.id}") + self._input_component_id = self.figure._arguments.get("id", f"__input_{self.id}") def build(self): # The pagination setting (and potentially others) only work when the initially built AgGrid has the same diff --git a/vizro-core/src/vizro/models/_components/table.py b/vizro-core/src/vizro/models/_components/table.py index 91b7c9dcf..1d3da5892 100644 --- a/vizro-core/src/vizro/models/_components/table.py +++ b/vizro-core/src/vizro/models/_components/table.py @@ -100,7 +100,7 @@ def _filter_interaction( @_log_call def pre_build(self): - self._input_component_id = self.figure._arguments.get("id",f"__input_{self.id}") + self._input_component_id = self.figure._arguments.get("id", f"__input_{self.id}") def build(self): return dcc.Loading( From 6ca06d71061abbc567e286cd5738c780e09fa78e Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Fri, 1 Mar 2024 09:42:51 +0100 Subject: [PATCH 124/128] Fix remaining unwanted CSS hover color --- vizro-core/examples/_dev/app.py | 45 ------------------- vizro-core/src/vizro/static/css/aggrid.css | 38 +++++++++------- .../src/vizro/static/js/models/dashboard.js | 2 +- vizro-core/src/vizro/tables/_dash_ag_grid.py | 2 +- 4 files changed, 23 insertions(+), 64 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 6316d8bf2..5b3e3f22c 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -33,51 +33,6 @@ id="dash_ag_grid_1", data_frame=px.data.gapminder(), ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Table( - id="table_country", - title="Click on a cell", - figure=dash_data_table( - id="dash_data_table_country", - data_frame=df, - columns=[{"id": col, "name": col} for col in df.columns], - sort_action="native", - style_cell={"textAlign": "left"}, - ), - actions=[vm.Action(function=filter_interaction(targets=["line_country"]))], - ), - vm.Graph( - id="line_country", - figure=px.line( - df_concat, - title="Country vs. Continent", - x="year", - y="gdpPercap", - color="color", - labels={"year": "Year", "data": "Data", "gdpPercap": "GDP per capita"}, - color_discrete_map={"Country": "#afe7f9", "Continent": "#003875"}, - markers=True, - hover_name="country", - ), - ), - vm.Button( - text="Export data", - actions=[ - vm.Action( - function=export_data( - targets=["line_country"], - ) - ), - ], - ), - ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False, title="Select continent")), - vm.Filter(column="year", selector=vm.RangeSlider(title="Select timeframe", step=1, marks=None)), - vm.Parameter( - targets=["line_country.y"], - selector=vm.RadioItems(options=["lifeExp", "gdpPercap", "pop"], value="gdpPercap", title="Choose y-axis"), ), ], ) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index caa01d698..c9d87564d 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -1,10 +1,7 @@ -.ag-theme-quartz.vizro, -.ag-theme-quartz-dark.vizro { - /* All variables with current important tag have no effect - need to understand why */ - --ag-quartz-icon-hover-color: var(--main-container-bg-color) !important; +.ag-theme-quartz.ag-theme-vizro, +.ag-theme-quartz-dark.ag-theme-vizro { --ag-active-color: var(--state-overlays-selected-hover); --ag-background-color: var(--main-container-bg-color); - --ag-foreground-color: var(--text-primary); --ag-odd-row-background-color: var(--main-container-bg-color); --ag-header-foreground-color: var(--text-secondary); --ag-data-color: var(--text-primary); @@ -21,7 +18,7 @@ --ag-row-border-color: var(--border-subtle-alpha-01); --ag-row-border-width: 1px; --ag-selected-row-background-color: var(--state-overlays-selected); - --ag-checkbox-checked-color: var(--text-primary) !important; + --ag-checkbox-checked-color: var(--text-primary); } /* Header ------- */ @@ -83,14 +80,6 @@ text-overflow: ellipsis; } -/* Affects pagination */ -#page-container .ag-picker-field-wrapper { - border: none; - border-radius: 0; - box-shadow: none; - font-size: var(--text-size-02); -} - #page-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); box-shadow: var(--box-shadow-elevation-0); @@ -132,7 +121,22 @@ padding: var(--spacing-02) var(--spacing-01); } -/* Can be controlled via --ag-checkbox-checked-color, but somehow the line below is also required */ -#page-container .ag-radio-button-input-wrapper.ag-checked::after { - color: var(--text-primary); +/* Scroll Bar */ +#page-container .ag-body-horizontal-scroll-viewport { + height: 4px !important; + max-height: 4px !important; + min-height: 4px !important; +} + +/* Pagination */ +#page-container .ag-picker-field-wrapper { + border: none; + border-radius: 0; + box-shadow: none; + font-size: var(--text-size-02); +} + +.ag-theme-quartz.ag-theme-vizro .ag-header-cell-menu-button, +.ag-theme-quartz-dark.ag-theme-vizro .ag-header-cell-menu-button { + --ag-quartz-icon-hover-color: transparent; } diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index 01bbb51b5..86d1b1825 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -7,7 +7,7 @@ export function _update_dashboard_theme(checked) { } export function _update_ag_grid_theme(checked) { - return checked ? "ag-theme-quartz vizro" : "ag-theme-quartz-dark vizro"; + return checked ? "ag-theme-quartz ag-theme-vizro" : "ag-theme-quartz-dark ag-theme-vizro"; } export function _collapse_nav_panel(n_clicks, is_open) { diff --git a/vizro-core/src/vizro/tables/_dash_ag_grid.py b/vizro-core/src/vizro/tables/_dash_ag_grid.py index 46672433c..ec7345c03 100644 --- a/vizro-core/src/vizro/tables/_dash_ag_grid.py +++ b/vizro-core/src/vizro/tables/_dash_ag_grid.py @@ -50,7 +50,7 @@ def dash_ag_grid(data_frame, **kwargs): """Implementation of `dash_ag_grid.AgGrid` with sensible defaults.""" defaults = { - "className": "ag-theme-quartz-dark vizro", + "className": "ag-theme-quartz-dark ag-theme-vizro", "columnDefs": [{"field": col} for col in data_frame.columns], "rowData": data_frame.apply( lambda x: ( From b0ca6de6adf114bc845beeca341a98bec8e62d9c Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Fri, 1 Mar 2024 09:54:23 +0100 Subject: [PATCH 125/128] Remove unused properties --- vizro-core/examples/_dev/app.py | 1 - vizro-core/src/vizro/static/css/aggrid.css | 10 ++-------- vizro-core/src/vizro/static/js/models/dashboard.js | 4 +++- 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/vizro-core/examples/_dev/app.py b/vizro-core/examples/_dev/app.py index 5b3e3f22c..7c4da4b59 100644 --- a/vizro-core/examples/_dev/app.py +++ b/vizro-core/examples/_dev/app.py @@ -8,7 +8,6 @@ import vizro.models as vm import vizro.plotly.express as px from vizro import Vizro -from vizro.actions import export_data, filter_interaction from vizro.tables import dash_ag_grid, dash_data_table df = px.data.gapminder() diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index c9d87564d..3ec5da5db 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -12,7 +12,7 @@ --ag-row-height: 48px; --ag-header-height: 40px; --ag-borders: none; - --ag-border-radius: 0 !important; + --ag-border-radius: 0; --ag-border-color: transparent; --ag-row-border-style: solid; --ag-row-border-color: var(--border-subtle-alpha-01); @@ -26,7 +26,6 @@ align-items: flex-start; border-bottom: 1px solid var(--border-subtle-alpha-02); display: flex; - font-weight: 400; padding: var(--spacing-03) 0 0 0; } @@ -63,14 +62,11 @@ #page-container .ag-menu { background-color: var(--surfaces-bg-02); border: 1px solid var(--border-subtle-alpha-02); - border-radius: 0; color: var(--text-primary); } #page-container .ag-ltr .ag-filter-filter input { background-color: var(--field-enabled); - border: none; - border-radius: 0; box-shadow: var(--box-shadow-elevation-0); display: flex; font-size: var(--text-size-02); @@ -87,8 +83,6 @@ #page-container .ag-select-list { background-color: var(--field-enabled); - border: none; - border-radius: 0; box-shadow: var(--box-shadow-elevation-0); color: var(--text-primary); font-size: var(--text-size-02); @@ -128,7 +122,7 @@ min-height: 4px !important; } -/* Pagination */ +/* Filter fields and pagination */ #page-container .ag-picker-field-wrapper { border: none; border-radius: 0; diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index 86d1b1825..5352cfaed 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -7,7 +7,9 @@ export function _update_dashboard_theme(checked) { } export function _update_ag_grid_theme(checked) { - return checked ? "ag-theme-quartz ag-theme-vizro" : "ag-theme-quartz-dark ag-theme-vizro"; + return checked + ? "ag-theme-quartz ag-theme-vizro" + : "ag-theme-quartz-dark ag-theme-vizro"; } export function _collapse_nav_panel(n_clicks, is_open) { From cccd02a2846e20062558bceff14c0726707137c3 Mon Sep 17 00:00:00 2001 From: huong-li-nguyen Date: Fri, 1 Mar 2024 10:08:26 +0100 Subject: [PATCH 126/128] Lint --- vizro-core/src/vizro/static/css/aggrid.css | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/vizro-core/src/vizro/static/css/aggrid.css b/vizro-core/src/vizro/static/css/aggrid.css index 3ec5da5db..60107a3d5 100644 --- a/vizro-core/src/vizro/static/css/aggrid.css +++ b/vizro-core/src/vizro/static/css/aggrid.css @@ -76,6 +76,14 @@ text-overflow: ellipsis; } +/* Filter fields and pagination */ +#page-container .ag-picker-field-wrapper { + border: none; + border-radius: 0; + box-shadow: none; + font-size: var(--text-size-02); +} + #page-container .ag-filter-select .ag-picker-field-wrapper { background-color: var(--field-enabled); box-shadow: var(--box-shadow-elevation-0); @@ -122,14 +130,6 @@ min-height: 4px !important; } -/* Filter fields and pagination */ -#page-container .ag-picker-field-wrapper { - border: none; - border-radius: 0; - box-shadow: none; - font-size: var(--text-size-02); -} - .ag-theme-quartz.ag-theme-vizro .ag-header-cell-menu-button, .ag-theme-quartz-dark.ag-theme-vizro .ag-header-cell-menu-button { --ag-quartz-icon-hover-color: transparent; From 43909deecad48797411888343edc403f5eac5e71 Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 1 Mar 2024 12:04:10 +0100 Subject: [PATCH 127/128] Screenshots and small edits --- .../docs/assets/user_guides/table/aggrid.png | Bin 0 -> 187797 bytes .../user_guides/table/formatted_aggrid.png | Bin 0 -> 172792 bytes .../assets/user_guides/table/styled_aggrid.png | Bin 0 -> 178189 bytes vizro-core/docs/pages/user-guides/table.md | 14 +++++++------- 4 files changed, 7 insertions(+), 7 deletions(-) create mode 100644 vizro-core/docs/assets/user_guides/table/aggrid.png create mode 100644 vizro-core/docs/assets/user_guides/table/formatted_aggrid.png create mode 100644 vizro-core/docs/assets/user_guides/table/styled_aggrid.png diff --git a/vizro-core/docs/assets/user_guides/table/aggrid.png b/vizro-core/docs/assets/user_guides/table/aggrid.png new file mode 100644 index 0000000000000000000000000000000000000000..517812f30ab09462b9a260491c57af864f9bf892 GIT binary patch literal 187797 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�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

7JRCR>rfI+fGqY&Ps8D9NuJ2Vy#pFOpk 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 G>jw%gb(4)8>yBU)DX)d8)A`?SY|rYH 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 literal 0 HcmV?d00001 diff --git a/vizro-core/docs/pages/user-guides/table.md b/vizro-core/docs/pages/user-guides/table.md index 6ed507158..c7a3fad5c 100755 --- a/vizro-core/docs/pages/user-guides/table.md +++ b/vizro-core/docs/pages/user-guides/table.md @@ -77,9 +77,9 @@ Note that some defaults are set for some of the arguments (e.g. for `columnDefs` title: Example of a Dash AG Grid ``` === "Result" - [![Table]][Table] + [![AGGrid]][AGGrid] - [Table]: ../../assets/user_guides/table/table.png + [AGGrid]: ../../assets/user_guides/table/aggrid.png ### Formatting columns @@ -153,9 +153,9 @@ In the example below we select and format some columns of the gapminder dataset. title: Example of AG Grid with formatted columns ``` === "Result" - [![Table2]][Table2] + [![AGGrid2]][AGGrid2] - [Table2]: ../../assets/user_guides/table/styled_table.png + [AGGrid2]: ../../assets/user_guides/table/formatted_aggrid.png #### Dates @@ -169,7 +169,7 @@ No specific formatting is available for custom objects and strings, however you to format e.g. displayed strings automatically. -### Styling/Modifying the AG Grid +### Styling and modifying the AG Grid As mentioned above, all [parameters of the Dash AG Grid](https://dash.plotly.com/dash-ag-grid/reference) can be entered as keyword arguments. Below you can find an example of a styled AG Grid where some conditional formatting is applied, and where the columns are editable, but not filterable or resizable. @@ -289,9 +289,9 @@ There are many more ways to alter the grid beyond this showcase. title: Example of a Dash AG Grid ``` === "Result" - [![Table2]][Table2] + [![AGGrid3]][AGGrid3] - [Table2]: ../../assets/user_guides/table/styled_table.png + [AGGrid3]: ../../assets/user_guides/table/styled_aggrid.png If the available arguments are not sufficient, there is always the option to create a [custom AG Grid callable](custom-tables.md). From c6b099afba69fd202f9202c55ea58a91c4c6efec Mon Sep 17 00:00:00 2001 From: Maximilian Schulz Date: Fri, 1 Mar 2024 12:53:52 +0100 Subject: [PATCH 128/128] Add changelog --- ..._124716_maximilian_schulz_aggrid_docs_2.md | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 vizro-core/changelog.d/20240301_124716_maximilian_schulz_aggrid_docs_2.md diff --git a/vizro-core/changelog.d/20240301_124716_maximilian_schulz_aggrid_docs_2.md b/vizro-core/changelog.d/20240301_124716_maximilian_schulz_aggrid_docs_2.md new file mode 100644 index 000000000..6f680acca --- /dev/null +++ b/vizro-core/changelog.d/20240301_124716_maximilian_schulz_aggrid_docs_2.md @@ -0,0 +1,49 @@ + + +### Highlights ✨ + +- Introduce `AgGrid` as a new `Page` component, allowing the usage of + [AG Grid](https://www.ag-grid.com/javascript-data-grid/scrolling-scenarios/) in + `Vizro`. See the [user guide on tables](https://vizro.readthedocs.io/en/stable/pages/user_guides/table/) + for more information. ([#289](https://github.com/mckinsey/vizro/pull/289),[#268](https://github.com/mckinsey/vizro/pull/268),[#324](https://github.com/mckinsey/vizro/pull/324)) + + + + + + +