diff --git a/vizro-core/examples/dev/app.py b/vizro-core/examples/dev/app.py index 1265ab20c..74ade57b7 100644 --- a/vizro-core/examples/dev/app.py +++ b/vizro-core/examples/dev/app.py @@ -820,13 +820,13 @@ def multiple_cards(data_frame: pd.DataFrame, n_rows: Optional[int] = 1) -> html. if __name__ == "__main__": app = Vizro().build(dashboard) - app.dash.layout.children.append( - dbc.NavLink( - ["Made with ", html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"), "vizro"], - href="https://github.com/mckinsey/vizro", - target="_blank", - className="anchor-container", - ) + + banner = dbc.NavLink( + ["Made with ", html.Img(src=get_asset_url("logo.svg"), id="banner", alt="Vizro logo"), "vizro"], + href="https://github.com/mckinsey/vizro", + target="_blank", + className="anchor-container", ) + app.dash.layout.children = [app.dash.layout.children, banner] server = app.dash.server app.run() diff --git a/vizro-core/examples/scratch_dev/app.py b/vizro-core/examples/scratch_dev/app.py index 56413e831..43c7c57e5 100644 --- a/vizro-core/examples/scratch_dev/app.py +++ b/vizro-core/examples/scratch_dev/app.py @@ -1,89 +1,24 @@ """Dev app to try things out.""" - from vizro import Vizro import vizro.models as vm import vizro.plotly.express as px -df = px.data.gapminder() -gapminder_data = ( - df.groupby(by=["continent", "year"]).agg({"lifeExp": "mean", "pop": "sum", "gdpPercap": "mean"}).reset_index() -) -first_page = vm.Page( - title="First Page", - layout=vm.Layout(grid=[[0, 0], [1, 2], [1, 2], [1, 2]]), - components=[ - vm.Card( - text=""" - # First dashboard page - This pages shows the inclusion of markdown text in a page and how components - can be structured using Layout. - """, - ), - vm.Graph( - id="box_cont", - figure=px.box( - gapminder_data, - x="continent", - y="lifeExp", - color="continent", - labels={"lifeExp": "Life Expectancy", "continent": "Continent"}, - ), - ), - vm.Graph( - id="line_gdp", - figure=px.line( - gapminder_data, - x="year", - y="gdpPercap", - color="continent", - labels={"year": "Year", "continent": "Continent", "gdpPercap": "GDP Per Cap"}, - ), - ), - ], - controls=[ - vm.Filter(column="continent", targets=["box_cont", "line_gdp"]), - ], -) +stocks = px.data.stocks(datetimes=True) -iris_data = px.data.iris() -second_page = vm.Page( - title="Second Page", +page = vm.Page( + title="Page", components=[ vm.Graph( - id="scatter_iris", - figure=px.scatter( - iris_data, - x="sepal_width", - y="sepal_length", - color="species", - color_discrete_map={"setosa": "#00b4ff", "versicolor": "#ff9222"}, - labels={"sepal_width": "Sepal Width", "sepal_length": "Sepal Length", "species": "Species"}, - ), - ), - vm.Graph( - id="hist_iris", - figure=px.histogram( - iris_data, - x="sepal_width", - color="species", - color_discrete_map={"setosa": "#00b4ff", "versicolor": "#ff9222"}, - labels={"sepal_width": "Sepal Width", "count": "Count", "species": "Species"}, - ), + figure=px.line(stocks, x="date", y="GOOG", title="Stocks Data"), ), ], controls=[ - vm.Parameter( - targets=["scatter_iris.color_discrete_map.virginica", "hist_iris.color_discrete_map.virginica"], - selector=vm.Dropdown(options=["#ff5267", "#3949ab"], multi=False, value="#3949ab", title="Color Virginica"), - ), - vm.Parameter( - targets=["scatter_iris.opacity"], - selector=vm.Slider(min=0, max=1, value=0.8, title="Opacity"), - ), + vm.Filter(column="GOOG"), + vm.Filter(column="date", selector=vm.DatePicker(title="Date Picker (Stocks - date)")), ], ) -dashboard = vm.Dashboard(pages=[first_page, second_page]) +dashboard = vm.Dashboard(pages=[page]) if __name__ == "__main__": - Vizro().build(dashboard).run() + Vizro().build(dashboard).run() \ No newline at end of file diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 1bf2b9577..11cce50b3 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -21,7 +21,7 @@ dependencies = [ "pandas>=2", "plotly>=5.12.0", "pydantic>=1.10.16", # must be synced with pre-commit mypy hook manually - "dash_mantine_components<0.13.0", # 0.13.0 is not compatible with 0.12, + "dash_mantine_components==0.15.1", # 0.13.0 is not compatible with 0.12, "flask_caching>=2", "wrapt>=1", "black", diff --git a/vizro-core/src/vizro/_vizro.py b/vizro-core/src/vizro/_vizro.py index f648d053f..f2fa41a27 100644 --- a/vizro-core/src/vizro/_vizro.py +++ b/vizro-core/src/vizro/_vizro.py @@ -8,6 +8,7 @@ from typing import TYPE_CHECKING, TypedDict, cast import dash +import dash_mantine_components as dmc import plotly.io as pio from dash.development.base_component import ComponentRegistry from flask_caching import SimpleCache @@ -17,6 +18,9 @@ from vizro.managers import data_manager, model_manager from vizro.models import Dashboard, Filter +# this can be removed when Dash uses React 18 as a default (likely V3.0 https://github.com/plotly/dash/pull/3093) +dash._dash_renderer._set_react_version("18.2.0") + logger = logging.getLogger(__name__) if TYPE_CHECKING: @@ -49,6 +53,13 @@ def __init__(self, **kwargs): use_pages=True, ) + # Ensure external_stylesheets is a list and append the additional stylesheet + external_stylesheets = self.dash.config.external_stylesheets + self.dash.config.external_stylesheets = ( + external_stylesheets if isinstance(external_stylesheets, list) else [external_stylesheets] + ) + self.dash.config.external_stylesheets.append(dmc.styles.DATES) + # When Vizro is used as a framework, we want to include the library and framework resources. # Dash serves resources in the order 1. external_stylesheets/scripts; 2. library resources from the # ComponentRegistry; 3. resources added by append_css/scripts. diff --git a/vizro-core/src/vizro/models/_components/form/date_picker.py b/vizro-core/src/vizro/models/_components/form/date_picker.py index b3dc416fd..d487ca9e6 100644 --- a/vizro-core/src/vizro/models/_components/form/date_picker.py +++ b/vizro-core/src/vizro/models/_components/form/date_picker.py @@ -1,7 +1,7 @@ from typing import Literal, Optional, Union import dash_mantine_components as dmc -from dash import ClientsideFunction, Input, Output, State, clientside_callback, dcc, html +from dash import html try: from pydantic.v1 import Field, PrivateAttr, validator @@ -9,7 +9,6 @@ from pydantic import Field, PrivateAttr, validator -import datetime from datetime import date import dash_bootstrap_components as dbc @@ -42,6 +41,8 @@ class DatePicker(VizroBaseModel): max: Optional[date] = Field(None, description="End date for date picker.") value: Optional[Union[list[date], date]] = Field(None, description="Default date for date picker") title: str = Field("", description="Title to be displayed.") + + # Could probably delete the `range` arg, but keeping it makes it backwards compatible range: bool = Field(True, description="Boolean flag for displaying range picker.") actions: list[Action] = [] @@ -57,48 +58,17 @@ def build(self): init_value = self.value or ([self.min, self.max] if self.range else self.min) # type: ignore[list-item] date_range_picker_kwargs = {"allowSingleDateInRange": True} if self.range else {} - output = [ - Output(self.id, "value"), - Output(f"{self.id}_input_store", "data"), - ] - inputs = [ - Input(self.id, "value"), - State(f"{self.id}_input_store", "data"), - ] - - clientside_callback( - ClientsideFunction(namespace="date_picker", function_name="update_date_picker_values"), - output=output, - inputs=inputs, - ) - # clientside callback is required as a workaround when the date-picker is overflowing its parent container - # if there is not enough space. Caused by another workaround for this issue: - # https://github.com/snehilvj/dash-mantine-components/issues/219 - clientside_callback( - ClientsideFunction(namespace="date_picker", function_name="update_date_picker_position"), - output=Output(self.id, "dropdownPosition"), - inputs=Input(self.id, "n_clicks"), - ) - - date_picker_class = dmc.DateRangePicker if self.range else dmc.DatePicker - - # dropdownPosition must be set to bottom-start as a workaround for issue: - # https://github.com/snehilvj/dash-mantine-components/issues/219 - # clearable must be set to False as a workaround for issue: - # https://github.com/snehilvj/dash-mantine-components/issues/212 - # maxDate must be increased by one day, and later on disabledDates must be set as maxDate + 1 day - # as a workaround for issue: https://github.com/snehilvj/dash-mantine-components/issues/230 - date_picker = date_picker_class( + date_picker = dmc.DatePickerInput( id=self.id, minDate=self.min, value=init_value, - maxDate=self.max + datetime.timedelta(days=1) if self.max else None, + maxDate=self.max, persistence=True, persistence_type="session", - dropdownPosition="bottom-start", - clearable=False, - disabledDates=self.max + datetime.timedelta(days=1) if self.max else None, + type="range" if self.range else "default", className="datepicker", + # removes the default red color for weekend days + styles={"day": {"color": "var(--mantine-color-text"}}, **date_range_picker_kwargs, ) @@ -106,6 +76,5 @@ def build(self): children=[ dbc.Label(children=self.title, html_for=self.id) if self.title else None, date_picker, - dcc.Store(id=f"{self.id}_input_store", storage_type="session", data=init_value), ], ) diff --git a/vizro-core/src/vizro/models/_dashboard.py b/vizro-core/src/vizro/models/_dashboard.py index b635b8e95..62e1f12be 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -8,6 +8,7 @@ import dash import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import plotly.io as pio from dash import ( ClientsideFunction, @@ -156,7 +157,7 @@ def build(self): State("collapsable-left-side", "is_open"), ) - return html.Div( + layout = html.Div( id="dashboard-container", children=[ html.Div(id="vizro_version", children=vizro.__version__, hidden=True), @@ -171,6 +172,11 @@ def build(self): dash.page_container, ], ) + return dmc.MantineProvider( + layout, + # Use the `theme` to style all Mantine components with a Vizro theme. For more info see https://www.dash-mantine-components.com/components/mantineprovider + theme = {"primaryColor": "gray"} + ) def _validate_logos(self): logo_img = self._infer_image(filename="logo") diff --git a/vizro-core/src/vizro/static/css/datepicker.css b/vizro-core/src/vizro/static/css/datepicker.css index 3e167d5d0..e69de29bb 100644 --- a/vizro-core/src/vizro/static/css/datepicker.css +++ b/vizro-core/src/vizro/static/css/datepicker.css @@ -1,109 +0,0 @@ -.datepicker .mantine-Input-wrapper { - font-family: unset; - height: 2rem; -} - -.datepicker .mantine-DateRangePicker-input, -.datepicker .mantine-DatePicker-input { - background-color: var(--field-enabled); - border: none; - border-radius: 0; - box-shadow: var(--elevation-0); - color: var(--text-secondary); - font-size: 0.875rem; - height: 2rem; - line-height: 1rem; - min-height: 2rem; - padding: 0 0.5rem; -} - -.datepicker .mantine-DateRangePicker-input:hover, -.datepicker .mantine-DatePicker-input:hover { - color: var(--text-primary); -} - -.datepicker .mantine-DateRangePicker-dropdown, -.datepicker .mantine-DatePicker-dropdown { - background: var(--field-enabled); - border: none; - border-radius: 0; - box-shadow: var(--elevation-1); - padding: 1rem 11px; /* 11px otherwise not aligned with controls */ -} - -.datepicker .mantine-UnstyledButton-root { - border-radius: 0; - color: var(--text-secondary); - font-family: unset; - font-weight: 400; -} - -.datepicker .mantine-UnstyledButton-root:hover { - background: var(--stateOverlays-hover); - color: var(--text-primary); -} - -.datepicker .mantine-DateRangePicker-weekday, -.datepicker .mantine-DatePicker-weekday { - color: var(--text-secondary); - font-family: unset; - padding: 0.5rem; -} - -.datepicker .mantine-DateRangePicker-cell, -.datepicker .mantine-DatePicker-cell { - border: none; -} - -.datepicker .mantine-DateRangePicker-day, -.datepicker .mantine-DatePicker-day { - background: var(--field-enabled); - border-radius: 0; - color: var(--text-secondary); - font-family: unset; -} - -.datepicker .mantine-DateRangePicker-day[data-outside], -.datepicker .mantine-DateRangePicker-day:disabled, -.datepicker .mantine-DatePicker-day[data-outside], -.datepicker .mantine-DatePicker-day:disabled, -.datepicker .mantine-UnstyledButton-root:disabled { - color: var(--text-disabled); -} - -.datepicker .mantine-DateRangePicker-day:hover, -.datepicker .mantine-DateRangePicker-day:focus-visible, -.datepicker .mantine-DatePicker-day:hover, -.datepicker .mantine-DatePicker-day:focus-visible { - background: var(--stateOverlays-hover); - border: none; - color: var(--text-primary); - outline: none; - text-decoration: none; -} - -.datepicker .mantine-DateRangePicker-day[data-in-range] { - background: var(--stateOverlays-selected); - color: var(--text-primary); -} - -.datepicker .mantine-DateRangePicker-day[data-selected], -.datepicker .mantine-DateRangePicker-yearPickerControlActive, -.datepicker .mantine-DateRangePicker-monthPickerControlActive, -.datepicker .mantine-DatePicker-day[data-selected], -.datepicker .mantine-DatePicker-yearPickerControlActive, -.datepicker .mantine-DatePicker-monthPickerControlActive { - background: var(--stateOverlays-selected-inverted); - color: var(--text-primary-inverted); - text-decoration: underline; -} - -.datepicker - .mantine-DateRangePicker-calendarHeader - .mantine-UnstyledButton-root:hover, -.datepicker - .mantine-DatePicker-calendarHeader - .mantine-UnstyledButton-root:hover { - background: transparent; - color: var(--text-primary); -} diff --git a/vizro-core/src/vizro/static/js/models/dashboard.js b/vizro-core/src/vizro/static/js/models/dashboard.js index e04084788..5a278e1ac 100644 --- a/vizro-core/src/vizro/static/js/models/dashboard.js +++ b/vizro-core/src/vizro/static/js/models/dashboard.js @@ -1,8 +1,10 @@ function update_dashboard_theme(theme_selector_checked) { - document.documentElement.setAttribute( - "data-bs-theme", - theme_selector_checked ? "light" : "dark", - ); + const theme = theme_selector_checked ? "light" : "dark"; + + // Update theme attributes for Bootstrap and Mantine + document.documentElement.setAttribute("data-bs-theme", theme); + document.documentElement.setAttribute("data-mantine-color-scheme", theme); + return window.dash_clientside.no_update; } diff --git a/vizro-core/src/vizro/static/js/models/date_picker.js b/vizro-core/src/vizro/static/js/models/date_picker.js deleted file mode 100644 index 1073acacb..000000000 --- a/vizro-core/src/vizro/static/js/models/date_picker.js +++ /dev/null @@ -1,27 +0,0 @@ -function update_date_picker_values(value, input_store) { - if ( - value === null || - dash_clientside.callback_context.triggered.length === 0 - ) { - return [input_store, input_store]; - } - return [value, value]; -} - -function update_date_picker_position(clicks) { - var element_id = window.dash_clientside.callback_context.inputs_list[0]["id"]; - var element = document.getElementById(element_id); - var rect = element.getBoundingClientRect(); - var position = - rect.y + 360 > window.innerHeight ? "top-start" : "bottom-start"; - - return position; -} - -window.dash_clientside = { - ...window.dash_clientside, - date_picker: { - update_date_picker_values: update_date_picker_values, - update_date_picker_position: update_date_picker_position, - }, -};