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 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/)
-
+
+
+
+
@@ -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.
-
+
+
+
+
## 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 @@
[](https://pypi.org/project/vizro/) [](https://badge.fury.io/py/vizro_ai) [](https://github.com/mckinsey/vizro/blob/main/LICENSE.md) [](https://vizro-ai.readthedocs.io/) [](https://www.bestpractices.dev/projects/7858)
-
+
+
+
+
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.
-
+
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 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/)
-
+
+
+
+
@@ -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.
-
+
+
+
+
## 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)]