Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable dash datatable #114

Merged
merged 99 commits into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
642ec46
Bring over changes from old repo
huong-li-nguyen Sep 28, 2023
a300631
Add notes
huong-li-nguyen Oct 10, 2023
3c45787
Add comments which code sections are plotly specific
huong-li-nguyen Oct 10, 2023
16516f7
Add more thoughts
huong-li-nguyen Oct 11, 2023
6ad33ca
Update scratch.py
huong-li-nguyen Oct 11, 2023
3bd9e48
Clean up
huong-li-nguyen Oct 11, 2023
c494fcb
Comment out d3 changes
huong-li-nguyen Oct 11, 2023
caf154a
Merge branch 'main' into feature/table_component_MS
maxschulz-COL Oct 12, 2023
5aa22b0
Revert table CSS (for now)
maxschulz-COL Oct 13, 2023
e3a08b6
Update refactoring places
maxschulz-COL Oct 13, 2023
53e3124
UPdate example
maxschulz-COL Oct 13, 2023
447f9af
Abstracting several plotly dependent methods
maxschulz-COL Oct 16, 2023
ad363b7
Improvement to abstraction level
maxschulz-COL Oct 16, 2023
bc16ba5
Further abstraction
maxschulz-COL Oct 16, 2023
e84fcb7
implement hasattr logic more
maxschulz-COL Oct 16, 2023
f2376ed
Individually comment out CSS
maxschulz-COL Oct 16, 2023
6fc5c78
further abstraction of update theme
maxschulz-COL Oct 16, 2023
b83ec59
Simplify update layout even further
maxschulz-COL Oct 16, 2023
b2b2b29
Fix table CSS
maxschulz-COL Oct 17, 2023
b38b8b4
Avoid jumping
maxschulz-COL Oct 17, 2023
fc87121
interaction example
maxschulz-COL Oct 17, 2023
6ad35de
Private attributes for input properties
maxschulz-COL Oct 17, 2023
8ff80c7
Take out hover behaviour on dark
maxschulz-COL Oct 17, 2023
5a7ffe9
Table CSS
maxschulz-COL Oct 17, 2023
51bcd91
reminder
maxschulz-COL Oct 17, 2023
5d080c3
Try out AGGrid
maxschulz-COL Oct 17, 2023
ac58e28
Delete unnecessary files
maxschulz-COL Oct 17, 2023
381514b
Enable proper capture decorator
maxschulz-COL Oct 17, 2023
05f19c6
remove scratch
maxschulz-COL Oct 17, 2023
5168a6f
Clean up comments
maxschulz-COL Oct 17, 2023
edd0386
Mypy lint including ModelID
maxschulz-COL Oct 18, 2023
77719b7
General lint
maxschulz-COL Oct 18, 2023
652ab17
Clean up table
maxschulz-COL Oct 18, 2023
322202b
Docstring
maxschulz-COL Oct 18, 2023
2db779d
Enable datatable properly
maxschulz-COL Oct 18, 2023
6cc33be
Schema
maxschulz-COL Oct 18, 2023
178559e
Demo callable Card.text
antonymilne Oct 19, 2023
81879eb
Revert "Demo callable Card.text"
antonymilne Oct 19, 2023
0f931fb
Improve table build
maxschulz-COL Oct 19, 2023
a4648cc
Merge branch 'feature/table_component_MS' of github.com:mckinsey/vizr…
maxschulz-COL Oct 19, 2023
96741c1
Showcase bound_arguments interference with kwargs
maxschulz-COL Oct 19, 2023
b4b4e6b
Warning to table fix
maxschulz-COL Oct 19, 2023
2a5db2b
Explanation for kwargs behaviour
maxschulz-COL Oct 19, 2023
997bed4
Fixing decorator again
maxschulz-COL Oct 19, 2023
7b6dadc
Delete metadata
maxschulz-COL Oct 19, 2023
4ca792a
add unit test for erronous binding behaviour
maxschulz-COL Oct 19, 2023
809a883
Add callable component private attribute
maxschulz-COL Oct 20, 2023
95e4d16
Add reusable validator
maxschulz-COL Oct 20, 2023
846b346
Add unit test to check on arguments
maxschulz-COL Oct 20, 2023
f2d4db4
Move theme switch back to page
maxschulz-COL Oct 20, 2023
459f037
lint
maxschulz-COL Oct 20, 2023
8d89d30
remove input property from graph
maxschulz-COL Oct 20, 2023
1da2585
Change _callable_component to string + add to page
maxschulz-COL Oct 20, 2023
3aefc06
Lint
maxschulz-COL Oct 20, 2023
fda85b0
Cleanup
maxschulz-COL Oct 23, 2023
95448f7
Move table to correct import path
maxschulz-COL Oct 23, 2023
4bf294e
Fix docs
maxschulz-COL Oct 23, 2023
60f3963
Add import path
maxschulz-COL Oct 23, 2023
fa055a5
Remove callable_component and add figure to table
maxschulz-COL Oct 23, 2023
5548fa2
Add unit tests
maxschulz-COL Oct 23, 2023
3329484
Lint
maxschulz-COL Oct 23, 2023
9131a25
Merge branch 'main' into feature/table_component_MS
maxschulz-COL Oct 23, 2023
ebc1d18
Fix tests
maxschulz-COL Oct 23, 2023
60c456d
Add documentation
maxschulz-COL Oct 23, 2023
c8cbdf7
Cleanup
maxschulz-COL Oct 23, 2023
7670eab
dropdown css bugfix
nadijagraca Oct 24, 2023
205b759
First round of PR comments
maxschulz-COL Oct 25, 2023
ab5d738
Table styling and default setting
maxschulz-COL Oct 25, 2023
ff3f9ff
Update docs and remove comments
maxschulz-COL Oct 25, 2023
7632ea7
adjusting tests to keep up with code change
maxschulz-COL Oct 25, 2023
369d63f
Lint
maxschulz-COL Oct 25, 2023
6ce1015
Fix overflow of table
huong-li-nguyen Oct 25, 2023
bc494e5
Merge branch 'feature/table_component_MS' of https://github.com/mckin…
huong-li-nguyen Oct 25, 2023
5f4c46b
PR comments
maxschulz-COL Oct 25, 2023
22c85c9
Merge branch 'main' into feature/table_component_MS
maxschulz-COL Oct 25, 2023
b75d2bf
Improve table docs
maxschulz-COL Oct 25, 2023
167b506
Merge branch 'main' into feature/table_component_MS
maxschulz-COL Oct 25, 2023
30aa168
Change test
maxschulz-COL Oct 25, 2023
5a6d6a8
Merge branch 'feature/table_component_MS' of github.com:mckinsey/vizr…
maxschulz-COL Oct 25, 2023
5669c12
Update vizro-core/src/vizro/models/_components/table.py
maxschulz-COL Oct 26, 2023
2b7d936
Update vizro-core/src/vizro/static/css/dropdown.css
maxschulz-COL Oct 26, 2023
7aa773e
Update vizro-core/src/vizro/static/css/table.css
maxschulz-COL Oct 26, 2023
c1c442f
Update vizro-core/src/vizro/static/css/dropdown.css
maxschulz-COL Oct 26, 2023
e2c346e
Update vizro-core/src/vizro/static/css/dropdown.css
maxschulz-COL Oct 26, 2023
3fb1155
Lint
maxschulz-COL Oct 26, 2023
2617b9b
Add custom table example
maxschulz-COL Oct 26, 2023
e39e603
PR comments
maxschulz-COL Oct 27, 2023
55a55f0
PR comments
maxschulz-COL Oct 27, 2023
ae5618a
Add dcc Loading to table
maxschulz-COL Oct 30, 2023
f55d46e
Revert changes
maxschulz-COL Oct 30, 2023
5e55817
Revert yaml changes
maxschulz-COL Oct 30, 2023
7ad5ce0
Merge branch 'main' into feature/table_component_MS
maxschulz-COL Oct 30, 2023
e950cbc
Small docs change
maxschulz-COL Oct 30, 2023
7eb7e90
Linting issue
maxschulz-COL Oct 30, 2023
151f795
Different solution try
maxschulz-COL Oct 30, 2023
51f492a
Schema
maxschulz-COL Oct 30, 2023
4fb900a
Lint schema using hatch rather than subprocess
maxschulz-COL Oct 30, 2023
91f9fb1
use subprocess for linting
maxschulz-COL Oct 30, 2023
c551e71
Changelog
maxschulz-COL Oct 30, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
583 changes: 72 additions & 511 deletions vizro-core/examples/default/app.py
maxschulz-COL marked this conversation as resolved.
Show resolved Hide resolved
maxschulz-COL marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

6 changes: 5 additions & 1 deletion vizro-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ dependencies = [
"numpy>=1.22.2", # not directly required, pinned by Snyk to avoid a vulnerability: https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-2321970
"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
"MarkupSafe" # required to sanitise user input
"MarkupSafe", # required to sanitise user input
# "d3_bar_chart @ file:///Users/huong_li_nguyen/Desktop/d3_bar_chart-0.0.1-py3-none-any.whl"
]
description = "Vizro is a package to facilitate visual analytics."
dynamic = ["version"]
Expand Down Expand Up @@ -64,3 +65,6 @@ skip_covered = true
branch = true
parallel = true
source_pkgs = ["vizro"]

[tool.hatch.metadata]
allow-direct-references = true
97 changes: 97 additions & 0 deletions vizro-core/src/scratch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# API for Table/React Charts
import d3_bar_chart
from dash import dash_table

import vizro.models as vm
from vizro.models.types import capture


# Dash Table
@capture("graph")
def dash_table(data_frame=None, **kwargs):
"""Custom table."""
return dash_table.DataTable(
data=data_frame.to_dict("records"), columns=[{"name": i, "id": i} for i in data_frame.columns], **kwargs
)


# D3 Visualisation
@capture("graph")
def d3_bar_chart(data_frame=None):
"""Custom table."""
return d3_bar_chart.D3BarChart(
data_frame=data_frame, # Custom D3 charts should have data_frame or other data arg
id="input",
value="my-value",
label="my-label",
)


# 1) Graph ---------------------
vm.Graph(figure=dash_table)
vm.Graph(figure=d3_bar_chart)

"""
Pro:
- One wrapper for all charts

Con:
- Returning different object based on figure -> dcc.Graph or html.Div
- Would contradict our current paradigm of naming models close to what is being used underneath

To consider
- dash keeps Graph and Table separate as well -> Graph enables all plotly.js powered charts, Table has different figure json schema and is therefore separate
- clickEvent property differs from the rest of px.charts --> needs to be refactored out of Graph
- post update calls differ (e.g. update_layout not available) --> needs to be refactored out of Graph
"""


# 2) Table & React --------------------
vm.Table2(data_frame, **kwargs)
vm.React(figure=d3_bar_chart)

"""
Pro:
- No need to create a custom chart, as datatable logic will live within the component (consistent with other components other than Graph)

Con:
- Need to enable many of the args from the dash datatable or enable users to provide *kwargs which deviates from all other models
- Would need to create a custom chart from Table instance anyways to enable more sophisticated functionality required by verticals
- Would need another wrapper for other react charts -> bad paradigm to create new model for each new react chart
"""

# 3) Table & React (currently preferred) --------------------
vm.Table(figure=dash_table)
vm.React(figure=d3_bar_chart)

"""
Pro:
- Users can customize their table to their liking and we don't maintain these args

Con:
- Users have to create a custom chart first and then provide it to the table
- Separate models for Table and other react components
- Table would only support one type of figure - does it then even deserve its own model?

To consider
- Does it make sense to have a figure argument if only one type of figure can be provided?
- Would we create our own custom react table at some point?
"""


# 4) ReactFigure --------------------
vm.React(figure=dash_table)
vm.React(figure=d3_bar_chart)

"""
Pro:
- Eventually gives us more freedom in creating custom js components
- Is only a thin wrapper and consistently returns an html.Div (one possible return type per model)

Con:
- Will have different clickEvent properties (that could be configured via custom actions though)

To consider:
- Should we actually extend the concept of this to vm.Html or vm.Container as essentially it just wraps the component inside an html.Div?
That model could then also be re-used for several other things? (Would mix lots of different concepts then though, so maybe bad idea)
"""
17 changes: 10 additions & 7 deletions vizro-core/src/vizro/actions/_actions_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,12 +192,15 @@ def _get_modified_page_charts(
parameters=ctds_parameters,
)

outputs = {
target: model_manager[target]( # type: ignore[index, operator]
data_frame=filtered_data[target],
**parameterized_config[target],
).update_layout(template="vizro_dark" if ctd_theme["value"] else "vizro_light")
for target in targets
}
outputs = {}
for target in targets:
if hasattr(model_manager[target], "_update_theme_call"):
outputs[target] = model_manager[target]._update_theme_call(
theme_bool=ctd_theme["value"], data_frame=filtered_data[target], **parameterized_config[target]
)
else:
outputs[target] = model_manager[target](data_frame=filtered_data[target], **parameterized_config[target])
# LN: needs to be refactored so plotly-independent or extendable - DONE
# MS: is the common theme to be targetable?

return outputs
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def _get_inputs_of_controls(action_id: ModelID, control_type: ControlType) -> Li
return [
State(
component_id=control.selector.id,
component_property="value",
component_property="value", # LN: needs to be refactored so that it is independent of implementation details
)
for control in page.controls
if isinstance(control, control_type)
Expand All @@ -107,7 +107,7 @@ def _get_inputs_of_chart_interactions(
return [
State(
component_id=_get_triggered_model(action_id=ModelID(str(action.id))).id,
component_property="clickData",
component_property="clickData", # LN: needs to be refactored so that it is independent of implementation details
)
for action in chart_interactions_on_page
]
Expand Down Expand Up @@ -157,13 +157,10 @@ def _get_action_callback_outputs(action_id: ModelID) -> Dict[str, Output]:
if action_function == _on_page_load.__wrapped__:
targets = _get_components_with_data(action_id=action_id)

return {
target: Output(
component_id=target,
component_property="figure",
allow_duplicate=True,
)
return { # LN: needs to be refactored so plotly-independent or extendable - DONE
target: model_manager[target]._get_action_callback_output()
for target in targets
if hasattr(model_manager[target], "_get_action_callback_output")
}


Expand Down
8 changes: 6 additions & 2 deletions vizro-core/src/vizro/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
# 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, Graph
from ._components import Card, Graph, Table, React
from ._components.form import Button, Checklist, Dropdown, RadioItems, RangeSlider, Slider
from ._controls import Filter, Parameter
from ._navigation.navigation import Navigation
from ._dashboard import Dashboard
from ._layout import Layout
from ._page import Page

Page.update_forward_refs(Button=Button, Card=Card, Filter=Filter, Graph=Graph, Parameter=Parameter)
Page.update_forward_refs(
Button=Button, Card=Card, Filter=Filter, Graph=Graph, Parameter=Parameter, Table=Table, React=React
)
Dashboard.update_forward_refs(Page=Page, Navigation=Navigation)

# Please keep alphabetically ordered
Expand All @@ -28,6 +30,8 @@
"Parameter",
"RadioItems",
"RangeSlider",
"React",
"Slider",
"VizroBaseModel",
"Table",
]
5 changes: 4 additions & 1 deletion vizro-core/src/vizro/models/_components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
from vizro.models._components.button import Button
from vizro.models._components.card import Card
from vizro.models._components.graph import Graph
from vizro.models._components.react import React
from vizro.models._components.table import Table
from vizro.models._components.table2 import Table2

__all__ = ["Button", "Card", "Graph"]
__all__ = ["Button", "Card", "Graph", "Table", "Table2", "React"]
29 changes: 26 additions & 3 deletions vizro-core/src/vizro/models/_components/graph.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import logging
from typing import List, Literal

from dash import dcc
from dash import Output, dcc
from plotly import graph_objects as go
from pydantic import Field, validator

Expand Down Expand Up @@ -37,7 +37,9 @@ class Graph(VizroBaseModel):
"""

type: Literal["graph"] = "graph"
figure: CapturedCallable = Field(..., import_path=px)
figure: CapturedCallable = Field(
..., import_path=px
) # LN: needs to be refactored so plotly-independent or extendable - No, as this is within the boundaries of Graph model
actions: List[Action] = []

# Re-used validators
Expand Down Expand Up @@ -78,7 +80,9 @@ def __call__(self, **kwargs):

# Remove top margin if title is provided
if fig.layout.title.text is None:
fig.update_layout(margin_t=24)
fig.update_layout(
margin_t=24
) # LN: needs to be refactored so plotly-independent or extendable: No, as this is within the boundaries of Graph model
return fig

# Convenience wrapper/syntactic sugar.
Expand Down Expand Up @@ -109,3 +113,22 @@ def build(self):
color="grey",
parent_className="chart_container",
)

def _update_theme_call(self, theme_bool, **kwargs):
"""Define __call__ method that includes theme update if applicable."""
return self.__call__(**kwargs).update_layout(template="vizro_dark" if theme_bool else "vizro_light")

def _get_action_callback_output(self):
return Output(
component_id=self.id,
component_property="figure",
allow_duplicate=True,
)

def _get_update_graph_theme_output(self):
"""Define output for theme selector callback."""
return Output(self.id, "figure", allow_duplicate=True)

# def _get_click_trigger_property(self):
# """Define trigger property for click interaction"""
# return "clickData"
80 changes: 80 additions & 0 deletions vizro-core/src/vizro/models/_components/react.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import logging
from typing import Callable, List, Literal

from dash import html
from pydantic import Field, validator

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._models_utils import _log_call
from vizro.models.types import CapturedCallable

logger = logging.getLogger(__name__)


class React(VizroBaseModel):
"""Wrapper for react components to visualize in dashboard.

Args:
type (Literal["react"]): Defaults to `"react"`.
figure (CapturedCallable): React object to be displayed.
# actions (List[Action]): List of the Action objects, that allows to
# configure app interactions, triggered by affecting this component.
"""

type: Literal["react"] = "react"
data: Callable = None
figure: CapturedCallable = Field(..., description="React component to be visualized on dashboard")
actions: List[Action] = []

# validator
set_actions = _action_validator_factory("active_cell") # type: ignore[pydantic-field]

@validator("figure")
def process_component_data_frame(cls, figure, values):
data_frame = figure["data_frame"]

# Enable running "iris" from the Python API and specification of "data_frame": "iris" through JSON.
# In these cases, data already exists in the data manager and just needs to be linked to the component.
if isinstance(data_frame, str):
data_manager._add_component(values["id"], data_frame)
return figure

# Standard case for df: pd.DataFrame.
# Extract dataframe from the captured function and put it into the data manager.
dataset_name = str(id(data_frame))

logger.debug("Adding data to data manager for Graph with id %s", values["id"])
# If the dataset already exists in the data manager then it's not a problem, it just means that we don't need
# to duplicate it. Just log the exception for debugging purposes.
try:
data_manager[dataset_name] = data_frame
except ValueError as exc:
logger.debug(exc)

data_manager._add_component(values["id"], dataset_name)

# No need to keep the data in the captured function any more so remove it to save memory.
# del figure["data_frame"]
return figure

# Convenience wrapper/syntactic sugar.
def __call__(self, **kwargs):
kwargs.setdefault("data_frame", data_manager._get_component_data(self.id)) # type: ignore[arg-type]
return self.figure(**kwargs)

# Convenience wrapper/syntactic sugar.
def __getitem__(self, arg_name: str):
# pydantic discriminated union validation seems to try Graph["type"], which throws an error unless we
# explicitly redirect it to the correct attribute.
if arg_name == "type":
return self.type
return self.figure[arg_name]

@_log_call
def build(self):
data = data_manager._get_component_data(self.id) # type: ignore
additional_args = self.figure._arguments.copy()
additional_args.pop("data_frame", None)
return html.Div(self.figure._function(data_frame=data, **additional_args), id=self.id)
Loading