diff --git a/.github/images/toolkit_vizro_ai_fallback.png b/.github/images/toolkit_vizro_ai_fallback.png new file mode 100644 index 000000000..89a3ce018 Binary files /dev/null and b/.github/images/toolkit_vizro_ai_fallback.png differ diff --git a/.github/images/vizro_spash_teaser_fallback.png b/.github/images/vizro_spash_teaser_fallback.png new file mode 100644 index 000000000..f916f7312 Binary files /dev/null and b/.github/images/vizro_spash_teaser_fallback.png differ diff --git a/.github/workflows/checks-vizro-core.yml b/.github/workflows/checks-vizro-core.yml index 751b9647f..5af2bda02 100644 --- a/.github/workflows/checks-vizro-core.yml +++ b/.github/workflows/checks-vizro-core.yml @@ -44,6 +44,9 @@ jobs: - name: Check schema is up to date run: hatch run schema-check + - name: Check plotly template is up to date + run: hatch run templates-check + - name: Find changed files to see if changelog fragment needed id: changed-files if: ${{ github.event_name == 'pull_request' }} diff --git a/README.md b/README.md index 32834d3c4..81bb86422 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ - Vizro logo + Vizro logo #### Vizro is a low-code toolkit for building high-quality data visualization apps @@ -14,7 +14,10 @@ [Documentation](https://vizro.readthedocs.io/en/stable/) | [Get Started](https://vizro.readthedocs.io/en/stable/pages/tutorials/first_dashboard/) | [Vizro examples gallery](http://vizro.mckinsey.com/) - + + + Gif to demonstrate Vizro features +

@@ -120,7 +123,10 @@ Vizro-AI is a separate package (called `vizro_ai`) that extends Vizro to incorpo Visit the [Vizro-AI documentation](https://vizro.readthedocs.io/projects/vizro-ai/) for more details. - + + + Gif to demonstrate Vizro-AI + ## Installation and first steps diff --git a/vizro-ai/README.md b/vizro-ai/README.md index faed6c79a..880dc00de 100644 --- a/vizro-ai/README.md +++ b/vizro-ai/README.md @@ -4,7 +4,10 @@ [![Python version](https://img.shields.io/badge/python-3.9%20%7C%203.10%20%7C%203.11%20%7C%203.12-blue.svg)](https://pypi.org/project/vizro/) [![PyPI version](https://badge.fury.io/py/vizro_ai.svg)](https://badge.fury.io/py/vizro_ai) [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://github.com/mckinsey/vizro/blob/main/LICENSE.md) [![Documentation](https://readthedocs.org/projects/vizro-ai/badge/?version=latest)](https://vizro-ai.readthedocs.io/) [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/7858/badge)](https://www.bestpractices.dev/projects/7858) -Gif to demonstrate vizro-ai + + + Gif to demonstrate Vizro-AI +

diff --git a/vizro-ai/docs/assets/readme/readme_animation.gif b/vizro-ai/docs/assets/readme/readme_animation.gif deleted file mode 100644 index b7f8c7c56..000000000 Binary files a/vizro-ai/docs/assets/readme/readme_animation.gif and /dev/null differ diff --git a/vizro-ai/docs/index.md b/vizro-ai/docs/index.md index 9d6b190fb..b50b93ece 100644 --- a/vizro-ai/docs/index.md +++ b/vizro-ai/docs/index.md @@ -6,7 +6,7 @@ By using Vizro's themes, you can incorporate design best practices by default. I Even if you are an experienced data practitioner, Vizro-AI optimizes how you create visually appealing layouts to present detailed insights about your data. -Gif to demonstrate vizro-ai +Gif to demonstrate vizro-ai

diff --git a/vizro-ai/pyproject.toml b/vizro-ai/pyproject.toml index d623ecccb..d39791c76 100644 --- a/vizro-ai/pyproject.toml +++ b/vizro-ai/pyproject.toml @@ -70,7 +70,10 @@ filterwarnings = [ # Ignore deprecation warning until this is solved: https://github.com/plotly/dash/issues/2590: "ignore:HTTPResponse.getheader():DeprecationWarning", # Happens during dash_duo teardown in vizro_ai_ui tests. Not affecting functionality: - "ignore:Exception in thread" + "ignore:Exception in thread", + # Ignore deprecation warning as it comes from the plotly default template. In our templates `vizro_dark` and + # `vizro_light`, we do not use mapbox anymore, see: https://github.com/mckinsey/vizro/pull/974 + "ignore:.*scattermapbox.*is deprecated.*Use.*scattermap.*instead:DeprecationWarning" ] pythonpath = ["../tools/tests"] diff --git a/vizro-core/README.md b/vizro-core/README.md index 32834d3c4..81bb86422 100644 --- a/vizro-core/README.md +++ b/vizro-core/README.md @@ -5,7 +5,7 @@ - Vizro logo + Vizro logo #### Vizro is a low-code toolkit for building high-quality data visualization apps @@ -14,7 +14,10 @@ [Documentation](https://vizro.readthedocs.io/en/stable/) | [Get Started](https://vizro.readthedocs.io/en/stable/pages/tutorials/first_dashboard/) | [Vizro examples gallery](http://vizro.mckinsey.com/) - + + + Gif to demonstrate Vizro features +

@@ -120,7 +123,10 @@ Vizro-AI is a separate package (called `vizro_ai`) that extends Vizro to incorpo Visit the [Vizro-AI documentation](https://vizro.readthedocs.io/projects/vizro-ai/) for more details. - + + + Gif to demonstrate Vizro-AI + ## Installation and first steps diff --git a/vizro-core/changelog.d/20250123_140015_huong_li_nguyen_refactor_template_generation.md b/vizro-core/changelog.d/20250123_140015_huong_li_nguyen_refactor_template_generation.md new file mode 100644 index 000000000..7c0d58d4f --- /dev/null +++ b/vizro-core/changelog.d/20250123_140015_huong_li_nguyen_refactor_template_generation.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/changelog.d/20250128_164637_huong_li_nguyen_update_material_font.md b/vizro-core/changelog.d/20250128_164637_huong_li_nguyen_update_material_font.md new file mode 100644 index 000000000..e9e7e8f0c --- /dev/null +++ b/vizro-core/changelog.d/20250128_164637_huong_li_nguyen_update_material_font.md @@ -0,0 +1,48 @@ + + + + + + +### Changed + +- Update `material-symbols-outlined.wolff2` to include the latest icons. ([#972](https://github.com/mckinsey/vizro/pull/972)) + + + + + diff --git a/vizro-core/changelog.d/20250129_112526_huong_li_nguyen_check_plotly_update.md b/vizro-core/changelog.d/20250129_112526_huong_li_nguyen_check_plotly_update.md new file mode 100644 index 000000000..7c0d58d4f --- /dev/null +++ b/vizro-core/changelog.d/20250129_112526_huong_li_nguyen_check_plotly_update.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/docs/pages/user-guides/themes.md b/vizro-core/docs/pages/user-guides/themes.md index ac1d6fc7b..95abc053e 100644 --- a/vizro-core/docs/pages/user-guides/themes.md +++ b/vizro-core/docs/pages/user-guides/themes.md @@ -73,8 +73,16 @@ To change the theme to `vizro_light` for all charts, run: ```python import plotly.io as pio +import vizro.plotly.express as px pio.templates.default = "vizro_light" + +df = px.data.iris() +px.scatter_matrix( + df, + dimensions=["sepal_length", "sepal_width", "petal_length", "petal_width"], + color="species", +) ``` ### Set themes for selected charts diff --git a/vizro-core/examples/scratch_dev/app.py b/vizro-core/examples/scratch_dev/app.py index f29db239b..b1d65c651 100644 --- a/vizro-core/examples/scratch_dev/app.py +++ b/vizro-core/examples/scratch_dev/app.py @@ -1,26 +1,28 @@ """Dev app to try things out.""" -from vizro import Vizro import vizro.models as vm import vizro.plotly.express as px +from vizro import Vizro +from vizro.figures import kpi_card -from vizro.tables import dash_data_table - -gapminder = px.data.gapminder() +tips = px.data.tips +# Create a layout with five rows and four columns. The KPI card is positioned in the first cell, while the remaining cells are empty. page = vm.Page( - title="Page", + title="KPI card", + layout=vm.Layout(grid=[[0, 0, -1, -1]] + [[-1, -1, -1, -1]] * 2), components=[ - vm.Table( - figure=dash_data_table(data_frame=gapminder), - title="Gapminder Data Insights", + vm.Figure( + figure=kpi_card( # For more information, refer to the API reference for kpi_card + data_frame=tips, + value_column="tip", + value_format="${value:.2f}", + icon="folder_check", + title="KPI card I", + ) ) ], - controls=[ - vm.Filter(column="continent", selector=vm.Dropdown(value=["Europe"])), - vm.Filter(column="continent", selector=vm.Dropdown(value="Europe", multi=False)), - vm.Filter(column="continent", selector=vm.Checklist()), - ], + controls=[vm.Filter(column="day", selector=vm.RadioItems())], ) dashboard = vm.Dashboard(pages=[page]) diff --git a/vizro-core/hatch.toml b/vizro-core/hatch.toml index 3c1f031ab..f434a5f62 100644 --- a/vizro-core/hatch.toml +++ b/vizro-core/hatch.toml @@ -58,6 +58,11 @@ pypath = "python -c 'import sys; print(sys.executable)'" # Only run pre-commit hooks when schema is generated, not when it's checked. This keeps the check fast in CI. schema = ["python schemas/generate.py", '- pre-commit run --files="schemas/$(hatch version).json" > /dev/null'] schema-check = ["python schemas/generate.py --check"] +templates = [ + "python src/vizro/_themes/generate_plotly_templates.py", + '- pre-commit run --files src/vizro/_themes/vizro_dark.json src/vizro/_themes/vizro_light.json > /dev/null' +] +templates-check = ["python src/vizro/_themes/generate_plotly_templates.py --check"] # Note `hatch run test` currently fails due to interference between integration tests and unit tests. Ideally we would # fix this, but we don't actually use `hatch run test` anywhere right now. # See comments added in https://github.com/mckinsey/vizro/pull/444. diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 2da2d4566..367f12aff 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -54,7 +54,7 @@ exclude_lines = [ "if __name__ == .__main__.:", "if TYPE_CHECKING:" ] -fail_under = 92 +fail_under = 90 show_missing = true skip_covered = true @@ -81,7 +81,10 @@ filterwarnings = [ # Ignore warning for Pydantic v1 API and Python 3.13: "ignore:Failing to pass a value to the 'type_params' parameter of 'typing.ForwardRef._evaluate' is deprecated:DeprecationWarning", # Ignore deprecation warning until this is solved: https://github.com/plotly/dash/issues/2590: - "ignore:HTTPResponse.getheader():DeprecationWarning" + "ignore:HTTPResponse.getheader():DeprecationWarning", + # Ignore deprecation warning as it comes from the plotly default template. In our templates `vizro_dark` and + # `vizro_light`, we do not use mapbox anymore, see: https://github.com/mckinsey/vizro/pull/974 + "ignore:.*scattermapbox.*is deprecated.*Use.*scattermap.*instead:DeprecationWarning" ] norecursedirs = ["tests/tests_utils", "tests/js"] pythonpath = ["tests/tests_utils"] diff --git a/vizro-core/src/vizro/__init__.py b/vizro-core/src/vizro/__init__.py index 2933c315f..558b0f742 100644 --- a/vizro-core/src/vizro/__init__.py +++ b/vizro-core/src/vizro/__init__.py @@ -1,16 +1,19 @@ +import json import logging import os +from pathlib import Path import plotly.io as pio from dash.development.base_component import ComponentRegistry from ._constants import VIZRO_ASSETS_PATH -from ._themes import dark, light from ._vizro import Vizro, _make_resource_spec logging.basicConfig(level=os.getenv("VIZRO_LOG_LEVEL", "WARNING")) -pio.templates["vizro_dark"] = dark -pio.templates["vizro_light"] = light + +base_path = Path(__file__).parent / "_themes" +pio.templates["vizro_dark"] = json.loads((base_path / "vizro_dark.json").read_text()) +pio.templates["vizro_light"] = json.loads((base_path / "vizro_light.json").read_text()) __all__ = ["Vizro"] diff --git a/vizro-core/src/vizro/_themes/__init__.py b/vizro-core/src/vizro/_themes/__init__.py deleted file mode 100644 index a89133889..000000000 --- a/vizro-core/src/vizro/_themes/__init__.py +++ /dev/null @@ -1,6 +0,0 @@ -"""Init.""" - -from ._templates.template_dark import dark -from ._templates.template_light import light - -__all__ = ["dark", "light"] diff --git a/vizro-core/src/vizro/_themes/_color_values.py b/vizro-core/src/vizro/_themes/_color_values.py deleted file mode 100644 index b02412635..000000000 --- a/vizro-core/src/vizro/_themes/_color_values.py +++ /dev/null @@ -1,185 +0,0 @@ -"""Colors for plotly templates.""" - -COLORS = { - # GRID COLOR - var(--fill-subtle) / var(--bs-border-color) - "WHITE_12": "rgba(255, 255, 255, 0.1)", - "BLACK_12": "rgba(20, 23, 33, 0.1) ", - # AXIS COLOR - var(--fill-disabled) / var(--bs-tertiary-color) - "WHITE_30": "rgba(255, 255, 255, 0.30)", - "BLACK_30": "rgba(20, 23, 33, 0.30)", - # FONT COLOR SECONDARY - var(--text-secondary) / var(--bs-secondary) - "WHITE_55": "rgba(255, 255, 255, 0.6)", - "BLACK_55": "rgba(20, 23, 33, 0.6)", - # FONT COLOR PRIMARY - var(--text-primary) / var(--bs-primary) - "WHITE_85": "rgba(255, 255, 255, 0.88)", - "BLACK_85": "rgba(20, 23, 33, 0.88)", - # BG COLOR - var(--bs-body-bg), var(--surfaces-bg03) - dark, var(--surfaces-bg01) - light - "DARK_BG03": "#141721", - "Light_BG01": "#FFFFFF", - # OTHER - "GREY_30": "#C0C6CB", - "GREY_55": "#747F88", - # SEQUENCES - "DIVERGING_PURPLE_ORANGE": [ - "#0f237c", - "#2b3794", - "#464bab", - "#615ec2", - "#7c74d6", - "#958be4", - "#afa3f1", - "#cabcff", - "#dfd8fa", - "#f5f5f5", - "#f9d8ac", - "#feb85b", - "#f09b32", - "#db811e", - "#c76809", - "#b05000", - "#973a00", - "#7e2400", - "#640d00", - ], - "DIVERGING_ORANGE_TEAL": [ - "#640d00", - "#7e2400", - "#973a00", - "#b05000", - "#c76809", - "#db811e", - "#f09b32", - "#feb85b", - "#f9d8ac", - "#f5f5f5", - "#a5eae8", - "#46dad7", - "#32c2bf", - "#1daaa8", - "#099391", - "#007c7a", - "#006565", - "#005050", - "#003b3c", - ], - "DIVERGING_RED_CYAN": [ - "#7e000c", - "#9d1021", - "#bc1f37", - "#db2f4c", - "#ea536b", - "#f67486", - "#fe94a0", - "#fbb6be", - "#f8d6da", - "#E6E8EA", - "#afe7f9", - "#5bd6fe", - "#3bbef1", - "#24a6e1", - "#0d8ed1", - "#0077bd", - "#0061a4", - "#004c8c", - "#003875", - ], - "DIVERGING_YELLOW_BLUE": [ - "#fdc935", - "#fbbb34", - "#f5ae37", - "#eda23b", - "#e49640", - "#da8c45", - "#d0814a", - "#c5774e", - "#b96d53", - "#ad6457", - "#a15a5b", - "#94515e", - "#874962", - "#794166", - "#6a3969", - "#5a316d", - "#482b70", - "#312474", - "#021f77", - ], - "SEQUENTIAL_CYAN": [ - "#afe7f9", - "#5bd6fe", - "#3bbef1", - "#24a6e1", - "#0d8ed1", - "#0077bd", - "#0061a4", - "#004c8c", - "#003875", - ], - "SEQUENTIAL_BLUE": [ - "#dfdcff", - "#c3c1ff", - "#a7a7ff", - "#8a8df9", - "#7177e0", - "#5861c7", - "#3d4caf", - "#1c3997", - "#04247d", - ], - "SEQUENTIAL_ORANGE": [ - "#f9d8ac", - "#feb85b", - "#f09b32", - "#db811e", - "#c76809", - "#b05000", - "#973a00", - "#7e2400", - "#640d00", - ], - "SEQUENTIAL_TEAL": [ - "#aeebea", - "#54dfdb", - "#38c9c6", - "#25b4b1", - "#139f9c", - "#018a88", - "#007574", - "#006160", - "#004e4e", - ], - "SEQUENTIAL_GREEN": [ - "#b6ee82", - "#9dd46a", - "#84bb52", - "#6ba23b", - "#538a23", - "#3b7307", - "#215c00", - "#094600", - "#013000", - ], - "SEQUENTIAL_RED": [ - "#f8d6da", - "#fbb6be", - "#fe94a0", - "#f67486", - "#ea536b", - "#db2f4c", - "#bc1f37", - "#9d1021", - "#7e000c", - ], - "DISCRETE_10": [ - "#00b4ff", - "#ff9222", - "#3949ab", - "#ff5267", - "#08bdba", - "#fdc935", - "#689f38", - "#976fd1", - "#f781bf", - "#52733e", - ], -} diff --git a/vizro-core/src/vizro/_themes/_colors.py b/vizro-core/src/vizro/_themes/_colors.py new file mode 100644 index 000000000..beb176297 --- /dev/null +++ b/vizro-core/src/vizro/_themes/_colors.py @@ -0,0 +1,168 @@ +"""Color sequences by Vizro.""" + + +def get_colors(): + return { + "DIVERGING_PURPLE_ORANGE": [ + "#0f237c", + "#2b3794", + "#464bab", + "#615ec2", + "#7c74d6", + "#958be4", + "#afa3f1", + "#cabcff", + "#dfd8fa", + "#f5f5f5", + "#f9d8ac", + "#feb85b", + "#f09b32", + "#db811e", + "#c76809", + "#b05000", + "#973a00", + "#7e2400", + "#640d00", + ], + "DIVERGING_ORANGE_TEAL": [ + "#640d00", + "#7e2400", + "#973a00", + "#b05000", + "#c76809", + "#db811e", + "#f09b32", + "#feb85b", + "#f9d8ac", + "#f5f5f5", + "#a5eae8", + "#46dad7", + "#32c2bf", + "#1daaa8", + "#099391", + "#007c7a", + "#006565", + "#005050", + "#003b3c", + ], + "DIVERGING_RED_CYAN": [ + "#7e000c", + "#9d1021", + "#bc1f37", + "#db2f4c", + "#ea536b", + "#f67486", + "#fe94a0", + "#fbb6be", + "#f8d6da", + "#E6E8EA", + "#afe7f9", + "#5bd6fe", + "#3bbef1", + "#24a6e1", + "#0d8ed1", + "#0077bd", + "#0061a4", + "#004c8c", + "#003875", + ], + "DIVERGING_YELLOW_BLUE": [ + "#fdc935", + "#fbbb34", + "#f5ae37", + "#eda23b", + "#e49640", + "#da8c45", + "#d0814a", + "#c5774e", + "#b96d53", + "#ad6457", + "#a15a5b", + "#94515e", + "#874962", + "#794166", + "#6a3969", + "#5a316d", + "#482b70", + "#312474", + "#021f77", + ], + "SEQUENTIAL_CYAN": [ + "#afe7f9", + "#5bd6fe", + "#3bbef1", + "#24a6e1", + "#0d8ed1", + "#0077bd", + "#0061a4", + "#004c8c", + "#003875", + ], + "SEQUENTIAL_BLUE": [ + "#dfdcff", + "#c3c1ff", + "#a7a7ff", + "#8a8df9", + "#7177e0", + "#5861c7", + "#3d4caf", + "#1c3997", + "#04247d", + ], + "SEQUENTIAL_ORANGE": [ + "#f9d8ac", + "#feb85b", + "#f09b32", + "#db811e", + "#c76809", + "#b05000", + "#973a00", + "#7e2400", + "#640d00", + ], + "SEQUENTIAL_TEAL": [ + "#aeebea", + "#54dfdb", + "#38c9c6", + "#25b4b1", + "#139f9c", + "#018a88", + "#007574", + "#006160", + "#004e4e", + ], + "SEQUENTIAL_GREEN": [ + "#b6ee82", + "#9dd46a", + "#84bb52", + "#6ba23b", + "#538a23", + "#3b7307", + "#215c00", + "#094600", + "#013000", + ], + "SEQUENTIAL_RED": [ + "#f8d6da", + "#fbb6be", + "#fe94a0", + "#f67486", + "#ea536b", + "#db2f4c", + "#bc1f37", + "#9d1021", + "#7e000c", + ], + "DISCRETE_10": [ + "#00b4ff", + "#ff9222", + "#3949ab", + "#ff5267", + "#08bdba", + "#fdc935", + "#689f38", + "#976fd1", + "#f781bf", + "#52733e", + ], + } diff --git a/vizro-core/src/vizro/_themes/_templates/common_values.py b/vizro-core/src/vizro/_themes/_common_template.py similarity index 88% rename from vizro-core/src/vizro/_themes/_templates/common_values.py rename to vizro-core/src/vizro/_themes/_common_template.py index 8df14ca85..7dd9ba082 100644 --- a/vizro-core/src/vizro/_themes/_templates/common_values.py +++ b/vizro-core/src/vizro/_themes/_common_template.py @@ -2,23 +2,17 @@ from plotly import graph_objects as go -from vizro._themes._color_values import COLORS +from vizro._themes._colors import get_colors -def create_template_common(): - """Create general themed plotly template. - - Returns: - ------- - A plotly template object containing the general theme - with optional keys specified here: - https://plotly.com/python/reference/layout/ +def create_template_common() -> go.layout.Template: + """Creates template with common values for dark and light theme. + Returns: A plotly template object, see https://plotly.com/python/reference/layout/. """ + COLORS = get_colors() template_common = go.layout.Template() - template_common.layout = go.Layout( - # LAYOUT font_family="Inter, sans-serif, Arial", font_size=14, title_font_size=20, @@ -64,7 +58,6 @@ def create_template_common(): bargroupgap=0.1, uniformtext_minsize=12, uniformtext_mode="hide", - # X AXIS xaxis_visible=True, xaxis_title_font_size=16, xaxis_title_standoff=8, @@ -79,7 +72,6 @@ def create_template_common(): xaxis_layer="below traces", xaxis_linewidth=1, xaxis_zeroline=False, - # Y AXIS yaxis_visible=True, yaxis_title_font_size=16, yaxis_title_standoff=8, @@ -94,6 +86,7 @@ def create_template_common(): yaxis_layer="below traces", yaxis_linewidth=1, yaxis_zeroline=False, + annotationdefaults_showarrow=False, + annotationdefaults_font_size=14, ) - return template_common diff --git a/vizro-core/src/vizro/_themes/_templates/__init__.py b/vizro-core/src/vizro/_themes/_templates/__init__.py deleted file mode 100644 index 14e8999c9..000000000 --- a/vizro-core/src/vizro/_themes/_templates/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Init.""" diff --git a/vizro-core/src/vizro/_themes/_templates/template_dark.py b/vizro-core/src/vizro/_themes/_templates/template_dark.py deleted file mode 100644 index 3fe02f3a6..000000000 --- a/vizro-core/src/vizro/_themes/_templates/template_dark.py +++ /dev/null @@ -1,98 +0,0 @@ -"""Dark themed plotly template.""" - -from plotly import graph_objects as go -from plotly.graph_objs.layout._template import Template - -from vizro._themes._color_values import COLORS -from vizro._themes._templates.common_values import create_template_common - -# VARIABLES -FONT_COLOR_PRIMARY = COLORS["WHITE_85"] -FONT_COLOR_SECONDARY = COLORS["WHITE_55"] -BG_COLOR = COLORS["DARK_BG03"] -GRID_COLOR = COLORS["WHITE_12"] -AXIS_COLOR = COLORS["WHITE_30"] - - -def create_template_dark() -> Template: - """Create dark themed plotly template. - - Returns: - A plotly template object containing the dark theme - - """ - template_dark = create_template_common() - - # LAYOUT - template_dark["layout"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["legend"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["legend"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["paper_bgcolor"] = BG_COLOR - template_dark["layout"]["plot_bgcolor"] = BG_COLOR - template_dark["layout"]["geo"]["bgcolor"] = BG_COLOR - template_dark["layout"]["geo"]["lakecolor"] = BG_COLOR - template_dark["layout"]["geo"]["landcolor"] = BG_COLOR - template_dark["layout"]["polar"]["bgcolor"] = BG_COLOR - template_dark["layout"]["polar"]["angularaxis"]["gridcolor"] = GRID_COLOR - template_dark["layout"]["polar"]["angularaxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["polar"]["radialaxis"]["gridcolor"] = GRID_COLOR - template_dark["layout"]["polar"]["radialaxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["ternary"]["bgcolor"] = BG_COLOR - template_dark["layout"]["ternary"]["aaxis"]["gridcolor"] = GRID_COLOR - template_dark["layout"]["ternary"]["aaxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["ternary"]["baxis"]["gridcolor"] = GRID_COLOR - template_dark["layout"]["ternary"]["baxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["ternary"]["caxis"]["gridcolor"] = GRID_COLOR - template_dark["layout"]["ternary"]["caxis"]["linecolor"] = AXIS_COLOR - if "map" in template_dark["layout"]: - # "map" only available in plotly>=5.24.0, will replace "mapbox" soon. Until then, we need to set both. - # We need the if statement here in case the user is using an older version of plotly. - template_dark["layout"]["map"]["style"] = "carto-darkmatter" - template_dark["layout"]["mapbox"]["style"] = "carto-darkmatter" - template_dark["layout"]["coloraxis"]["colorbar"]["tickcolor"] = AXIS_COLOR - template_dark["layout"]["coloraxis"]["colorbar"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_dark["layout"]["coloraxis"]["colorbar"]["title"]["font"]["color"] = FONT_COLOR_SECONDARY - - # X AXIS - template_dark["layout"]["xaxis"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["xaxis"]["tickcolor"] = AXIS_COLOR - template_dark["layout"]["xaxis"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_dark["layout"]["xaxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["xaxis"]["gridcolor"] = GRID_COLOR - - # Y AXIS - template_dark["layout"]["yaxis"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_dark["layout"]["yaxis"]["tickcolor"] = AXIS_COLOR - template_dark["layout"]["yaxis"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_dark["layout"]["yaxis"]["linecolor"] = AXIS_COLOR - template_dark["layout"]["yaxis"]["gridcolor"] = GRID_COLOR - - # ANNOTATIONS - template_dark["layout"]["annotationdefaults"] = { - "font": {"color": FONT_COLOR_PRIMARY, "size": 14}, - "showarrow": False, - } - - # CHART TYPES - template_dark.data.bar = [ - go.Bar( - # This hides the border lines in a bar chart created from unaggregated data. - marker={"line": {"color": template_dark.layout.paper_bgcolor}}, - ) - ] - - template_dark.data.waterfall = [ - go.Waterfall( - decreasing={"marker": {"color": COLORS["DISCRETE_10"][1]}}, - increasing={"marker": {"color": COLORS["DISCRETE_10"][0]}}, - totals={"marker": {"color": COLORS["GREY_55"]}}, - textfont_color=template_dark.layout.title.font.color, - textposition="outside", - connector={"line": {"color": template_dark.layout.xaxis.tickcolor, "width": 1}}, - ) - ] - return template_dark - - -dark = create_template_dark() diff --git a/vizro-core/src/vizro/_themes/_templates/template_light.py b/vizro-core/src/vizro/_themes/_templates/template_light.py deleted file mode 100644 index ed4deb12e..000000000 --- a/vizro-core/src/vizro/_themes/_templates/template_light.py +++ /dev/null @@ -1,100 +0,0 @@ -"""Light themed plotly template.""" - -from plotly import graph_objects as go -from plotly.graph_objs.layout._template import Template - -from vizro._themes._color_values import COLORS -from vizro._themes._templates.common_values import create_template_common - -# VARIABLES -FONT_COLOR_PRIMARY = COLORS["BLACK_85"] -FONT_COLOR_SECONDARY = COLORS["BLACK_55"] -BG_COLOR = COLORS["Light_BG01"] -GRID_COLOR = COLORS["BLACK_12"] -AXIS_COLOR = COLORS["BLACK_30"] - - -def create_template_light() -> Template: - """Create light themed plotly template. - - Returns: - ------- - A plotly template object containing the light theme - - """ - template_light = create_template_common() - - # LAYOUT - template_light["layout"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["legend"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["legend"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["paper_bgcolor"] = BG_COLOR - template_light["layout"]["plot_bgcolor"] = BG_COLOR - template_light["layout"]["geo"]["bgcolor"] = BG_COLOR - template_light["layout"]["geo"]["lakecolor"] = BG_COLOR - template_light["layout"]["geo"]["landcolor"] = BG_COLOR - template_light["layout"]["polar"]["bgcolor"] = BG_COLOR - template_light["layout"]["polar"]["angularaxis"]["gridcolor"] = GRID_COLOR - template_light["layout"]["polar"]["angularaxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["polar"]["radialaxis"]["gridcolor"] = GRID_COLOR - template_light["layout"]["polar"]["radialaxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["ternary"]["bgcolor"] = BG_COLOR - template_light["layout"]["ternary"]["aaxis"]["gridcolor"] = GRID_COLOR - template_light["layout"]["ternary"]["aaxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["ternary"]["baxis"]["gridcolor"] = GRID_COLOR - template_light["layout"]["ternary"]["baxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["ternary"]["caxis"]["gridcolor"] = GRID_COLOR - template_light["layout"]["ternary"]["caxis"]["linecolor"] = AXIS_COLOR - if "map" in template_light["layout"]: - # "map" only available in plotly>=5.24.0, will replace "mapbox" soon. Until then, we need to set both. - # We need the if statement here in case the user is using an older version of plotly. - template_light["layout"]["map"]["style"] = "carto-positron" - template_light["layout"]["mapbox"]["style"] = "carto-positron" - template_light["layout"]["coloraxis"]["colorbar"]["tickcolor"] = AXIS_COLOR - template_light["layout"]["coloraxis"]["colorbar"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_light["layout"]["coloraxis"]["colorbar"]["title"]["font"]["color"] = FONT_COLOR_SECONDARY - - # X AXIS - template_light["layout"]["xaxis"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["xaxis"]["tickcolor"] = AXIS_COLOR - template_light["layout"]["xaxis"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_light["layout"]["xaxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["xaxis"]["gridcolor"] = GRID_COLOR - - # Y AXIS - template_light["layout"]["yaxis"]["title"]["font"]["color"] = FONT_COLOR_PRIMARY - template_light["layout"]["yaxis"]["tickcolor"] = AXIS_COLOR - template_light["layout"]["yaxis"]["tickfont"]["color"] = FONT_COLOR_SECONDARY - template_light["layout"]["yaxis"]["linecolor"] = AXIS_COLOR - template_light["layout"]["yaxis"]["gridcolor"] = GRID_COLOR - - # ANNOTATIONS - template_light["layout"]["annotationdefaults"] = { - "font": {"color": FONT_COLOR_PRIMARY, "size": 14}, - "showarrow": False, - } - - # CHART TYPES - template_light.data.bar = [ - go.Bar( - # This hides the border lines in a bar chart created from unaggregated data. - marker={"line": {"color": template_light.layout.paper_bgcolor}}, - ) - ] - - template_light.data.waterfall = [ - go.Waterfall( - decreasing={"marker": {"color": COLORS["DISCRETE_10"][1]}}, - increasing={"marker": {"color": COLORS["DISCRETE_10"][0]}}, - totals={"marker": {"color": COLORS["GREY_30"]}}, - textfont_color=template_light.layout.title.font.color, - textposition="outside", - connector={"line": {"color": template_light.layout.xaxis.tickcolor, "width": 1}}, - ) - ] - - return template_light - - -light = create_template_light() diff --git a/vizro-core/src/vizro/_themes/generate_plotly_templates.py b/vizro-core/src/vizro/_themes/generate_plotly_templates.py new file mode 100644 index 000000000..1add3b5b1 --- /dev/null +++ b/vizro-core/src/vizro/_themes/generate_plotly_templates.py @@ -0,0 +1,129 @@ +"""Generates Plotly JSON templates for the dark and light themes.""" + +import argparse +import json +import re +import sys +from pathlib import Path +from typing import Optional + +from plotly import graph_objects as go +from plotly.utils import PlotlyJSONEncoder + +from vizro._themes._colors import get_colors +from vizro._themes._common_template import create_template_common + +THEMES_FOLDER = Path(__file__).parent +CSS_PATH = THEMES_FOLDER.parent / "static/css/vizro-bootstrap.min.css" +VARIABLES = ["--bs-primary", "--bs-secondary", "--bs-tertiary-color", "--bs-border-color", "--bs-body-bg"] + + +def _extract_last_two_occurrences(variable: str, css_content: str) -> tuple[Optional[str], Optional[str]]: + """Extracts the last two occurrences of a variable from the CSS content. + + Within the `vizro-bootstrap.min.css` file, variables appear multiple times: initially from the default Bootstrap + values, followed by the dark theme, and lastly the light theme. We are interested in the final two occurrences, + as these represent the values for our dark and light themes. + """ + matches = re.findall(rf"{variable}:\s*([^;]+);", css_content) + if len(matches) >= 2: # noqa: PLR2004 + return matches[-2], matches[-1] + return None, None + + +def extract_bs_variables_from_css(variables: list[str], css_content: str) -> tuple[dict[str, str], dict[str, str]]: + """Extract the last two occurrences for each variable in the CSS file.""" + extracted_dark = {} + extracted_light = {} + + for variable in variables: + dark_value, light_value = _extract_last_two_occurrences(variable, css_content) + cleaned_variable = variable.replace("--", "").upper() + if dark_value and light_value: + extracted_dark[cleaned_variable] = dark_value + extracted_light[cleaned_variable] = light_value + + return extracted_dark, extracted_light + + +def generate_json_template(extracted_values: dict[str, str]) -> go.layout.Template: + """Generates the Plotly JSON chart template.""" + FONT_COLOR_PRIMARY = extracted_values["BS-PRIMARY"] + BG_COLOR = extracted_values["BS-BODY-BG"] + FONT_COLOR_SECONDARY = extracted_values["BS-SECONDARY"] + GRID_COLOR = extracted_values["BS-BORDER-COLOR"] + AXIS_COLOR = extracted_values["BS-TERTIARY-COLOR"] + + # Apply common values + COLORS = get_colors() + template = create_template_common() + layout = template.layout + layout.update( + font_color=FONT_COLOR_PRIMARY, + title_font_color=FONT_COLOR_PRIMARY, + legend_font_color=FONT_COLOR_PRIMARY, + legend_title_font_color=FONT_COLOR_PRIMARY, + paper_bgcolor=BG_COLOR, + plot_bgcolor=BG_COLOR, + geo_bgcolor=BG_COLOR, + geo_lakecolor=BG_COLOR, + geo_landcolor=BG_COLOR, + polar_bgcolor=BG_COLOR, + polar_angularaxis_gridcolor=GRID_COLOR, + polar_angularaxis_linecolor=AXIS_COLOR, + polar_radialaxis_gridcolor=GRID_COLOR, + polar_radialaxis_linecolor=AXIS_COLOR, + ternary_bgcolor=BG_COLOR, + ternary_aaxis_gridcolor=GRID_COLOR, + ternary_aaxis_linecolor=AXIS_COLOR, + ternary_baxis_gridcolor=GRID_COLOR, + ternary_baxis_linecolor=AXIS_COLOR, + ternary_caxis_gridcolor=GRID_COLOR, + ternary_caxis_linecolor=AXIS_COLOR, + coloraxis_colorbar_tickcolor=AXIS_COLOR, + coloraxis_colorbar_tickfont_color=FONT_COLOR_SECONDARY, + coloraxis_colorbar_title_font_color=FONT_COLOR_SECONDARY, + xaxis_title_font_color=FONT_COLOR_PRIMARY, + xaxis_tickcolor=AXIS_COLOR, + xaxis_tickfont_color=FONT_COLOR_SECONDARY, + xaxis_linecolor=AXIS_COLOR, + xaxis_gridcolor=GRID_COLOR, + yaxis_title_font_color=FONT_COLOR_PRIMARY, + yaxis_tickcolor=AXIS_COLOR, + yaxis_tickfont_color=FONT_COLOR_SECONDARY, + yaxis_linecolor=AXIS_COLOR, + yaxis_gridcolor=GRID_COLOR, + annotationdefaults_font_color=FONT_COLOR_PRIMARY, + ) + template.data.bar = [go.Bar(marker_line_color=BG_COLOR)] + template.data.waterfall = [ + go.Waterfall( + decreasing={"marker": {"color": COLORS["DISCRETE_10"][1]}}, + increasing={"marker": {"color": COLORS["DISCRETE_10"][0]}}, + totals={"marker": {"color": "grey"}}, + textfont_color=FONT_COLOR_PRIMARY, + textposition="outside", + connector={"line": {"color": AXIS_COLOR, "width": 1}}, + ) + ] + return template + + +if __name__ == "__main__": + extracted_dark, extracted_light = extract_bs_variables_from_css(VARIABLES, CSS_PATH.read_text()) + template_dark = generate_json_template(extracted_dark) + template_light = generate_json_template(extracted_light) + + parser = argparse.ArgumentParser(description="Generate JSON plotly templates.") + parser.add_argument("--check", help="check plotly templates are up to date", action="store_true") + args = parser.parse_args() + + for generated_template, file_name in zip([template_dark, template_light], ["vizro_dark.json", "vizro_light.json"]): + existing_template_path = Path(f"{THEMES_FOLDER}/{file_name}") + existing_template = json.loads(existing_template_path.read_text()) + if args.check: + if existing_template != generated_template.to_plotly_json(): + sys.exit(f"Chart template `{file_name}` is out of date. Run `hatch run templates` to update it.") + print(f"Chart template `{file_name}` is up to date 🎉") # noqa: T201 + else: + existing_template_path.write_text(json.dumps(generated_template, indent=4, cls=PlotlyJSONEncoder)) diff --git a/vizro-core/src/vizro/_themes/_templates/template_dashboard_overrides.py b/vizro-core/src/vizro/_themes/template_dashboard_overrides.py similarity index 100% rename from vizro-core/src/vizro/_themes/_templates/template_dashboard_overrides.py rename to vizro-core/src/vizro/_themes/template_dashboard_overrides.py diff --git a/vizro-core/src/vizro/_themes/vizro_dark.json b/vizro-core/src/vizro/_themes/vizro_dark.json new file mode 100644 index 000000000..e7287b117 --- /dev/null +++ b/vizro-core/src/vizro/_themes/vizro_dark.json @@ -0,0 +1,272 @@ +{ + "layout": { + "annotationdefaults": { + "font": { + "size": 14, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "showarrow": false + }, + "bargroupgap": 0.1, + "coloraxis": { + "autocolorscale": false, + "colorbar": { + "outlinewidth": 0, + "showticklabels": true, + "thickness": 20, + "tickfont": { + "size": 14, + "color": "rgba(255, 255, 255, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 14, + "color": "rgba(255, 255, 255, 0.6)" + } + }, + "tickcolor": "rgba(255, 255, 255, 0.3019607843)" + } + }, + "colorscale": { + "diverging": [ + [0.0, "#7e000c"], + [0.05555555555555555, "#9d1021"], + [0.1111111111111111, "#bc1f37"], + [0.16666666666666666, "#db2f4c"], + [0.2222222222222222, "#ea536b"], + [0.2777777777777778, "#f67486"], + [0.3333333333333333, "#fe94a0"], + [0.3888888888888889, "#fbb6be"], + [0.4444444444444444, "#f8d6da"], + [0.5, "#E6E8EA"], + [0.5555555555555556, "#afe7f9"], + [0.6111111111111112, "#5bd6fe"], + [0.6666666666666666, "#3bbef1"], + [0.7222222222222222, "#24a6e1"], + [0.7777777777777778, "#0d8ed1"], + [0.8333333333333334, "#0077bd"], + [0.8888888888888888, "#0061a4"], + [0.9444444444444444, "#004c8c"], + [1.0, "#003875"] + ], + "sequential": [ + [0.0, "#afe7f9"], + [0.125, "#5bd6fe"], + [0.25, "#3bbef1"], + [0.375, "#24a6e1"], + [0.5, "#0d8ed1"], + [0.625, "#0077bd"], + [0.75, "#0061a4"], + [0.875, "#004c8c"], + [1.0, "#003875"] + ], + "sequentialminus": [ + [0.0, "#7e000c"], + [0.125, "#9d1021"], + [0.25, "#bc1f37"], + [0.375, "#db2f4c"], + [0.5, "#ea536b"], + [0.625, "#f67486"], + [0.75, "#fe94a0"], + [0.875, "#fbb6be"], + [1.0, "#f8d6da"] + ] + }, + "colorway": [ + "#00b4ff", + "#ff9222", + "#3949ab", + "#ff5267", + "#08bdba", + "#fdc935", + "#689f38", + "#976fd1", + "#f781bf", + "#52733e" + ], + "font": { + "family": "Inter, sans-serif, Arial", + "size": 14, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "legend": { + "bgcolor": "rgba(0,0,0,0)", + "font": { + "size": 14, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "orientation": "h", + "title": { + "font": { + "size": 14, + "color": "rgba(255, 255, 255, 0.8784313725)" + } + }, + "y": -0.2 + }, + "margin": { + "autoexpand": true, + "b": 64, + "l": 80, + "pad": 0, + "r": 24, + "t": 64 + }, + "showlegend": true, + "title": { + "font": { + "size": 20, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "pad": { + "b": 0, + "l": 24, + "r": 24, + "t": 24 + }, + "x": 0, + "xanchor": "left", + "xref": "container", + "y": 1, + "yanchor": "top", + "yref": "container" + }, + "uniformtext": { + "minsize": 12, + "mode": "hide" + }, + "xaxis": { + "automargin": true, + "layer": "below traces", + "linewidth": 1, + "showline": true, + "showticklabels": true, + "tickfont": { + "size": 14, + "color": "rgba(255, 255, 255, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 16, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "standoff": 8 + }, + "visible": true, + "zeroline": false, + "tickcolor": "rgba(255, 255, 255, 0.3019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)", + "gridcolor": "rgba(255, 255, 255, 0.1019607843)" + }, + "yaxis": { + "automargin": true, + "layer": "below traces", + "linewidth": 1, + "showline": false, + "showticklabels": true, + "tickfont": { + "size": 14, + "color": "rgba(255, 255, 255, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 16, + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "standoff": 8 + }, + "visible": true, + "zeroline": false, + "tickcolor": "rgba(255, 255, 255, 0.3019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)", + "gridcolor": "rgba(255, 255, 255, 0.1019607843)" + }, + "paper_bgcolor": "#141721", + "plot_bgcolor": "#141721", + "geo": { + "bgcolor": "#141721", + "lakecolor": "#141721", + "landcolor": "#141721" + }, + "polar": { + "bgcolor": "#141721", + "angularaxis": { + "gridcolor": "rgba(255, 255, 255, 0.1019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)" + }, + "radialaxis": { + "gridcolor": "rgba(255, 255, 255, 0.1019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)" + } + }, + "ternary": { + "bgcolor": "#141721", + "aaxis": { + "gridcolor": "rgba(255, 255, 255, 0.1019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)" + }, + "baxis": { + "gridcolor": "rgba(255, 255, 255, 0.1019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)" + }, + "caxis": { + "gridcolor": "rgba(255, 255, 255, 0.1019607843)", + "linecolor": "rgba(255, 255, 255, 0.3019607843)" + } + } + }, + "data": { + "bar": [ + { + "marker": { + "line": { + "color": "#141721" + } + }, + "type": "bar" + } + ], + "waterfall": [ + { + "connector": { + "line": { + "color": "rgba(255, 255, 255, 0.3019607843)", + "width": 1 + } + }, + "decreasing": { + "marker": { + "color": "#ff9222" + } + }, + "increasing": { + "marker": { + "color": "#00b4ff" + } + }, + "textfont": { + "color": "rgba(255, 255, 255, 0.8784313725)" + }, + "textposition": "outside", + "totals": { + "marker": { + "color": "grey" + } + }, + "type": "waterfall" + } + ] + } +} diff --git a/vizro-core/src/vizro/_themes/vizro_light.json b/vizro-core/src/vizro/_themes/vizro_light.json new file mode 100644 index 000000000..014babb0f --- /dev/null +++ b/vizro-core/src/vizro/_themes/vizro_light.json @@ -0,0 +1,272 @@ +{ + "layout": { + "annotationdefaults": { + "font": { + "size": 14, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "showarrow": false + }, + "bargroupgap": 0.1, + "coloraxis": { + "autocolorscale": false, + "colorbar": { + "outlinewidth": 0, + "showticklabels": true, + "thickness": 20, + "tickfont": { + "size": 14, + "color": "rgba(20, 23, 33, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 14, + "color": "rgba(20, 23, 33, 0.6)" + } + }, + "tickcolor": "rgba(20, 23, 33, 0.3019607843)" + } + }, + "colorscale": { + "diverging": [ + [0.0, "#7e000c"], + [0.05555555555555555, "#9d1021"], + [0.1111111111111111, "#bc1f37"], + [0.16666666666666666, "#db2f4c"], + [0.2222222222222222, "#ea536b"], + [0.2777777777777778, "#f67486"], + [0.3333333333333333, "#fe94a0"], + [0.3888888888888889, "#fbb6be"], + [0.4444444444444444, "#f8d6da"], + [0.5, "#E6E8EA"], + [0.5555555555555556, "#afe7f9"], + [0.6111111111111112, "#5bd6fe"], + [0.6666666666666666, "#3bbef1"], + [0.7222222222222222, "#24a6e1"], + [0.7777777777777778, "#0d8ed1"], + [0.8333333333333334, "#0077bd"], + [0.8888888888888888, "#0061a4"], + [0.9444444444444444, "#004c8c"], + [1.0, "#003875"] + ], + "sequential": [ + [0.0, "#afe7f9"], + [0.125, "#5bd6fe"], + [0.25, "#3bbef1"], + [0.375, "#24a6e1"], + [0.5, "#0d8ed1"], + [0.625, "#0077bd"], + [0.75, "#0061a4"], + [0.875, "#004c8c"], + [1.0, "#003875"] + ], + "sequentialminus": [ + [0.0, "#7e000c"], + [0.125, "#9d1021"], + [0.25, "#bc1f37"], + [0.375, "#db2f4c"], + [0.5, "#ea536b"], + [0.625, "#f67486"], + [0.75, "#fe94a0"], + [0.875, "#fbb6be"], + [1.0, "#f8d6da"] + ] + }, + "colorway": [ + "#00b4ff", + "#ff9222", + "#3949ab", + "#ff5267", + "#08bdba", + "#fdc935", + "#689f38", + "#976fd1", + "#f781bf", + "#52733e" + ], + "font": { + "family": "Inter, sans-serif, Arial", + "size": 14, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "legend": { + "bgcolor": "rgba(0,0,0,0)", + "font": { + "size": 14, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "orientation": "h", + "title": { + "font": { + "size": 14, + "color": "rgba(20, 23, 33, 0.8784313725)" + } + }, + "y": -0.2 + }, + "margin": { + "autoexpand": true, + "b": 64, + "l": 80, + "pad": 0, + "r": 24, + "t": 64 + }, + "showlegend": true, + "title": { + "font": { + "size": 20, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "pad": { + "b": 0, + "l": 24, + "r": 24, + "t": 24 + }, + "x": 0, + "xanchor": "left", + "xref": "container", + "y": 1, + "yanchor": "top", + "yref": "container" + }, + "uniformtext": { + "minsize": 12, + "mode": "hide" + }, + "xaxis": { + "automargin": true, + "layer": "below traces", + "linewidth": 1, + "showline": true, + "showticklabels": true, + "tickfont": { + "size": 14, + "color": "rgba(20, 23, 33, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 16, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "standoff": 8 + }, + "visible": true, + "zeroline": false, + "tickcolor": "rgba(20, 23, 33, 0.3019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)", + "gridcolor": "rgba(20, 23, 33, 0.1019607843)" + }, + "yaxis": { + "automargin": true, + "layer": "below traces", + "linewidth": 1, + "showline": false, + "showticklabels": true, + "tickfont": { + "size": 14, + "color": "rgba(20, 23, 33, 0.6)" + }, + "ticklabelposition": "outside", + "ticklen": 8, + "ticks": "outside", + "tickwidth": 1, + "title": { + "font": { + "size": 16, + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "standoff": 8 + }, + "visible": true, + "zeroline": false, + "tickcolor": "rgba(20, 23, 33, 0.3019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)", + "gridcolor": "rgba(20, 23, 33, 0.1019607843)" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white" + }, + "polar": { + "bgcolor": "white", + "angularaxis": { + "gridcolor": "rgba(20, 23, 33, 0.1019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)" + }, + "radialaxis": { + "gridcolor": "rgba(20, 23, 33, 0.1019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)" + } + }, + "ternary": { + "bgcolor": "white", + "aaxis": { + "gridcolor": "rgba(20, 23, 33, 0.1019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)" + }, + "baxis": { + "gridcolor": "rgba(20, 23, 33, 0.1019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)" + }, + "caxis": { + "gridcolor": "rgba(20, 23, 33, 0.1019607843)", + "linecolor": "rgba(20, 23, 33, 0.3019607843)" + } + } + }, + "data": { + "bar": [ + { + "marker": { + "line": { + "color": "white" + } + }, + "type": "bar" + } + ], + "waterfall": [ + { + "connector": { + "line": { + "color": "rgba(20, 23, 33, 0.3019607843)", + "width": 1 + } + }, + "decreasing": { + "marker": { + "color": "#ff9222" + } + }, + "increasing": { + "marker": { + "color": "#00b4ff" + } + }, + "textfont": { + "color": "rgba(20, 23, 33, 0.8784313725)" + }, + "textposition": "outside", + "totals": { + "marker": { + "color": "grey" + } + }, + "type": "waterfall" + } + ] + } +} diff --git a/vizro-core/src/vizro/models/_dashboard.py b/vizro-core/src/vizro/models/_dashboard.py index 2720db4fc..3c1dea11e 100644 --- a/vizro-core/src/vizro/models/_dashboard.py +++ b/vizro-core/src/vizro/models/_dashboard.py @@ -23,7 +23,7 @@ ) import vizro -from vizro._themes._templates.template_dashboard_overrides import dashboard_overrides +from vizro._themes.template_dashboard_overrides import dashboard_overrides try: from pydantic.v1 import Field, validator diff --git a/vizro-core/src/vizro/static/css/figures.css b/vizro-core/src/vizro/static/css/figures.css index 709cd32b9..17bb2f109 100644 --- a/vizro-core/src/vizro/static/css/figures.css +++ b/vizro-core/src/vizro/static/css/figures.css @@ -6,9 +6,8 @@ /* We currently ship this CSS file with the package, so the font definitions need to live here at the moment. The CSS is taken from https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined and the corresponding font file -downloaded to fonts/material-symbols-outlined.woff2. See: -https://developers.google.com/fonts/docs/material_symbols#self-hosting_the_font -https://fonts.google.com/knowledge/using_type/self_hosting_web_fonts +downloaded from: https://github.com/google/material-design-icons/tree/master/variablefont +See: https://developers.google.com/fonts/docs/material_symbols#self-hosting_the_font Note the relative url works with both serve_locally=True and serve_locally=False through the CDN. */ @font-face { @@ -16,7 +15,6 @@ Note the relative url works with both serve_locally=True and serve_locally=False src: url("./fonts/material-symbols-outlined.woff2") format("woff2"); } -/* This applies to all icons inside vizro */ .material-symbols-outlined { direction: ltr; display: inline-block; diff --git a/vizro-core/src/vizro/static/css/fonts/material-symbols-outlined.woff2 b/vizro-core/src/vizro/static/css/fonts/material-symbols-outlined.woff2 index f823fa0c4..8eb2ad28d 100644 Binary files a/vizro-core/src/vizro/static/css/fonts/material-symbols-outlined.woff2 and b/vizro-core/src/vizro/static/css/fonts/material-symbols-outlined.woff2 differ diff --git a/vizro-core/tests/e2e/component_library/test_component_library.py b/vizro-core/tests/e2e/component_library/test_component_library.py index 1ef534a8b..6d0a36153 100644 --- a/vizro-core/tests/e2e/component_library/test_component_library.py +++ b/vizro-core/tests/e2e/component_library/test_component_library.py @@ -20,6 +20,7 @@ value_column="Actual", title="KPI with aggregation", agg_func="median", + icon="folder_check_2", ), kpi_card( data_frame=df_kpi, @@ -82,8 +83,20 @@ def test_kpi_card_component_library(dash_duo, request): ] ) dash_duo.start_server(app) - dash_duo.wait_for_page(timeout=20) - dash_duo.wait_for_element("div[class='card-kpi card']") + dash_duo.wait_for_text_to_equal( + "div[class='vstack gap-4'] div:nth-of-type(1) div:nth-of-type(2) p[class='material-symbols-outlined']", + "folder_check_2", + ) + dash_duo.wait_for_text_to_equal( + "div[class='vstack gap-4'] div:nth-of-type(1) div:nth-of-type(2) div[class='card-body']", "200.0" + ) + dash_duo.wait_for_text_to_equal( + "div[class='vstack gap-4'] div:nth-of-type(2) div:nth-of-type(4) p[class='material-symbols-outlined']", + "shopping_cart", + ) + dash_duo.wait_for_text_to_equal( + "div[class='vstack gap-4'] div:nth-of-type(2) div:nth-of-type(4) div[class='card-body']", "1000" + ) result_image_path, expected_image_path = make_screenshot_and_paths(dash_duo.driver, request.node.name) assert_image_equal(result_image_path, expected_image_path) assert dash_duo.get_logs() == [], "browser console should contain no error" diff --git a/vizro-core/tests/e2e/screenshots/main_kpi_card_component_library.png b/vizro-core/tests/e2e/screenshots/main_kpi_card_component_library.png index f03627760..8712c9d71 100644 Binary files a/vizro-core/tests/e2e/screenshots/main_kpi_card_component_library.png and b/vizro-core/tests/e2e/screenshots/main_kpi_card_component_library.png differ diff --git a/vizro-core/tests/tests_utils/e2e/asserts.py b/vizro-core/tests/tests_utils/e2e/asserts.py index efb829423..acdf9b94a 100644 --- a/vizro-core/tests/tests_utils/e2e/asserts.py +++ b/vizro-core/tests/tests_utils/e2e/asserts.py @@ -36,8 +36,8 @@ def _create_image_difference(expected_image, result_image): # Calculate bounding rectangles around detected contour (x, y, width, height) = cv2.boundingRect(contour) # Draw red rectangle around difference area - cv2.rectangle(result_image, (x, y), (x + width, y + height), (0, 0, 255), 2) - return result_image + cv2.rectangle(expected_image, (x, y), (x + width, y + height), (0, 0, 255), 2) + return expected_image def make_screenshot_and_paths(driver, request_node_name): @@ -54,7 +54,7 @@ def assert_image_equal(result_image_path, expected_image_path): expected_image_name = Path(expected_image_path).name result_image = cv2.imread(result_image_path) try: - _compare_images(expected_image, result_image) + _compare_images(expected_image=expected_image, result_image=result_image) # Deleting created branch image to leave only failed for github artifacts Path(result_image_path).unlink() except AssertionError as exc: diff --git a/vizro-core/tests/unit/vizro/themes/test_create_chart_template.py b/vizro-core/tests/unit/vizro/themes/test_create_chart_template.py new file mode 100644 index 000000000..c92f2bc09 --- /dev/null +++ b/vizro-core/tests/unit/vizro/themes/test_create_chart_template.py @@ -0,0 +1,55 @@ +import pytest + +from vizro._themes.generate_plotly_templates import _extract_last_two_occurrences, extract_bs_variables_from_css + + +@pytest.fixture +def css_content(): + css_content = """ + :root, [data-bs-theme=light] { + --bs-primary: #007bff; + --bs-tertiary-color: #adb5bd; + } + :root, [data-bs-theme=dark] { + --bs-primary: #375a7f; + --bs-secondary: #6c757d; + + } + [data-bs-theme=light] { + --bs-primary: #976fd1; + --bs-secondary: #444fff; + } + """ + + return css_content + + +@pytest.mark.parametrize( + "variable, expected", + [ + ("--bs-primary", ("#375a7f", "#976fd1")), + ("--bs-secondary", ("#6c757d", "#444fff")), + ("--bs-tertiary", (None, None)), + ], +) +def test_extract_last_two_occurrences(variable, css_content, expected): + result_dark, result_light = _extract_last_two_occurrences(variable, css_content) + assert (result_dark, result_light) == expected + + +def test_extract_bs_variables_from_css_file(css_content): + expected_dark = { + "BS-PRIMARY": "#375a7f", + "BS-SECONDARY": "#6c757d", + } + expected_light = { + "BS-PRIMARY": "#976fd1", + "BS-SECONDARY": "#444fff", + } + + result_dark, result_light = extract_bs_variables_from_css( + ["--bs-primary", "--bs-secondary", "--bs-tertiary"], css_content + ) + + assert result_dark == expected_dark + assert result_light == expected_light diff --git a/vizro-core/tests/unit/vizro/themes/test_theme.py b/vizro-core/tests/unit/vizro/themes/test_theme.py deleted file mode 100644 index f12ef79fc..000000000 --- a/vizro-core/tests/unit/vizro/themes/test_theme.py +++ /dev/null @@ -1,42 +0,0 @@ -"""Tests for templates.""" - -import numpy as np -import pandas as pd -import plotly.express as px -import pytest -from plotly.graph_objs.layout._template import Template - -import vizro._themes as themes -from vizro._themes._color_values import COLORS - - -@pytest.fixture -def template(): - """Fixture for template.""" - return np.random.choice([themes.dark, themes.light], 1)[0] - - -@pytest.fixture -def chart_data(): - """Fixture for chart data.""" - data_points = np.random.randint(0, 100, 1, dtype=int) - data = pd.DataFrame( - { - "x": np.random.choice(["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"], data_points), - "y": np.random.normal(0, 200, data_points), - } - ) - return data - - -def test_template(template): - assert isinstance(template, Template) - - -def test_color_palette_applied(chart_data, template): - fig_1 = px.scatter(chart_data, x="x", y="y", color="y") # continuous column for color - fig_2 = px.scatter(chart_data, x="x", y="y", color="x") # discrete column for color - - marker_colors = [fig_2.data[i].marker.color for i in range(len(fig_2.data))] - assert fig_1.layout.coloraxis.colorscale == template.layout.colorscale.sequential - assert marker_colors == COLORS["DISCRETE_10"][: len(marker_colors)]