diff --git a/pyproject.toml b/pyproject.toml index 194714c4c..ab4666b58 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -26,8 +26,8 @@ target-version = ["py38"] [tool.codespell] builtin = "clear,rare,en-GB_to_en-US" -ignore-words-list = "grey,ned,sav" -skip = "*.min.css.map,*.min.css,.vale/*" +ignore-words-list = "grey,ned,sav,Thur" +skip = "*.min.css.map,*.min.css,.vale/*, *assets/*" [tool.mypy] # strict checks : strict = true @@ -91,3 +91,5 @@ select = [ "**/tests/**" = ["PLR2004", "S101", "TID252", "D100", "D101", "D102", "D103", "PLC1901", "RUF012"] # Ignore import violations in all __init__.py files "__init__.py" = ["E402", "F401"] +# Ignore missing docstrings in chart gallery examples to keep them succinct. +"vizro-core/examples/_chart-gallery/pages/examples/**" = ["D100", "D103"] diff --git a/vizro-ai/changelog.d/20240702_183601_anna_xiong_vizro_ai_restructure.md b/vizro-ai/changelog.d/20240702_183601_anna_xiong_vizro_ai_restructure.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-ai/changelog.d/20240702_183601_anna_xiong_vizro_ai_restructure.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-ai/changelog.d/20240725_132140_nadija_ratkusic_graca_vizroai_update_docs.md b/vizro-ai/changelog.d/20240725_132140_nadija_ratkusic_graca_vizroai_update_docs.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-ai/changelog.d/20240725_132140_nadija_ratkusic_graca_vizroai_update_docs.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-ai/changelog.d/20240727_031259_lingyi_zhang_vizroai_code.md b/vizro-ai/changelog.d/20240727_031259_lingyi_zhang_vizroai_code.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-ai/changelog.d/20240727_031259_lingyi_zhang_vizroai_code.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-ai/changelog.d/20240727_033910_lingyi_zhang_chart_dataframe_mutation.md b/vizro-ai/changelog.d/20240727_033910_lingyi_zhang_chart_dataframe_mutation.md new file mode 100644 index 000000000..5513f81dc --- /dev/null +++ b/vizro-ai/changelog.d/20240727_033910_lingyi_zhang_chart_dataframe_mutation.md @@ -0,0 +1,49 @@ + + + + + + +### Changed + +- Stabilized `plot` performance by addressing several dataframe mutation issues. ([#603](https://github.com/mckinsey/vizro/pull/603)) + + + + + diff --git a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md index e26209774..f940641d8 100644 --- a/vizro-ai/docs/pages/user-guides/run-vizro-ai.md +++ b/vizro-ai/docs/pages/user-guides/run-vizro-ai.md @@ -81,32 +81,32 @@ There are two ways to integrate Vizro-AI into an application, directly and by ac ``` -2. Vizro-AI's `_get_chart_code` method returns a string of Python code that manipulates the data and creates the visualization. Vizro-AI validates the code to ensure that it is executable and can be integrated. +2. When the `return_elements` argument of VizroAI's `plot` method is set to `True`, the method returns a `PlotOutputs` data class which contains all possible `VizroAI.plot()` outputs. +By setting `return_elements=True` you can access code or the figure object. Vizro-AI validates the code to ensure that it is executable and can be integrated. - !!! example "Application integration via chart code" + !!! example "Accessing outputs from PlotOutputs data class" === "app.py" - ```py - import vizro.plotly.express as px - from vizro_ai import VizroAI - - vizro_ai = VizroAI() + ```py + import vizro_ai + from vizro_ai import VizroAI + import vizro.plotly.express as px + from dotenv import load_dotenv - df = px.data.gapminder() - code_string = vizro_ai._get_chart_code(df, "describe life expectancy per continent over time") - ``` - === "code_string" - [![ResultCode]][ResultCode] + load_dotenv() - [ResultCode]: ../../assets/user_guides/code_string_app_integration.png + df = px.data.gapminder() + vizro_ai = VizroAI() - The returned `code_string` can be used to dynamically render charts within your application. You may have the option to encapsulate the chart within a `fig` object or convert the figure into a JSON string for further integration. + plot_outputs = vizro_ai.plot(df, "describe life expectancy per continent over time", explain=True, return_elements=True) + fig = plot_outputs.figure + code_string = plot_outputs.code + ``` - To use the insights or code explanation, you can use `vizro_ai._run_plot_tasks(df, ..., explain=True)`, which returns a dictionary containing the code explanation and chart insights alongside the code. ### How to use `max_debug_retry` parameter in plot function - Default Value: 3 -- Type: int +- Type: `int` - Brief: By default, the `max_debug_retry` is set to 3, the function will try to debug errors up to three times. If the errors are not resolved after the maximum number of retries, the function will stop further debugging retries. For example, if you would like adjust to 5 retries, you can set `max_debug_retry = 5` in the plot function: @@ -114,3 +114,17 @@ For example, if you would like adjust to 5 retries, you can set `max_debug_retry ```py vizro_ai.plot(df = df, user_input = "your user input", max_debug_retry= 5) ``` + +### How to use `return_elements` parameter in plot function +- Default Value: False +- Type: `bool` +- Brief: By default, the `return_elements` is set to `False`, and the `VizroAI.plot()` returns a `plotly.graph_objects` object. If `return_elements` is set to `True` `VizroAI.plot()` returns the `PlotOutputs` data class. +The `PlotOutputs` data class is designed to encapsulate all possible outputs generated by the `VizroAI.plot()` method. + +Attributes of `PlotOutputs`: + +- **`code`**: A string representing of the Python code that manipulates the data and creates the visualization. +- **`figure`**: A [`CapturedCallable`](https://vizro.readthedocs.io/en/stable/pages/API-reference/models/#vizro.models.types.CapturedCallable) object from [`Vizro`](https://vizro.readthedocs.io/en/stable/), representing the visual plot generated by `VizroAI.plot()`. This object is designed to run immediately in the Vizro dashboard, but otherwise, it behaves like a plotly `go.Figure`. +- **`business_insights`**: A string containing high-level business insights derived from the plot. `business_insights` is only available if `explain=True`. +- **`code_explanation`**: A string offering a detailed explanation of the code used to produce the plot. `code_explanation` is only available if `explain=True`. +- diff --git a/vizro-ai/src/vizro_ai/chains/_llm_models.py b/vizro-ai/src/vizro_ai/_llm_models.py similarity index 100% rename from vizro-ai/src/vizro_ai/chains/_llm_models.py rename to vizro-ai/src/vizro_ai/_llm_models.py diff --git a/vizro-ai/src/vizro_ai/_vizro_ai.py b/vizro-ai/src/vizro_ai/_vizro_ai.py index 2432fbe71..c0866aa41 100644 --- a/vizro-ai/src/vizro_ai/_vizro_ai.py +++ b/vizro-ai/src/vizro_ai/_vizro_ai.py @@ -5,9 +5,9 @@ import plotly.graph_objects as go from langchain_openai import ChatOpenAI -from vizro_ai.chains._llm_models import _get_llm_model -from vizro_ai.components import GetCodeExplanation, GetDebugger -from vizro_ai.task_pipeline._pipeline_manager import PipelineManager +from vizro_ai._llm_models import _get_llm_model +from vizro_ai.plot.components import GetCodeExplanation, GetDebugger +from vizro_ai.plot.task_pipeline._pipeline_manager import PipelineManager from vizro_ai.utils.helper import ( DebugFailure, PlotOutputs, @@ -64,12 +64,12 @@ def _run_plot_tasks( ) -> PlotOutputs: """Task execution.""" chart_type_pipeline = self.pipeline_manager.chart_type_pipeline - chart_types = chart_type_pipeline.run(initial_args={"chain_input": user_input, "df": df}) + chart_type = chart_type_pipeline.run(initial_args={"chain_input": user_input, "df": df}) # TODO update to loop through charts for multiple charts creation plot_pipeline = self.pipeline_manager.plot_pipeline custom_chart_code = plot_pipeline.run( - initial_args={"chain_input": user_input, "df": df, "chart_types": chart_types} + initial_args={"chain_input": user_input, "df": df, "chart_type": chart_type} ) # TODO add debug in pipeline after getting _debug_helper logic in component diff --git a/vizro-ai/src/vizro_ai/utils/__init__.py b/vizro-ai/src/vizro_ai/plot/__init__.py similarity index 100% rename from vizro-ai/src/vizro_ai/utils/__init__.py rename to vizro-ai/src/vizro_ai/plot/__init__.py diff --git a/vizro-ai/src/vizro_ai/task_pipeline/__init_.py b/vizro-ai/src/vizro_ai/plot/_utils/__init__.py similarity index 100% rename from vizro-ai/src/vizro_ai/task_pipeline/__init_.py rename to vizro-ai/src/vizro_ai/plot/_utils/__init__.py diff --git a/vizro-ai/src/vizro_ai/utils/_constants.py b/vizro-ai/src/vizro_ai/plot/_utils/_constants.py similarity index 100% rename from vizro-ai/src/vizro_ai/utils/_constants.py rename to vizro-ai/src/vizro_ai/plot/_utils/_constants.py diff --git a/vizro-ai/src/vizro_ai/utils/safeguard.py b/vizro-ai/src/vizro_ai/plot/_utils/_safeguard.py similarity index 100% rename from vizro-ai/src/vizro_ai/utils/safeguard.py rename to vizro-ai/src/vizro_ai/plot/_utils/_safeguard.py diff --git a/vizro-ai/src/vizro_ai/components/__init__.py b/vizro-ai/src/vizro_ai/plot/components/__init__.py similarity index 85% rename from vizro-ai/src/vizro_ai/components/__init__.py rename to vizro-ai/src/vizro_ai/plot/components/__init__.py index 3d0fc8204..0ec637392 100644 --- a/vizro-ai/src/vizro_ai/components/__init__.py +++ b/vizro-ai/src/vizro_ai/plot/components/__init__.py @@ -1,4 +1,4 @@ -from ._base import VizroAiComponentBase +from ._base import VizroAIComponentBase from .chart_selection import GetChartSelection from .code_validation import GetDebugger from .custom_chart_wrap import GetCustomChart @@ -7,7 +7,7 @@ from .visual_code import GetVisualCode __all__ = [ - "VizroAiComponentBase", + "VizroAIComponentBase", "GetChartSelection", "GetDataFrameCraft", "GetVisualCode", diff --git a/vizro-ai/src/vizro_ai/components/_base.py b/vizro-ai/src/vizro_ai/plot/components/_base.py similarity index 98% rename from vizro-ai/src/vizro_ai/components/_base.py rename to vizro-ai/src/vizro_ai/plot/components/_base.py index a0f16b96f..afeef8ddd 100644 --- a/vizro-ai/src/vizro_ai/components/_base.py +++ b/vizro-ai/src/vizro_ai/plot/components/_base.py @@ -6,7 +6,7 @@ from vizro_ai.chains import FunctionCallChain -class VizroAiComponentBase(ABC): +class VizroAIComponentBase(ABC): """Abstract Base Class that represents a blueprint for Vizro-AI components. Attributes diff --git a/vizro-ai/src/vizro_ai/components/chart_selection.py b/vizro-ai/src/vizro_ai/plot/components/chart_selection.py similarity index 79% rename from vizro-ai/src/vizro_ai/components/chart_selection.py rename to vizro-ai/src/vizro_ai/plot/components/chart_selection.py index c0d23e9b9..de1da2b0d 100644 --- a/vizro-ai/src/vizro_ai/components/chart_selection.py +++ b/vizro-ai/src/vizro_ai/plot/components/chart_selection.py @@ -12,8 +12,9 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager +from vizro_ai.utils.helper import _get_df_info # initialization of schema manager, and register schema needed # preprocess: llm kwargs for function description schema + partial vars @@ -35,12 +36,12 @@ class ChartSelection(BaseModel): # 2. Define prompt -chart_type_prompt = "choose a best chart types for this df info:{df_schema}, {df_head} and user question {input}?" +chart_type_prompt = "choose a best chart type for this df info:{df_schema}, {df_sample} and user question {input}?" # 3. Define Component -class GetChartSelection(VizroAiComponentBase): - """Get Chart Types. +class GetChartSelection(VizroAIComponentBase): + """Get chart type. Attributes prompt (str): Prompt chart selection chains. @@ -63,9 +64,9 @@ def _pre_process(self, df: pd.DataFrame, *args, **kwargs) -> Tuple[Dict, Dict]: It should return llm_kwargs and partial_vars_map for """ - df_schema, df_head = self._get_df_info(df) + df_schema, df_sample = _get_df_info(df) llm_kwargs_to_use = openai_schema_manager.get_llm_kwargs("ChartSelection") - partial_vars = {"df_schema": df_schema, "df_head": df_head} + partial_vars = {"df_schema": df_schema, "df_sample": df_sample} return llm_kwargs_to_use, partial_vars def _post_process(self, response: Dict, *args, **kwargs) -> str: @@ -87,14 +88,6 @@ def run(self, chain_input: str, df: pd.DataFrame = None) -> str: """ return super().run(chain_input=chain_input, df=df) - @staticmethod - def _get_df_info(df: pd.DataFrame) -> Tuple[str, str]: - """Get the dataframe schema and head info as string.""" - formatted_pairs = [f"{col_name}: {dtype}" for col_name, dtype in df.dtypes.items()] - schema_string = "\n".join(formatted_pairs) - - return schema_string, df.head().to_markdown() - @staticmethod def _chart_to_use(load_args) -> str: """Get Chart name as string or list of chart names as string.""" @@ -109,7 +102,7 @@ def _chart_to_use(load_args) -> str: if __name__ == "__main__": import vizro.plotly.express as px - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() diff --git a/vizro-ai/src/vizro_ai/components/code_validation.py b/vizro-ai/src/vizro_ai/plot/components/code_validation.py similarity index 79% rename from vizro-ai/src/vizro_ai/components/code_validation.py rename to vizro-ai/src/vizro_ai/plot/components/code_validation.py index 2609c1faf..3615d5896 100644 --- a/vizro-ai/src/vizro_ai/components/code_validation.py +++ b/vizro-ai/src/vizro_ai/plot/components/code_validation.py @@ -11,8 +11,8 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager # 1. Define schema openai_schema_manager = SchemaManager() @@ -28,14 +28,21 @@ class CodeDebug(BaseModel): # 2. Define prompt -debugging_prompt = ( - "Return the full code snippet after fixing the bug in the code snippet {code_snippet}, this is the error message " - "{input}," -) +debugging_prompt = """ +You are an expert Python and Pandas code reviewer and corrector. +Your task is to review Pandas code strings provided, identify any issues or improvements, +and return a corrected version of the code. +Return the full code snippet after fixing the bug in the code snippet: +{code_snippet} + +IMPORTANT: Avoid adding fake data for the variable df. It will be provided by the user when executed. +This is the error message: +{input}, +""" # 3. Define Component -class GetDebugger(VizroAiComponentBase): +class GetDebugger(VizroAIComponentBase): """Get Visual code. Attributes @@ -89,7 +96,7 @@ def run(self, code_snippet: str, chain_input: str = "") -> str: if __name__ == "__main__": import vizro.plotly.express as px - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() diff --git a/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py b/vizro-ai/src/vizro_ai/plot/components/custom_chart_wrap.py similarity index 80% rename from vizro-ai/src/vizro_ai/components/custom_chart_wrap.py rename to vizro-ai/src/vizro_ai/plot/components/custom_chart_wrap.py index ee7d8fa80..79386bb63 100644 --- a/vizro-ai/src/vizro_ai/components/custom_chart_wrap.py +++ b/vizro-ai/src/vizro_ai/plot/components/custom_chart_wrap.py @@ -11,8 +11,8 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager logger = logging.getLogger(__name__) @@ -22,23 +22,29 @@ @openai_schema_manager.register class CustomChart(BaseModel): - """Plotly code per user request that is suitable for chart types for given data.""" + """Plotly code per user request that is suitable for chart type for given data.""" custom_chart_code: str = Field(..., description="Modified and decorated code snippet to allow use in dashboards") # 2. Define prompt custom_chart_prompt = """ - Please modify the following code {input} such that: - 1. You wrap the entire chart code into function called 'custom_chart' that takes a single optional arg called - data_frame and returns only the fig object, ie `def custom_chart(data_frame): as first line - 2. You ensure that the above function only returns the plotly fig object, - and that the variables are renamed such that all data is derived from 'data_frame' - 3. Leave all imports as is above that function, and do NOT add anything else - """ +Your task is to correctly wrap the provided code as instructed. IMPORTANT: Do not mock the data. + +Instruction: +1. You wrap the entire chart code into function called 'custom_chart' that takes a single optional arg called +data_frame and returns only the fig object, ie `def custom_chart(data_frame): as first line. +2. You ensure that the above function only returns the plotly fig object, +and that the variables are renamed such that all data is derived from 'data_frame'. +3. Leave all imports as is above that function, and do NOT add anything else. + +Please modify the following code: +{input} + +""" -class GetCustomChart(VizroAiComponentBase): +class GetCustomChart(VizroAIComponentBase): # TODO Explore if it is possible to create CustomChart without LLM """Get custom chart code. @@ -106,7 +112,7 @@ def _add_capture_code(code_string: str) -> str: if __name__ == "__main__": import vizro.plotly.express as px - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() diff --git a/vizro-ai/src/vizro_ai/components/dataframe_craft.py b/vizro-ai/src/vizro_ai/plot/components/dataframe_craft.py similarity index 71% rename from vizro-ai/src/vizro_ai/components/dataframe_craft.py rename to vizro-ai/src/vizro_ai/plot/components/dataframe_craft.py index 6d2bbfe83..84b72b07e 100755 --- a/vizro-ai/src/vizro_ai/components/dataframe_craft.py +++ b/vizro-ai/src/vizro_ai/plot/components/dataframe_craft.py @@ -14,8 +14,9 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager +from vizro_ai.utils.helper import _get_df_info logger = logging.getLogger(__name__) @@ -31,18 +32,25 @@ class DataFrameCraft(BaseModel): # 2. Define prompt -dataframe_prompt = """Context: You are working with a pandas DataFrame in Python named df. -DataFrame Details Schema: {df_schema}, Sample Data: {df_head}, User Query: {input} -Instructions: 1.Write code to manipulate the df DataFrame according to the user's query. -2.Do not create any new DataFrames; work only with df. -3.Ensure that any aggregated columns are named appropriately and re-indexed if necessary. -4.If a visualization is implied by the user's query, only write the necessary DataFrame manipulation -code for that visualization. 5.Do not include any plotting code. -6. Produce the code in a line-by-line format, not wrapped inside a function.""" +dataframe_prompt = """ +You are a software engineer working with a pandas DataFrame in Python named df. +Your task is to write code to manipulate the df DataFrame according to the user's query. +So user can get the desired output for create subsequent visualization. +DataFrame Details Schema: {df_schema}, Sample Data: {df_sample}, User Query: {input} + +Instructions: +1. Write code to manipulate the df DataFrame according to the user's query. +2. Do not create any new DataFrames; work only with df. +3. Always make a hard copy of the DataFrame before manipulating it. Important: Do not modify the original DataFrame. +4. Ensure that any aggregated columns are named appropriately and re-indexed if necessary. +5. If a visualization is implied by the user's query, only write the necessary DataFrame manipulation +code for that visualization. +6. Do not include any plotting code. +7. Produce the code in a line-by-line format, not wrapped inside a function.""" # 3. Define Component -class GetDataFrameCraft(VizroAiComponentBase): +class GetDataFrameCraft(VizroAIComponentBase): """Get dataframe code. Attributes @@ -66,9 +74,9 @@ def _pre_process(self, df: pd.DataFrame, *args, **kwargs) -> Tuple[Dict, Dict]: It should return llm_kwargs and partial_vars_map """ - df_schema, df_head = self._get_df_info(df) + df_schema, df_sample = _get_df_info(df) llm_kwargs_to_use = openai_schema_manager.get_llm_kwargs("DataFrameCraft") - partial_vars_map = {"df_schema": df_schema, "df_head": df_head} + partial_vars_map = {"df_schema": df_schema, "df_sample": df_sample} return llm_kwargs_to_use, partial_vars_map @@ -90,14 +98,6 @@ def run(self, chain_input: str, df: pd.DataFrame = None) -> str: """ return super().run(chain_input, df) - @staticmethod - def _get_df_info(df: pd.DataFrame) -> Tuple[str, str]: - """Get the dataframe schema and head info as string.""" - formatted_pairs = [f"{col_name}: {dtype}" for col_name, dtype in df.dtypes.items()] - schema_string = "\n".join(formatted_pairs) - - return schema_string, df.head().to_markdown() - @staticmethod def _format_dataframe_string(s: str) -> str: """Format the dataframe code snippet string.""" @@ -126,7 +126,7 @@ def _format_dataframe_string(s: str) -> str: if __name__ == "__main__": import vizro.plotly.express as px - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() df = px.data.gapminder() diff --git a/vizro-ai/src/vizro_ai/components/explanation.py b/vizro-ai/src/vizro_ai/plot/components/explanation.py similarity index 90% rename from vizro-ai/src/vizro_ai/components/explanation.py rename to vizro-ai/src/vizro_ai/plot/components/explanation.py index 1ca074bc9..e030eb911 100644 --- a/vizro-ai/src/vizro_ai/components/explanation.py +++ b/vizro-ai/src/vizro_ai/plot/components/explanation.py @@ -10,8 +10,8 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager # 1. Define schema openai_schema_manager = SchemaManager() @@ -28,14 +28,14 @@ class CodeExplanation(BaseModel): # 2. Define prompt -code_explanation_prompt = ( - "Given user question {input} and answer {code_snippet}, (less than 400 characters)," - "DO NOT just use one sentence for business insights, give detailed information" -) +code_explanation_prompt = """ +Given user question {input} and answer {code_snippet} (less than 400 characters), +DO NOT just use one sentence for business insights, give detailed information. +""" # 3. Define Component -class GetCodeExplanation(VizroAiComponentBase): +class GetCodeExplanation(VizroAIComponentBase): """Get Explanation of a code snippet. Attributes @@ -98,7 +98,7 @@ def _text_cleanup(load_args) -> str: if __name__ == "__main__": - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() diff --git a/vizro-ai/src/vizro_ai/components/visual_code.py b/vizro-ai/src/vizro_ai/plot/components/visual_code.py similarity index 59% rename from vizro-ai/src/vizro_ai/components/visual_code.py rename to vizro-ai/src/vizro_ai/plot/components/visual_code.py index fb1de4130..9c42fe2dc 100644 --- a/vizro-ai/src/vizro_ai/components/visual_code.py +++ b/vizro-ai/src/vizro_ai/plot/components/visual_code.py @@ -2,6 +2,8 @@ from typing import Dict, Tuple +import pandas as pd + try: from pydantic.v1 import BaseModel, Field except ImportError: # pragma: no cov @@ -10,8 +12,9 @@ from langchain_core.language_models.chat_models import BaseChatModel from vizro_ai.chains._chain_utils import _log_time -from vizro_ai.components import VizroAiComponentBase -from vizro_ai.schema_manager import SchemaManager +from vizro_ai.plot.components import VizroAIComponentBase +from vizro_ai.plot.schema_manager import SchemaManager +from vizro_ai.utils.helper import _get_df_info # 1. Define schema openai_schema_manager = SchemaManager() @@ -19,22 +22,41 @@ @openai_schema_manager.register class VizroCode(BaseModel): - """Plotly code per user request that is suitable for chart types for given data.""" + """Plotly code per user request that is suitable for chart type for given data.""" visual_code: str = Field(..., description="code snippet for plot visuals using plotly") # 2. Define prompt -visual_code_prompt = ( - "Context: You are working with a pandas dataframe in Python. The name of the dataframe is `df`." - "Instructions: Given the code snippet {df_code}, generate Plotly visualization code to produce a {chart_types} " - "chart that addresses user query: {input}. " - "Please ensure the Plotly code aligns with the provided DataFrame details." -) +visual_code_prompt = """ +Context: You are an AI assistant specialized in data visualization using Python, pandas, and Plotly. + +Given: +- A pandas DataFrame named `df` +- DataFrame schema: {df_schema} +- Sample data (first few rows): {df_sample} +- Data preprocessing code: {df_code} +- User's visualization request: {input} +- Requested chart type: {chart_type} + +Instructions: +1. Analyze the provided DataFrame information and preprocessing code. +2. Generate Plotly code to create a {chart_type} chart that addresses the user's query: {input} +3. Ensure the visualization accurately represents the data and aligns with the DataFrame structure. +4. Use appropriate Plotly Express functions when possible for simplicity. +5. If custom Plotly Graph Objects are necessary, provide clear explanations. +6. Include axis labels, title, and any other relevant chart components. +7. If color coding or additional visual elements would enhance the chart, incorporate them. + +Output: +- Provide the complete Plotly code required to generate the requested visualization. + +Note: Ensure all variable names and data references are consistent with the provided DataFrame (`df`). +""" # 3. Define Component -class GetVisualCode(VizroAiComponentBase): +class GetVisualCode(VizroAIComponentBase): """Get Visual code. Attributes @@ -53,13 +75,21 @@ def __init__(self, llm: BaseChatModel): """ super().__init__(llm) - def _pre_process(self, chart_types: str, df_code: str, *args, **kwargs) -> Tuple[Dict, Dict]: + def _pre_process(self, chart_type: str, df_code: str, df: pd.DataFrame, *args, **kwargs) -> Tuple[Dict, Dict]: """Preprocess for visual code. It should return llm_kwargs and partial_vars_map. """ llm_kwargs_to_use = openai_schema_manager.get_llm_kwargs("VizroCode") - partial_vars_map = {"chart_types": chart_types, "df_code": df_code} + + df_schema, df_sample = _get_df_info(df) + + partial_vars_map = { + "chart_type": chart_type, + "df_code": df_code, + "df_schema": df_schema, + "df_sample": df_sample, + } return llm_kwargs_to_use, partial_vars_map @@ -72,19 +102,20 @@ def _post_process(self, response: Dict, df_code: str, *args, **kwargs) -> str: return self._clean_visual_code(code_snippet) @_log_time - def run(self, chain_input: str, df_code: str, chart_types: str) -> str: + def run(self, chain_input: str, df_code: str, chart_type: str, df: pd.DataFrame = None) -> str: """Run chain to get visual code. Args: chain_input: User input or intermediate question if needed. df_code: Code snippet of dataframe. - chart_types: Chart types. + chart_type: chart type. + df: The dataframe for plotting. Returns: Visual code snippet. """ - return super().run(chain_input=chain_input, df_code=df_code, chart_types=chart_types) + return super().run(chain_input=chain_input, df_code=df_code, chart_type=chart_type, df=df) @staticmethod def _add_df_string(code_string: str, df_code: str) -> str: @@ -104,7 +135,7 @@ def _clean_visual_code(raw_code: str) -> str: if __name__ == "__main__": import vizro.plotly.express as px - from vizro_ai.chains._llm_models import _get_llm_model + from vizro_ai._llm_models import _get_llm_model llm_to_use = _get_llm_model() df = px.data.gapminder() @@ -117,7 +148,7 @@ def _clean_visual_code(raw_code: str) -> str: res = test_visual_code.run( chain_input="choose a best chart for describe the composition of gdp in continent, " "and horizontal line for avg gdp", - chart_types="bar", + chart_type="bar", df_code=df_code, ) print(res) # noqa: T201 diff --git a/vizro-ai/src/vizro_ai/schema_manager/__init__.py b/vizro-ai/src/vizro_ai/plot/schema_manager/__init__.py similarity index 100% rename from vizro-ai/src/vizro_ai/schema_manager/__init__.py rename to vizro-ai/src/vizro_ai/plot/schema_manager/__init__.py diff --git a/vizro-ai/src/vizro_ai/schema_manager/schema_manager.py b/vizro-ai/src/vizro_ai/plot/schema_manager/schema_manager.py similarity index 100% rename from vizro-ai/src/vizro_ai/schema_manager/schema_manager.py rename to vizro-ai/src/vizro_ai/plot/schema_manager/schema_manager.py diff --git a/vizro-ai/src/vizro_ai/plot/task_pipeline/__init_.py b/vizro-ai/src/vizro_ai/plot/task_pipeline/__init_.py new file mode 100644 index 000000000..e69de29bb diff --git a/vizro-ai/src/vizro_ai/task_pipeline/_pipeline.py b/vizro-ai/src/vizro_ai/plot/task_pipeline/_pipeline.py similarity index 100% rename from vizro-ai/src/vizro_ai/task_pipeline/_pipeline.py rename to vizro-ai/src/vizro_ai/plot/task_pipeline/_pipeline.py diff --git a/vizro-ai/src/vizro_ai/task_pipeline/_pipeline_manager.py b/vizro-ai/src/vizro_ai/plot/task_pipeline/_pipeline_manager.py similarity index 75% rename from vizro-ai/src/vizro_ai/task_pipeline/_pipeline_manager.py rename to vizro-ai/src/vizro_ai/plot/task_pipeline/_pipeline_manager.py index 5e5cabf40..e2f7860b8 100644 --- a/vizro-ai/src/vizro_ai/task_pipeline/_pipeline_manager.py +++ b/vizro-ai/src/vizro_ai/plot/task_pipeline/_pipeline_manager.py @@ -1,8 +1,8 @@ """Pipeline Manager.""" from langchain_core.language_models.chat_models import BaseChatModel -from vizro_ai.components import GetChartSelection, GetCustomChart, GetDataFrameCraft, GetVisualCode -from vizro_ai.task_pipeline._pipeline import Pipeline +from vizro_ai.plot.components import GetChartSelection, GetCustomChart, GetDataFrameCraft, GetVisualCode +from vizro_ai.plot.task_pipeline._pipeline import Pipeline class PipelineManager: @@ -19,9 +19,9 @@ def __init__(self, llm: BaseChatModel = None): @property def chart_type_pipeline(self): - """Target chart types pipeline.""" + """Target chart type pipeline.""" pipeline = Pipeline(self.llm) - pipeline.add(GetChartSelection, input_keys=["df", "chain_input"], output_key="chart_types") + pipeline.add(GetChartSelection, input_keys=["df", "chain_input"], output_key="chart_type") return pipeline @property @@ -29,6 +29,6 @@ def plot_pipeline(self): """Plot pipeline.""" pipeline = Pipeline(self.llm) pipeline.add(GetDataFrameCraft, input_keys=["df", "chain_input"], output_key="df_code") - pipeline.add(GetVisualCode, input_keys=["chain_input", "chart_types", "df_code"], output_key="chain_input") + pipeline.add(GetVisualCode, input_keys=["chain_input", "chart_type", "df_code", "df"], output_key="chain_input") pipeline.add(GetCustomChart, input_keys=["chain_input"], output_key="custom_chart_code") return pipeline diff --git a/vizro-ai/src/vizro_ai/utils/helper.py b/vizro-ai/src/vizro_ai/utils/helper.py index b9f927910..48fe03638 100644 --- a/vizro-ai/src/vizro_ai/utils/helper.py +++ b/vizro-ai/src/vizro_ai/utils/helper.py @@ -2,12 +2,11 @@ import traceback from dataclasses import dataclass, field -from typing import Callable, Dict, Optional +from typing import Callable, Dict, Optional, Tuple import pandas as pd import plotly.graph_objects as go - -from .safeguard import _safeguard_check +from vizro_ai.plot._utils._safeguard import _safeguard_check @dataclass @@ -20,6 +19,13 @@ class PlotOutputs: code_explanation: Optional[str] = field(default=None) +def _get_df_info(df: pd.DataFrame) -> Tuple[str, str]: + """Get the dataframe schema and head info as string.""" + formatted_pairs = [f"{col_name}: {dtype}" for col_name, dtype in df.dtypes.items()] + schema_string = "\n".join(formatted_pairs) + return schema_string, df.sample(5, replace=True, random_state=19).to_markdown() + + # Taken from rich.console. See https://github.com/Textualize/rich. def _is_jupyter() -> bool: # pragma: no cover """Checks if we're running in a Jupyter notebook.""" diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_chart_selection.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_chart_selection.py similarity index 86% rename from vizro-ai/tests/unit/vizro-ai/components/test_chart_selection.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_chart_selection.py index 5a83bc2d4..5552cd633 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_chart_selection.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_chart_selection.py @@ -1,7 +1,7 @@ import pandas as pd import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetChartSelection +from vizro_ai.plot.components import GetChartSelection @pytest.fixture @@ -22,7 +22,10 @@ def setup_method(self, fake_llm): def test_pre_process(self): df = pd.DataFrame({"A": [1, 2, 3], "B": [4, 5, 6]}) llm_kwargs, partial_vars = self.get_chart_selection._pre_process(df) - expected_partial_vars = {"df_schema": "A: int64\nB: int64", "df_head": df.head().to_markdown()} + expected_partial_vars = { + "df_schema": "A: int64\nB: int64", + "df_sample": df.sample(5, replace=True, random_state=19).to_markdown(), + } assert partial_vars == expected_partial_vars @pytest.mark.parametrize( diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_code_validation.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_code_validation.py similarity index 97% rename from vizro-ai/tests/unit/vizro-ai/components/test_code_validation.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_code_validation.py index 1c8f42108..f0a4ef057 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_code_validation.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_code_validation.py @@ -1,6 +1,6 @@ import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetDebugger +from vizro_ai.plot.components import GetDebugger @pytest.fixture diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_custom_chart_wrap.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_custom_chart_wrap.py similarity index 99% rename from vizro-ai/tests/unit/vizro-ai/components/test_custom_chart_wrap.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_custom_chart_wrap.py index e1e055c50..d10646741 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_custom_chart_wrap.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_custom_chart_wrap.py @@ -1,6 +1,6 @@ import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetCustomChart +from vizro_ai.plot.components import GetCustomChart @pytest.fixture diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_dataframe_craft.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_dataframe_craft.py similarity index 96% rename from vizro-ai/tests/unit/vizro-ai/components/test_dataframe_craft.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_dataframe_craft.py index 7e40ea528..438b6fa9a 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_dataframe_craft.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_dataframe_craft.py @@ -3,7 +3,7 @@ import pandas as pd import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetDataFrameCraft +from vizro_ai.plot.components import GetDataFrameCraft def dataframe_code(): @@ -46,7 +46,7 @@ def test_pre_process(self, input_df): llm_kwargs_to_use, partial_vars = self.get_dataframe_craft._pre_process(df=input_df) expected_partial_vars = { "df_schema": "contintent: object\ncountry: object\ngdpPercap: int64", - "df_head": input_df.head().to_markdown(), + "df_sample": input_df.sample(5, replace=True, random_state=19).to_markdown(), } assert partial_vars == expected_partial_vars diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_explanation.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_explanation.py similarity index 98% rename from vizro-ai/tests/unit/vizro-ai/components/test_explanation.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_explanation.py index c6eb35c87..13866de73 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_explanation.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_explanation.py @@ -1,6 +1,6 @@ import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetCodeExplanation +from vizro_ai.plot.components import GetCodeExplanation @pytest.fixture diff --git a/vizro-ai/tests/unit/vizro-ai/components/test_visual_code.py b/vizro-ai/tests/unit/vizro-ai/plot/components/test_visual_code.py similarity index 73% rename from vizro-ai/tests/unit/vizro-ai/components/test_visual_code.py rename to vizro-ai/tests/unit/vizro-ai/plot/components/test_visual_code.py index 0ba9f19a7..e6bc021b8 100644 --- a/vizro-ai/tests/unit/vizro-ai/components/test_visual_code.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/components/test_visual_code.py @@ -1,13 +1,42 @@ +import pandas as pd import pytest from langchain_community.llms.fake import FakeListLLM -from vizro_ai.components import GetVisualCode +from vizro_ai.plot.components import GetVisualCode @pytest.fixture -def chart_types(): +def chart_type(): return "bar" +@pytest.fixture +def input_df(): + input_df = pd.DataFrame( + { + "contintent": ["Asia", "Asia", "America", "Europe", "Europe"], + "country": ["China", "India", "US", "UK", "Germany"], + "gdpPercap": [102, 110, 300, 200, 250], + } + ) + return input_df + + +@pytest.fixture +def df_schema(): + return "contintent: object\ncountry: object\ngdpPercap: int64" + + +@pytest.fixture +def df_sample(): + return """| | contintent | country | gdpPercap | +|---:|:-------------|:----------|------------:| +| 2 | America | US | 300 | +| 0 | Asia | China | 102 | +| 3 | Europe | UK | 200 | +| 4 | Europe | Germany | 250 | +| 2 | America | US | 300 |""" + + @pytest.fixture def df_code_1(): return """import pandas as pd @@ -85,9 +114,14 @@ def test_instantiation(self): def setup_method(self, fake_llm): self.get_visual_code = GetVisualCode(llm=fake_llm) - def test_pre_process(self, chart_types, df_code_1): - _, partial_vars = self.get_visual_code._pre_process(chart_types=chart_types, df_code=df_code_1) - assert partial_vars == {"chart_types": chart_types, "df_code": df_code_1} + def test_pre_process(self, chart_type, input_df, df_code_1, df_schema, df_sample): # noqa: PLR0913 + _, partial_vars = self.get_visual_code._pre_process(chart_type=chart_type, df_code=df_code_1, df=input_df) + assert partial_vars == { + "chart_type": chart_type, + "df_code": df_code_1, + "df_schema": df_schema, + "df_sample": df_sample, + } @pytest.mark.parametrize( "input,output,df_code", @@ -112,10 +146,11 @@ def test_fake_run( # noqa: PLR0913 output_visual_code_LLM_1, expected_final_output_1, df_code_1, - chart_types, + chart_type, + input_df, ): get_visual_code = GetVisualCode(fake_llm) processed_code = get_visual_code.run( - chain_input=output_visual_code_LLM_1, df_code=df_code_1, chart_types=chart_types + chain_input=output_visual_code_LLM_1, df_code=df_code_1, chart_type=chart_type, df=input_df ) assert processed_code == expected_final_output_1 diff --git a/vizro-ai/tests/unit/vizro-ai/utils/test_safeguard_code.py b/vizro-ai/tests/unit/vizro-ai/plot/utils/test_safeguard_code.py similarity index 98% rename from vizro-ai/tests/unit/vizro-ai/utils/test_safeguard_code.py rename to vizro-ai/tests/unit/vizro-ai/plot/utils/test_safeguard_code.py index 9bd58121c..5cf739c82 100644 --- a/vizro-ai/tests/unit/vizro-ai/utils/test_safeguard_code.py +++ b/vizro-ai/tests/unit/vizro-ai/plot/utils/test_safeguard_code.py @@ -1,7 +1,7 @@ import re import pytest -from vizro_ai.utils.safeguard import _safeguard_check +from vizro_ai.plot._utils._safeguard import _safeguard_check class TestMaliciousImports: diff --git a/vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py b/vizro-ai/tests/unit/vizro-ai/test_llm_models.py similarity index 85% rename from vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py rename to vizro-ai/tests/unit/vizro-ai/test_llm_models.py index 07d3822e5..bd5b085f8 100644 --- a/vizro-ai/tests/unit/vizro-ai/chains/test_llm_models.py +++ b/vizro-ai/tests/unit/vizro-ai/test_llm_models.py @@ -1,5 +1,5 @@ import pytest -from vizro_ai.chains._llm_models import _get_llm_model +from vizro_ai._llm_models import _get_llm_model @pytest.mark.parametrize( diff --git a/vizro-core/changelog.d/20240712_135610_huong_li_nguyen_chart_gallery.md b/vizro-core/changelog.d/20240712_135610_huong_li_nguyen_chart_gallery.md new file mode 100644 index 000000000..f1f65e73c --- /dev/null +++ b/vizro-core/changelog.d/20240712_135610_huong_li_nguyen_chart_gallery.md @@ -0,0 +1,48 @@ + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/LICENSE.txt b/vizro-core/examples/_chart-gallery/LICENSE.txt new file mode 100644 index 000000000..423838800 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 FT Interactive News + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vizro-core/examples/_chart-gallery/README.md b/vizro-core/examples/_chart-gallery/README.md new file mode 100644 index 000000000..74443d69a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/README.md @@ -0,0 +1,86 @@ +# Chart gallery dashboard + +This dashboard shows a gallery of charts. It includes guidance on when to use each chart type and sample Python code +to create them using [Plotly](https://plotly.com/python/) and [Vizro](https://vizro.mckinsey.com/). + +Inspired by the [FT Visual Vocabulary](https://github.com/Financial-Times/chart-doctor/blob/main/visual-vocabulary/README.md): +FT Graphics: Alan Smith, Chris Campbell, Ian Bott, Liz Faunce, Graham Parrish, Billy Ehrenberg, Paul McCallum, Martin Stabe. + +## Chart types + +The dashboard is still in development. Below is an overview of the chart types for which a completed page is available. + +| Chart Type | Status | Category | +| --------------------- | ------ | ------------------------ | +| Arc | ❌ | Part-to-whole | +| Area | ❌ | Time | +| Bar | ✅ | Magnitude | +| Barcode | ❌ | Distribution | +| Beeswarm | ❌ | Distribution | +| Boxplot | ✅ | Distribution | +| Bubble | ❌ | Correlation, Magnitude | +| Bubble Map | ❌ | Spatial | +| Bubble Timeline | ❌ | Time | +| Bullet | ❌ | Magnitude | +| Bump | ❌ | Ranking | +| Butterfly | ✅ | Deviation, Distribution | +| Chord | ❌ | Flow | +| Choropleth | ✅ | Spatial | +| Column | ✅ | Magnitude, Time | +| Column-Line | ❌ | Correlation, Time | +| Connected Scatter | ❌ | Correlation, Time | +| Cumulative Curve | ❌ | Distribution | +| Diverging Bar | ❌ | Deviation | +| Diverging Stacked Bar | ❌ | Deviation | +| Donut | ✅ | Part-to-whole | +| Dot Map | ❌ | Spatial | +| Dot Plot | ❌ | Distribution | +| Fan | ❌ | Time | +| Flow Map | ❌ | Spatial | +| Funnel | ❌ | Part-to-whole | +| Gantt | ❌ | Time | +| Gridplot | ❌ | Part-to-whole | +| Heatmap | ❌ | Time | +| Heatmap-Matrix | ❌ | Correlation | +| Histogram | ❌ | Distribution | +| Line | ✅ | Time | +| Lollipop | ❌ | Ranking, Magnitude | +| Marimekko | ❌ | Magnitude, Part-to-whole | +| Network | ❌ | Flow | +| Ordered Bar | ✅ | Ranking | +| Ordered Bubble | ❌ | Ranking | +| Ordered Column | ✅ | Ranking | +| Paired Bar | ❌ | Magnitude | +| Paired Column | ❌ | Magnitude | +| Parallel Coordinates | ❌ | Magnitude | +| Pictogram | ❌ | Magnitude | +| Pie | ✅ | Part-to-whole | +| Radar | ❌ | Magnitude | +| Radial | ❌ | Magnitude | +| Sankey | ✅ | Flow | +| Scatter | ✅ | Correlation | +| Scatter Matrix | ❌ | Correlation | +| Slope | ❌ | Ranking, Time | +| Sparkline | ❌ | Time | +| Stacked Bar | ❌ | Part-to-whole | +| Stacked Column | ❌ | Part-to-whole | +| Stepped Line | ❌ | Ranking | +| Surplus-Deficit-Line | ❌ | Deviation | +| Treemap | ✅ | Part-to-whole | +| Venn | ❌ | Part-to-whole | +| Violin | ✅ | Distribution | +| Waterfall | ❌ | Part-to-whole, Flow | + +To contribute a chart, follow the steps below: + +1. Place an `svg` file named after the chart type in the `assets` folder if not already available. +2. Create a new page for the chart type in `pages.py` and a code sample in `pages/examples`. Refer to existing pages for guidance. +3. Add any new datasets to `pages/_page_utils.py`. +4. Remove the page from `incomplete_pages` in the relevant `ChartGroup`(s) in `chart_groups.py`. +5. Update this `README.md` with the new chart type. + +## How to run the example locally + +1. If you have `hatch` set up, run the example with the command `hatch run example _chart-gallery`. + Otherwise, with a virtual Python environment activated, run `pip install -r requirements.txt` and then `python app.py`. +2. You should now be able to access the app locally via http://127.0.0.1:8050/. diff --git a/vizro-core/examples/_chart-gallery/app.py b/vizro-core/examples/_chart-gallery/app.py new file mode 100644 index 000000000..fcbd48ac8 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/app.py @@ -0,0 +1,111 @@ +"""App configuration for chart gallery dashboard.""" + +from typing import List, Union + +import vizro.models as vm +from chart_groups import ALL_CHART_GROUP, CHART_GROUPS, ChartGroup, IncompletePage +from custom_components import FlexContainer, Markdown +from vizro import Vizro + + +def make_chart_card(page: Union[vm.Page, IncompletePage]) -> vm.Card: + """Makes a card with svg icon, linked to the right page if page is complete. + + Args: + page: page to make card for + + Returns: card with svg icon, linked to the right page if page is complete. + + """ + # There's one SVG per chart title, so that e.g. pages distribution-butterfly and deviation-butterfly, which both + # have title "Butterfly", correspond to butterfly.svg. + # Incomplete pages have page.path = "" so won't be linked to here. + svg_name = page.title.lower().replace(" ", "-") + return vm.Card( + text=f""" + ![](assets/images/charts/{svg_name}.svg#chart-icon) + + #### {page.title} + """, + href=page.path, + ) + + +def make_homepage_container(chart_group: ChartGroup) -> vm.Container: + """Makes a container with cards for each completed and incomplete chart in chart_group. + + Args: + chart_group: group of charts to make container for. + + Returns: container with cards for each chart in chart_group. + + """ + # Pages are sorted in title's alphabetical order and deduplicated so that e.g. pages distribution-butterfly and + # deviation-butterfly, which both have title "Butterfly", correspond to a single card. + return vm.Container( + title=chart_group.name, + layout=vm.Layout(grid=[[0, 1, 1, 1]], col_gap="40px"), + components=[ + Markdown(text=chart_group.intro_text, classname="intro-text"), + FlexContainer( + components=[ + make_chart_card(page) + for page in sorted( + _remove_duplicates(chart_group.pages + chart_group.incomplete_pages), + key=lambda page: page.title, + ) + ], + ), + ], + ) + + +def _remove_duplicates(pages: List[Union[vm.Page, IncompletePage]]) -> List[Union[vm.Page, IncompletePage]]: + # Deduplicate pages that have the same title. Using reversed means that the page that is kept is the first one + # in the dashboard. This will be the one that the card on the homepage links to. + return list({page.title: page for page in reversed(pages)}.values()) + + +def make_navlink(chart_group: ChartGroup) -> vm.NavLink: + """Makes a navlink with icon and links to every complete page within chart_group. + + Args: + chart_group: chart_group to make a navlink for. + + Returns: navlink for chart_group. + + """ + # Pages are sorted in alphabetical order within each chart group. + return vm.NavLink( + label=chart_group.name, + pages={chart_group.name: [page.id for page in sorted(chart_group.pages, key=lambda page: page.title)]}, + icon=chart_group.icon, + ) + + +homepage = vm.Page( + title="Overview", + components=[ + vm.Tabs(tabs=[make_homepage_container(chart_group) for chart_group in [ALL_CHART_GROUP, *CHART_GROUPS]]), + ], +) + +# TODO: consider whether each chart group should have its own individual homepage, +# e.g. at http://localhost:8050/deviation/. This could just repeat the content of the tab from the homepage and would +# work nicely with the hierarchical navigation. +dashboard = vm.Dashboard( + # ALL_CHART_GROUP.pages has duplicated pages, e.g. both distribution-butterfly and deviation-butterfly. + title="Vizro - Chart Gallery", + pages=[homepage, *ALL_CHART_GROUP.pages], + navigation=vm.Navigation( + nav_selector=vm.NavBar( + items=[ + vm.NavLink(label="Overview", pages=[homepage.id], icon="Home"), + ] + + [make_navlink(chart_group) for chart_group in CHART_GROUPS] + ) + ), +) + +if __name__ == "__main__": + Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/assets/app.svg b/vizro-core/examples/_chart-gallery/assets/app.svg new file mode 100644 index 000000000..9d07d6372 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/app.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/css/custom.css b/vizro-core/examples/_chart-gallery/assets/css/custom.css new file mode 100644 index 000000000..fa65d5e0b --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/css/custom.css @@ -0,0 +1,76 @@ +#page-header { + padding-left: 8px; +} + +#left-main { + width: 288px; +} + +img[src*="#chart-icon"] { + width: 100%; +} + +.code-clipboard { + font-size: 20px; + position: absolute; + right: 14px; + top: 12px; +} + +.code-clipboard-container { + background: var(--surfaces-bg-card); + font-family: monospace; + max-height: 500px; + overflow: auto; + padding: 1rem; + position: relative; +} + +.code-clipboard-container::-webkit-scrollbar-thumb { + border-color: var(--surfaces-bg-card); +} + +.flex-container { + display: flex; + flex-wrap: wrap; + gap: 20px; +} + +.flex-container h4 { + color: var(--text-secondary); + margin: 0; + padding-top: 12px; + text-align: center; +} + +.flex-container .card { + height: 228px; + opacity: 0.3; + width: 176px; +} + +.flex-container .card-nav { + opacity: 1; +} + +.flex-container .card img { + width: 100%; +} + +.intro-text { + border-left: 4px solid var(--text-secondary); + padding: 12px; +} + +.intro-text p { + font-size: 16px; + line-height: 20px; +} + +/* +.navbar .nav-link.active { + border-right: 1px solid var(--border-enabled); + color: var(--text-primary); + width: 64px; +} +*/ diff --git a/vizro-core/examples/_chart-gallery/assets/favicon.ico b/vizro-core/examples/_chart-gallery/assets/favicon.ico new file mode 100644 index 000000000..240c9f541 Binary files /dev/null and b/vizro-core/examples/_chart-gallery/assets/favicon.ico differ diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/arc.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/arc.svg new file mode 100644 index 000000000..35a5c81f8 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/arc.svg @@ -0,0 +1,25 @@ + + + + Group 6 Copy 34 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/area.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/area.svg new file mode 100644 index 000000000..f51c7d036 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/area.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bar.svg new file mode 100644 index 000000000..b2491fb52 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bar.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/barcode.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/barcode.svg new file mode 100644 index 000000000..cbe2ffc23 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/barcode.svg @@ -0,0 +1,136 @@ + + + + Group 6 Copy 14 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/beeswarm.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/beeswarm.svg new file mode 100644 index 000000000..7d29af852 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/beeswarm.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/boxplot.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/boxplot.svg new file mode 100644 index 000000000..224deddd8 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/boxplot.svg @@ -0,0 +1,78 @@ + + + + Group 6 Copy 15 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-map.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-map.svg new file mode 100644 index 000000000..cade9887d --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-map.svg @@ -0,0 +1,467 @@ + + + + Group 6 Copy 4 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-timeline.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-timeline.svg new file mode 100644 index 000000000..b98e5af32 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble-timeline.svg @@ -0,0 +1,35 @@ + + + + Group 6 Copy 22 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bubble.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble.svg new file mode 100644 index 000000000..a939659fc --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bubble.svg @@ -0,0 +1,32 @@ + + + + Group 6 Copy 3 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bullet.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bullet.svg new file mode 100644 index 000000000..d7f708173 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bullet.svg @@ -0,0 +1,41 @@ + + + + Group 6 Copy 33 + Created with Sketch. + + + + + + + + + + + + + + + + + + 0 + + + 2 + + + 4 + + + 6 + + + 8 + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/bump.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/bump.svg new file mode 100644 index 000000000..509f6cb0a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/bump.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/butterfly.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/butterfly.svg new file mode 100644 index 000000000..5f0281a60 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/butterfly.svg @@ -0,0 +1,27 @@ + + + + Group 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/chord.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/chord.svg new file mode 100644 index 000000000..8eecba457 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/chord.svg @@ -0,0 +1,23 @@ + + + + Group 6 Copy 29 + Created with Sketch. + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/choropleth.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/choropleth.svg new file mode 100644 index 000000000..6a00da7bd --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/choropleth.svg @@ -0,0 +1,126 @@ + + + + Group 6 Copy 44 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/column-line.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/column-line.svg new file mode 100644 index 000000000..c4af7f626 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/column-line.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 4 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/column.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/column.svg new file mode 100644 index 000000000..ae4a75e22 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/column.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/connected-scatter.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/connected-scatter.svg new file mode 100644 index 000000000..b3e46261a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/connected-scatter.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/cumulative-curve.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/cumulative-curve.svg new file mode 100644 index 000000000..93cb42b5b --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/cumulative-curve.svg @@ -0,0 +1,18 @@ + + + + Group 6 Copy 17 + Created with Sketch. + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-bar.svg new file mode 100644 index 000000000..bb0b80977 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-bar.svg @@ -0,0 +1,21 @@ + + + + Group 6 Copy + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-stacked-bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-stacked-bar.svg new file mode 100644 index 000000000..42cbdadc7 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/diverging-stacked-bar.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/donut.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/donut.svg new file mode 100644 index 000000000..df72ab3ca --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/donut.svg @@ -0,0 +1,25 @@ + + + + Group 6 Copy 24 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/dot-map.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/dot-map.svg new file mode 100644 index 000000000..f73372105 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/dot-map.svg @@ -0,0 +1,265 @@ + + + + Group 6 Copy 5 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/dot-plot.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/dot-plot.svg new file mode 100644 index 000000000..6640e2813 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/dot-plot.svg @@ -0,0 +1,30 @@ + + + + Group 6 Copy 13 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/fan.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/fan.svg new file mode 100644 index 000000000..cd0c938c3 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/fan.svg @@ -0,0 +1,21 @@ + + + + Group 6 Copy 39 + Created with Sketch. + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/flow-map.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/flow-map.svg new file mode 100644 index 000000000..238505d2a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/flow-map.svg @@ -0,0 +1,80 @@ + + + + Group 6 Copy 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/funnel.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/funnel.svg new file mode 100644 index 000000000..4d33728ad --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/funnel.svg @@ -0,0 +1,19 @@ + + + + Group 6 Copy 36 + Created with Sketch. + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/gantt.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/gantt.svg new file mode 100644 index 000000000..543e0ed85 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/gantt.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 21 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/gridplot.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/gridplot.svg new file mode 100644 index 000000000..276deb3a1 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/gridplot.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap-matrix.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap-matrix.svg new file mode 100644 index 000000000..84c13f73a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap-matrix.svg @@ -0,0 +1,179 @@ + + + + Group 6 Copy 40 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + v1 + + + 0.1 + + + 0.5 + + + v1 + + + v2 + + + v3 + + + v4 + + + v5 + + + v2 + + + v3 + + + v4 + + + v5 + + + 1.0 + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap.svg new file mode 100644 index 000000000..fb40db538 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/heatmap.svg @@ -0,0 +1,27 @@ + + + + Group 6 Copy 5 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/histogram.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/histogram.svg new file mode 100644 index 000000000..b61f86c45 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/histogram.svg @@ -0,0 +1,25 @@ + + + + Group 6 Copy 12 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/line.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/line.svg new file mode 100644 index 000000000..e6a042d24 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/line.svg @@ -0,0 +1,17 @@ + + + + Group 6 Copy 19 + Created with Sketch. + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/lollipop.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/lollipop.svg new file mode 100644 index 000000000..f04bc8ed7 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/lollipop.svg @@ -0,0 +1,26 @@ + + + + Group 6 Copy 8 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/marimekko.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/marimekko.svg new file mode 100644 index 000000000..38fb0de77 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/marimekko.svg @@ -0,0 +1,27 @@ + + + + Group 6 Copy 26 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/network.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/network.svg new file mode 100644 index 000000000..604485ef0 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/network.svg @@ -0,0 +1,44 @@ + + + + Group 6 Copy 32 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bar.svg new file mode 100644 index 000000000..342907fe4 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bar.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 10 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bubble.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bubble.svg new file mode 100644 index 000000000..bef4e6929 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-bubble.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 7 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-column.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-column.svg new file mode 100644 index 000000000..fca0509d6 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/ordered-column.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 11 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/paired-bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/paired-bar.svg new file mode 100644 index 000000000..2f75406e7 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/paired-bar.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/paired-column.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/paired-column.svg new file mode 100644 index 000000000..925d74f7e --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/paired-column.svg @@ -0,0 +1 @@ + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/parallel-coordinates.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/parallel-coordinates.svg new file mode 100644 index 000000000..16c16d68c --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/parallel-coordinates.svg @@ -0,0 +1,26 @@ + + + + Group 6 Copy 43 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/pictogram.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/pictogram.svg new file mode 100644 index 000000000..c7a787b0a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/pictogram.svg @@ -0,0 +1,63 @@ + + + + Group 6 Copy 42 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/pie.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/pie.svg new file mode 100644 index 000000000..c32e7c8b1 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/pie.svg @@ -0,0 +1,29 @@ + + + + Group 6 Copy 23 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/radar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/radar.svg new file mode 100644 index 000000000..5906b0740 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/radar.svg @@ -0,0 +1,33 @@ + + + + Group 6 Copy 38 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/radial.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/radial.svg new file mode 100644 index 000000000..0b8f96a90 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/radial.svg @@ -0,0 +1,18 @@ + + + + Group 6 Copy 37 + Created with Sketch. + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/sankey.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/sankey.svg new file mode 100644 index 000000000..9e9ad9d68 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/sankey.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 30 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/scatter-matrix.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/scatter-matrix.svg new file mode 100644 index 000000000..add1a0995 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/scatter-matrix.svg @@ -0,0 +1,176 @@ + + + + Group 6 Copy 41 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + v1 + + + v2 + + + v3 + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/scatter.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/scatter.svg new file mode 100644 index 000000000..3993f505f --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/scatter.svg @@ -0,0 +1,56 @@ + + + + Group 6 Copy 2 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/slope.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/slope.svg new file mode 100644 index 000000000..01b770af5 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/slope.svg @@ -0,0 +1,32 @@ + + + + Group 6 Copy 9 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/sparkline.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/sparkline.svg new file mode 100644 index 000000000..ed269cd53 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/sparkline.svg @@ -0,0 +1,33 @@ + + + + Group 6 Copy 35 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-bar.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-bar.svg new file mode 100644 index 000000000..432a2e4a6 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-bar.svg @@ -0,0 +1,27 @@ + + + + Group 6 Copy 25 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-column.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-column.svg new file mode 100644 index 000000000..7afbe494c --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/stacked-column.svg @@ -0,0 +1,27 @@ + + + + Group 6 Copy 25 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/stepped-line.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/stepped-line.svg new file mode 100644 index 000000000..5bb9c361e --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/stepped-line.svg @@ -0,0 +1,19 @@ + + + + Group 6 Copy 18 + Created with Sketch. + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/surplus-deficit-filled-line.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/surplus-deficit-filled-line.svg new file mode 100644 index 000000000..5f6baaf23 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/surplus-deficit-filled-line.svg @@ -0,0 +1,29 @@ + + + + Group 6 Copy 6 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/treemap.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/treemap.svg new file mode 100644 index 000000000..a09929e94 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/treemap.svg @@ -0,0 +1,24 @@ + + + + Group 6 Copy 27 + Created with Sketch. + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/venn.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/venn.svg new file mode 100644 index 000000000..753928cf7 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/venn.svg @@ -0,0 +1,18 @@ + + + + Group 6 Copy 28 + Created with Sketch. + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/violin.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/violin.svg new file mode 100644 index 000000000..537b0ecfe --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/violin.svg @@ -0,0 +1,131 @@ + + + + Group 6 Copy 16 + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/images/charts/waterfall.svg b/vizro-core/examples/_chart-gallery/assets/images/charts/waterfall.svg new file mode 100644 index 000000000..3b9072189 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/images/charts/waterfall.svg @@ -0,0 +1,20 @@ + + + + Group 6 Copy 31 + Created with Sketch. + + + + + + + + + + + + + + + diff --git a/vizro-core/examples/_chart-gallery/assets/logo.svg b/vizro-core/examples/_chart-gallery/assets/logo.svg new file mode 100644 index 000000000..0904b87de --- /dev/null +++ b/vizro-core/examples/_chart-gallery/assets/logo.svg @@ -0,0 +1,3 @@ + + + diff --git a/vizro-core/examples/_chart-gallery/chart_groups.py b/vizro-core/examples/_chart-gallery/chart_groups.py new file mode 100644 index 000000000..972aaf5d1 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/chart_groups.py @@ -0,0 +1,263 @@ +"""Defines chart groups.""" + +import itertools +from dataclasses import dataclass +from typing import List + +import pages.correlation +import pages.deviation +import pages.distribution +import pages.flow +import pages.magnitude +import pages.part_to_whole +import pages.ranking +import pages.spatial +import pages.time +import vizro.models as vm + + +class IncompletePage: + """Fake vm.Page-like class. + + This has the properties required to make it function sufficiently like a page when generating the navigation cards. + Only the title is configurable; path is fixed to "". + """ + + def __init__(self, title): # noqa: D107 + self.title = title + + @property + def path(self): # noqa: D102 + return "" + + +@dataclass +class ChartGroup: + """Represents a group of charts like "Deviation".""" + + name: str + pages: List[vm.Page] + incomplete_pages: List[IncompletePage] + intro_text: str + icon: str = "" # ALL_CHART_GROUP is the only one that doesn't require an icon. + + +deviation_intro_text = """ +Deviation enables you to draw attention to variations (+/-) from a fixed reference point. +Often this reference point is zero, but you might also show a target or a long term average. +You can also use deviation to express a positive, neutral or negative sentiment. +""" +deviation_chart_group = ChartGroup( + name="Deviation", + pages=pages.deviation.pages, + incomplete_pages=[ + IncompletePage(title="Diverging bar"), + IncompletePage("Diverging stacked bar"), + IncompletePage(title="Surplus deficit filled line"), + ], + icon="Contrast Square", + intro_text=deviation_intro_text, +) + + +correlation_intro_text = """ +Correlation helps you show the relationship between two or more variables. It is important that you +make it clear to your audience whether or not the relationship is causal, i.e., whether one causes the +other. +""" +correlation_chart_group = ChartGroup( + name="Correlation", + pages=pages.correlation.pages, + incomplete_pages=[ + IncompletePage("Scatter matrix"), + IncompletePage("Column line"), + IncompletePage("Connected scatter"), + IncompletePage("Heatmap matrix"), + IncompletePage("Bubble"), + ], + icon="Bubble Chart", + intro_text=correlation_intro_text, +) + + +ranking_intro_text = """ +Ranking enables you to present items in an ordered list. Use this when you want to highlight the +position of an item rather than its absolute or relative value. You might want to emphasize the most +interesting points with highlighting or labels to ensure the reader understands what matters most. +""" +ranking_chart_group = ChartGroup( + name="Ranking", + pages=pages.ranking.pages, + incomplete_pages=[ + IncompletePage("Ordered bubble"), + IncompletePage("Slope"), + IncompletePage("Lollipop"), + IncompletePage("Stepped line"), + IncompletePage("Bump"), + ], + icon="Stacked Bar Chart", + intro_text=ranking_intro_text, +) + + +distribution_intro_text = """ +Distribution helps you to present all the possible values (or intervals) of your data and how often they +occur. You can organize the data to show the number or percentage of items in a specified group, what shape +the group takes, where the center lies, and how much variability there is in the data. This shape +(or _skew_) of a distribution can be a powerful way for you to highlight either the existence or lack of +uniformity or equality in the data. +""" +distribution_chart_group = ChartGroup( + name="Distribution", + pages=pages.distribution.pages, + incomplete_pages=[ + IncompletePage("Histogram"), + IncompletePage("Dot plot"), + IncompletePage("Barcode"), + IncompletePage("Cumulative curve"), + IncompletePage("Beeswarm"), + ], + icon="Waterfall Chart", + intro_text=distribution_intro_text, +) + +magnitude_intro_text = """ +Magnitude allows you to emphasize size comparisons of **counted** items in your data set. You can show +relative comparisons (whether something is larger or smaller) or absolute differences (where the nuances +are most interesting). Typically, you will use magnitude for actual numbers versus calculated rates or +percentages. +""" +magnitude_chart_group = ChartGroup( + name="Magnitude", + pages=pages.magnitude.pages, + incomplete_pages=[ + IncompletePage("Paired column"), + IncompletePage("Paired bar"), + IncompletePage("Marimekko"), + IncompletePage("Bubble"), + IncompletePage("Lollipop"), + IncompletePage("Radar"), + IncompletePage("Parallel coordinates"), + IncompletePage("Pictogram"), + IncompletePage("Bullet"), + IncompletePage("Radial"), + ], + icon="Bar Chart", + intro_text=magnitude_intro_text, +) + +time_intro_text = """ +Time helps you draw attention to important trends emerging over a specified period. The time period you +select could be as short as seconds or as long as centuries. What matters most is selecting the correct +period of time to best show your audience the message they need to take away. +""" +time_chart_group = ChartGroup( + name="Time", + pages=pages.time.pages, + incomplete_pages=[ + IncompletePage("Gantt"), + IncompletePage("Column line"), + IncompletePage("Slope"), + IncompletePage("Fan"), + IncompletePage("Area"), + IncompletePage("Connected scatter"), + IncompletePage("Heatmap"), + IncompletePage("Bubble timeline"), + IncompletePage("Sparkline"), + ], + icon="Timeline", + intro_text=time_intro_text, +) + + +part_to_whole_intro_text = """ +Part-to-whole helps you show how one whole item breaks down into its component parts. If you consider the +size of the parts to be most important, a magnitude chart may be more appropriate. +""" +part_to_whole_chart_group = ChartGroup( + name="Part-to-whole", + pages=pages.part_to_whole.pages, + incomplete_pages=[ + IncompletePage("Stacked bar"), + IncompletePage("Stacked column"), + IncompletePage("Marimekko"), + IncompletePage("Funnel"), + IncompletePage("Arc"), + IncompletePage("Gridplot"), + IncompletePage("Venn"), + IncompletePage("Waterfall"), + ], + icon="Donut Small", + intro_text=part_to_whole_intro_text, +) + +flow_intro_text = """ +With flow charts, you can highlight the quantity or the intensity of the movement between more than one +state or condition. The flow might be steps in a logical sequence or movement between different geographical +locations. +""" +flow_chart_group = ChartGroup( + name="Flow", + pages=pages.flow.pages, + incomplete_pages=[ + IncompletePage("Waterfall"), + IncompletePage("Chord"), + IncompletePage("Network"), + ], + icon="Air", + intro_text=flow_intro_text, +) + +spatial_intro_text = """ +Spatial charts allow you to demonstrate precise locations or geographical patterns in your data. +""" +spatial_chart_group = ChartGroup( + name="Spatial", + pages=pages.spatial.pages, + incomplete_pages=[ + IncompletePage("Dot map"), + IncompletePage("Flow map"), + IncompletePage("Bubble map"), + ], + icon="Map", + intro_text=spatial_intro_text, +) + + +CHART_GROUPS = [ + deviation_chart_group, + correlation_chart_group, + ranking_chart_group, + distribution_chart_group, + magnitude_chart_group, + time_chart_group, + part_to_whole_chart_group, + flow_chart_group, + spatial_chart_group, +] + +all_intro_text = """ +This dashboard shows a gallery of charts. It includes guidance on when to use each chart type and sample Python code +to create them using [Plotly](https://plotly.com/python/) and [Vizro](https://github.com/mckinsey/vizro). + +Inspired by the +[FT Visual Vocabulary](https://github.com/Financial-Times/chart-doctor/blob/main/visual-vocabulary/README.md) +and [the Graphic Continuum](https://www.informationisbeautifulawards.com/showcase/611-the-graphic-continuum): + +- FT Graphic: Alan Smith, Chris Campbell, Ian Bott, Liz Faunce, Graham Parrish, Billy Ehrenberg, Paul McCallum, +Martin Stabe. + +- The Graphic Continuum: Jon Swabish and Severino Ribecca + + +""" + + +# This contains all pages used across all chart groups, without de-duplicating. De-duplication is done where required +# by remove_duplicates. +ALL_CHART_GROUP = ChartGroup( + name="All", + pages=list(itertools.chain(*(chart_group.pages for chart_group in CHART_GROUPS))), + incomplete_pages=list(itertools.chain(*(chart_group.incomplete_pages for chart_group in CHART_GROUPS))), + intro_text=all_intro_text, +) diff --git a/vizro-core/examples/_chart-gallery/custom_charts.py b/vizro-core/examples/_chart-gallery/custom_charts.py new file mode 100644 index 000000000..7a971d7a5 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/custom_charts.py @@ -0,0 +1,91 @@ +"""Contains custom charts used inside the dashboard.""" + +from typing import List + +import pandas as pd +from plotly import graph_objects as go +from vizro.models.types import capture + + +# TODO: consider how this should be represented in the code example files. Since the code is copy and pasted +# it can get out of sync. But probably we don't want the docstrings in the short code snippet. +# Ultimately these charts will probably move to vizro.charts anyway. +@capture("graph") +def butterfly(data_frame: pd.DataFrame, x1: str, x2: str, y: str) -> go.Figure: + """Creates a custom butterfly chart using Plotly's go.Figure. + + A butterfly chart is a type of bar chart where two sets of bars are displayed back-to-back, often used to compare + two sets of data. + + Args: + data_frame (pd.DataFrame): The data source for the chart. + x1 (str): The name of the column in the data frame for the first set of bars (negative values). + x2 (str): The name of the column in the data frame for the second set of bars (positive values). + y (str): The name of the column in the data frame for the y-axis (categories). + + Returns: + go.Figure: A Plotly Figure object representing the butterfly chart. + + """ + fig = go.Figure() + fig.add_trace( + go.Bar( + x=-data_frame[x1], + y=data_frame[y], + orientation="h", + name=x1, + ) + ) + fig.add_trace( + go.Bar( + x=data_frame[x2], + y=data_frame[y], + orientation="h", + name=x2, + ) + ) + fig.update_layout(barmode="relative") + return fig + + +@capture("graph") +def sankey(data_frame: pd.DataFrame, source: str, target: str, value: str, labels: List[str]) -> go.Figure: + """Creates a custom sankey chart using Plotly's `go.Sankey`. + + A Sankey chart is a type of flow diagram where the width of the arrows is proportional to the flow rate. + It is used to visualize the flow of resources or data between different stages or categories. + + Args: + data_frame (pd.DataFrame): The data source for the chart. + source (str): The name of the column in the data frame for the source nodes. + target (str): The name of the column in the data frame for the target nodes. + value (str): The name of the column in the data frame for the values representing the flow between nodes. + labels (List[str]): A list of labels for the nodes. + + Returns: + go.Figure: A Plotly Figure object representing the Sankey chart. + + For detailed information on additional parameters and customization, refer to the Plotly documentation: + https://plotly.com/python/reference/sankey/ + + """ + fig = go.Figure( + data=[ + go.Sankey( + node={ + "pad": 16, + "thickness": 16, + "label": labels, + }, + link={ + "source": data_frame[source], + "target": data_frame[target], + "value": data_frame[value], + "label": labels, + "color": "rgba(205, 209, 228, 0.4)", + }, + ) + ] + ) + fig.update_layout(barmode="relative") + return fig diff --git a/vizro-core/examples/_chart-gallery/custom_components.py b/vizro-core/examples/_chart-gallery/custom_components.py new file mode 100644 index 000000000..b4843184c --- /dev/null +++ b/vizro-core/examples/_chart-gallery/custom_components.py @@ -0,0 +1,72 @@ +"""Contains custom components used inside the dashboard.""" + +from typing import Literal + +import dash_bootstrap_components as dbc +import vizro.models as vm +from dash import dcc, html + +try: + from pydantic.v1 import Field +except ImportError: # pragma: no cov + from pydantic import Field + + +# TODO: Fix font and color in code clipboard +class CodeClipboard(vm.VizroBaseModel): + """Code snippet with a copy to clipboard button.""" + + type: Literal["code_clipboard"] = "code_clipboard" + code: str + language: str = "" + + def build(self): + """Returns the code clipboard component inside an accordion.""" + markdown_code = "\n".join([f"```{self.language}", self.code, "```"]) + return dbc.Accordion( + [ + dbc.AccordionItem( + html.Div( + [ + dcc.Markdown(markdown_code, id=self.id), + dcc.Clipboard(target_id=self.id, className="code-clipboard"), + ], + className="code-clipboard-container", + ), + title="SHOW CODE", + ) + ], + start_collapsed=False, + ) + + +class Markdown(vm.VizroBaseModel): + """Markdown component.""" + + type: Literal["markdown"] = "markdown" + text: str = Field( + ..., description="Markdown string to create card title/text that should adhere to the CommonMark Spec." + ) + classname: str = "" + + def build(self): + """Returns a markdown component with an optional classname.""" + return dcc.Markdown(id=self.id, children=self.text, dangerously_allow_html=False, className=self.classname) + + +class FlexContainer(vm.Container): + """Custom flex `Container`.""" + + type: Literal["flex_container"] = "flex_container" + title: str = None # Title exists in vm.Container but we don't want to use it here. + + def build(self): + """Returns a flex container.""" + return html.Div( + id=self.id, children=[component.build() for component in self.components], className="flex-container" + ) + + +vm.Container.add_type("components", FlexContainer) +vm.Container.add_type("components", Markdown) +vm.Page.add_type("components", CodeClipboard) diff --git a/vizro-core/examples/_chart-gallery/pages/__init__.py b/vizro-core/examples/_chart-gallery/pages/__init__.py new file mode 100644 index 000000000..cc121d915 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/__init__.py @@ -0,0 +1,5 @@ +# TODO: eventually deduplicate page generation into a function rather than copying and pasting across files? +# TODO: think about the best way to do code examples, e.g. +# - do we want full dashboard example or plot-only example? +# - or both? Could be done using a toggle switch or multiple tabs. +# - a link to py.cafe showing the dashboard code? diff --git a/vizro-core/examples/_chart-gallery/pages/_factories.py b/vizro-core/examples/_chart-gallery/pages/_factories.py new file mode 100644 index 000000000..57baba868 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/_factories.py @@ -0,0 +1,76 @@ +"""Contains reusable page functions to create identical content with a different `id`. + +Note: Since each page can only belong to one navigation group, we need a new page with a unique ID for +each chart type used in different groups. +""" + +import vizro.models as vm +import vizro.plotly.express as px +from custom_charts import butterfly + +from pages._pages_utils import PAGE_GRID, ages, make_code_clipboard_from_py_file, tips_agg + + +def column_factory(group: str): + """Reusable function to create the page content for the column chart with a unique ID.""" + return vm.Page( + id=f"{group}-column", + path=f"{group}/column", + title="Column", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + #### What is a column chart? + + A column chart is a vertical bar chart with column lengths varying by the categorical value they + represent, using a y-axis scale starting from zero. + +   + + #### When should I use it? + + Use a column chart to help your audience compare sizes and identify patterns in categorical data, + such as **how many?** in each category. Arrange the columns in any order to fit the message you want + to emphasize. Ensure clear labeling, especially with many columns, and consider using a legend or + abbreviations with fuller descriptions below. + """ + ), + vm.Graph( + figure=px.bar(tips_agg, y="total_bill", x="day", category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]}) + ), + make_code_clipboard_from_py_file("column.py"), + ], + ) + + +def butterfly_factory(group: str): + """Reusable function to create the page content for the butterfly chart with a unique ID.""" + return vm.Page( + id=f"{group}-butterfly", + path=f"{group}/butterfly", + title="Butterfly", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a butterfly chart? + + A butterfly chart (also called a tornado chart) is a bar chart for displaying two sets of data series + side by side. + +   + + #### When should I use it? + + Use a butterfly chart when you wish to emphasize the comparison between two data sets sharing the same + parameters. Sharing this chart with your audience will help them see at a glance how two groups differ + within the same parameters. You can also **stack** two bars on each side if you wish to divide your + categories. + """ + ), + vm.Graph(figure=butterfly(ages, x1="Male", x2="Female", y="Age")), + make_code_clipboard_from_py_file("butterfly.py"), + ], + ) diff --git a/vizro-core/examples/_chart-gallery/pages/_pages_utils.py b/vizro-core/examples/_chart-gallery/pages/_pages_utils.py new file mode 100644 index 000000000..f3b776c73 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/_pages_utils.py @@ -0,0 +1,44 @@ +"""Contains reusable data sets and constants.""" + +from pathlib import Path + +import black +import pandas as pd +import vizro.plotly.express as px +from custom_components import CodeClipboard + + +def make_code_clipboard_from_py_file(filepath: str): + # Black doesn't yet have a Python API, so format_str might not work at some point in the future. + # https://black.readthedocs.io/en/stable/faq.html#does-black-have-an-api + filepath = Path(__file__).parents[1] / "pages/examples" / filepath + return CodeClipboard( + code=black.format_str(filepath.read_text(encoding="utf-8"), mode=black.Mode(line_length=80)), + language="python", + ) + + +PAGE_GRID = [[0, 0, 0, 0, 0, 0, 0]] * 2 + [[1, 1, 1, 1, 2, 2, 2]] * 5 + +# DATA -------------------------------------------------------------- +gapminder = px.data.gapminder() +gapminder_2007 = gapminder.query("year == 2007") +iris = px.data.iris() +stocks = px.data.stocks() +tips = px.data.tips() +tips_agg = tips.groupby("day").agg({"total_bill": "sum"}).reset_index() +tips_sorted = tips.groupby("day").agg({"total_bill": "sum"}).sort_values("total_bill").reset_index() +ages = pd.DataFrame( + { + "Age": ["0-19", "20-29", "30-39", "40-49", "50-59", ">=60"], + "Male": [800, 2000, 4200, 5000, 2100, 800], + "Female": [1000, 3000, 3500, 3800, 3600, 700], + } +) +sankey_data = pd.DataFrame( + { + "Origin": [0, 1, 0, 2, 3, 3], + "Destination": [2, 3, 3, 4, 4, 5], + "Value": [8, 4, 2, 8, 4, 2], + } +) diff --git a/vizro-core/examples/_chart-gallery/pages/correlation.py b/vizro-core/examples/_chart-gallery/pages/correlation.py new file mode 100644 index 000000000..6a2b96c38 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/correlation.py @@ -0,0 +1,36 @@ +"""Correlation charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._pages_utils import PAGE_GRID, iris, make_code_clipboard_from_py_file + +scatter = vm.Page( + title="Scatter", + path="correlation/scatter", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a scatter chart? + + A scatter plot is a two-dimensional data visualization using dots to represent the values obtained for two + different variables - one plotted along the x-axis and the other plotted along the y-axis. + +   + + #### When should I use it? + + Use scatter plots when you want to show the relationship between two variables. Scatter plots are sometimes + called _Correlation plots_ because they show how two variables are correlated. Scatter plots are ideal when + you have paired numerical data and you want to see if one variable impacts the other. However, do remember + that correlation is not causation. Make sure your audience does not draw the wrong conclusions. + """ + ), + vm.Graph(figure=px.scatter(iris, x="sepal_width", y="sepal_length", color="species")), + make_code_clipboard_from_py_file("scatter.py"), + ], +) + +pages = [scatter] diff --git a/vizro-core/examples/_chart-gallery/pages/deviation.py b/vizro-core/examples/_chart-gallery/pages/deviation.py new file mode 100644 index 000000000..93b4f731f --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/deviation.py @@ -0,0 +1,7 @@ +"""Deviation charts.""" + +from pages._factories import butterfly_factory + +butterfly = butterfly_factory("deviation") + +pages = [butterfly] diff --git a/vizro-core/examples/_chart-gallery/pages/distribution.py b/vizro-core/examples/_chart-gallery/pages/distribution.py new file mode 100644 index 000000000..df57c2502 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/distribution.py @@ -0,0 +1,82 @@ +"""Distribution charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._factories import butterfly_factory +from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file, tips + +violin = vm.Page( + title="Violin", + path="distribution/violin", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a violin chart? + + A violin chart is similar to a box plot, but works better for visualizing more complex distributions and + their probability density at different values. + +   + + #### When should I use it? + + Use this chart to go beyond the simple box plot and show the distribution shape of the data, the + inter-quartile range, the confidence intervals and the median. + """ + ), + vm.Graph( + figure=px.violin( + tips, + y="total_bill", + x="day", + color="day", + box=True, + ) + ), + make_code_clipboard_from_py_file("violin.py"), + ], +) + +boxplot = vm.Page( + title="Boxplot", + path="distribution/boxplot", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a boxplot? + + A box plot (also known as whisker plot) provides a visual display of multiple datasets, + indicating the median (center) and the range of the data for each. + +   + + #### When should I use it? + + Choose a box plot when you need to summarize distributions between many groups or datasets. It takes up + less space than many other charts. + + Create boxes to display the median, and the upper and lower quartiles. Add whiskers to highlight + variability outside the upper and lower quartiles. You can add outliers as dots beyond, but in line with + the whiskers. + """ + ), + vm.Graph( + figure=px.box( + tips, + y="total_bill", + x="day", + color="day", + ) + ), + make_code_clipboard_from_py_file("boxplot.py"), + ], +) + +butterfly = butterfly_factory("distribution") + +pages = [violin, boxplot, butterfly] diff --git a/vizro-core/examples/_chart-gallery/pages/examples/__init__.py b/vizro-core/examples/_chart-gallery/pages/examples/__init__.py new file mode 100644 index 000000000..b1cac5fb9 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/__init__.py @@ -0,0 +1 @@ +"""Contains code examples inserted into `CodeClipboard`.""" diff --git a/vizro-core/examples/_chart-gallery/pages/examples/bar.py b/vizro-core/examples/_chart-gallery/pages/examples/bar.py new file mode 100644 index 000000000..98416a157 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/bar.py @@ -0,0 +1,24 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() +tips_agg = tips.groupby("day").agg({"total_bill": "sum"}).reset_index() + +page = vm.Page( + title="Bar", + components=[ + vm.Graph( + figure=px.bar( + tips_agg, + x="total_bill", + y="day", + orientation="h", + category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]}, + ) + ) + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/boxplot.py b/vizro-core/examples/_chart-gallery/pages/examples/boxplot.py new file mode 100644 index 000000000..ce9c79e83 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/boxplot.py @@ -0,0 +1,15 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() + +page = vm.Page( + title="Boxplot", + components=[ + vm.Graph(figure=px.boxplot(tips, y="total_bill", x="day", color="day")), + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/butterfly.py b/vizro-core/examples/_chart-gallery/pages/examples/butterfly.py new file mode 100644 index 000000000..69c66ed8d --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/butterfly.py @@ -0,0 +1,47 @@ +import pandas as pd +import plotly.graph_objects as go +import vizro.models as vm +from vizro import Vizro +from vizro.models.types import capture + +ages = pd.DataFrame( + { + "Age": ["0-19", "20-29", "30-39", "40-49", "50-59", ">=60"], + "Male": [800, 2000, 4200, 5000, 2100, 800], + "Female": [1000, 3000, 3500, 3800, 3600, 700], + } +) + + +@capture("graph") +def butterfly(data_frame: pd.DataFrame, x1: str, x2: str, y: str): + fig = go.Figure() + fig.add_trace( + go.Bar( + x=-data_frame[x1], + y=data_frame[y], + orientation="h", + name=x1, + ) + ) + fig.add_trace( + go.Bar( + x=data_frame[x2], + y=data_frame[y], + orientation="h", + name=x2, + ) + ) + fig.update_layout(barmode="relative") + return fig + + +dashboard = vm.Dashboard( + pages=[ + vm.Page( + title="Butterfly", + components=[vm.Graph(figure=butterfly(ages, x1="Male", x2="Female", y="Age"))], + ) + ] +) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/choropleth.py b/vizro-core/examples/_chart-gallery/pages/examples/choropleth.py new file mode 100644 index 000000000..fff634bc5 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/choropleth.py @@ -0,0 +1,15 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +gapminder_2007 = px.data.gapminder().query("year == 2007") + +page = vm.Page( + title="Choropleth", + components=[ + vm.Graph(figure=px.choropleth(gapminder_2007, locations="iso_alpha", color="lifeExp", hover_name="country")) + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/column.py b/vizro-core/examples/_chart-gallery/pages/examples/column.py new file mode 100644 index 000000000..14f170018 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/column.py @@ -0,0 +1,18 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() +tips_agg = tips.groupby("day").agg({"total_bill": "sum"}).reset_index() + +page = vm.Page( + title="Column", + components=[ + vm.Graph( + figure=px.bar(tips_agg, y="total_bill", x="day", category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]}) + ) + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/donut.py b/vizro-core/examples/_chart-gallery/pages/examples/donut.py new file mode 100644 index 000000000..3005c2fdc --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/donut.py @@ -0,0 +1,13 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() + +page = vm.Page( + title="Donut", + components=[vm.Graph(figure=px.pie(tips, values="tip", names="day", hole=0.4))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/line.py b/vizro-core/examples/_chart-gallery/pages/examples/line.py new file mode 100644 index 000000000..c442a8994 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/line.py @@ -0,0 +1,13 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +stocks = px.data.stocks() + +page = vm.Page( + title="Line", + components=[vm.Graph(figure=px.line(stocks, x="date", y="GOOG"))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/ordered_bar.py b/vizro-core/examples/_chart-gallery/pages/examples/ordered_bar.py new file mode 100644 index 000000000..04596d4a5 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/ordered_bar.py @@ -0,0 +1,14 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() +tips_sorted = tips.groupby("day").agg({"total_bill": "sum"}).sort_values("total_bill").reset_index() + +page = vm.Page( + title="Bar", + components=[vm.Graph(figure=px.bar(tips_sorted, x="total_bill", y="day", orientation="h"))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/ordered_column.py b/vizro-core/examples/_chart-gallery/pages/examples/ordered_column.py new file mode 100644 index 000000000..435776bfa --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/ordered_column.py @@ -0,0 +1,14 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() +tips_sorted = tips.groupby("day").agg({"total_bill": "sum"}).sort_values("total_bill").reset_index() + +page = vm.Page( + title="Column", + components=[vm.Graph(figure=px.bar(tips_sorted, y="total_bill", x="day"))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/pie.py b/vizro-core/examples/_chart-gallery/pages/examples/pie.py new file mode 100644 index 000000000..596abb2ff --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/pie.py @@ -0,0 +1,13 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() + +page = vm.Page( + title="Pie", + components=[vm.Graph(figure=px.pie(tips, values="tip", names="day"))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/sankey.py b/vizro-core/examples/_chart-gallery/pages/examples/sankey.py new file mode 100644 index 000000000..21789018a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/sankey.py @@ -0,0 +1,64 @@ +from typing import List + +import pandas as pd +import plotly.graph_objects as go +import vizro.models as vm +from vizro import Vizro +from vizro.models.types import capture + +sankey_data = pd.DataFrame( + { + "Origin": [0, 1, 0, 2, 3, 3], # indices inside labels + "Destination": [2, 3, 3, 4, 4, 5], # indices inside labels + "Value": [8, 4, 2, 8, 4, 2], + } +) + + +@capture("graph") +def sankey( + data_frame: pd.DataFrame, + source: str, + target: str, + value: str, + labels: List[str], +): + fig = go.Figure( + data=[ + go.Sankey( + node={ + "pad": 16, + "thickness": 16, + "label": labels, + }, + link={ + "source": data_frame[source], + "target": data_frame[target], + "value": data_frame[value], + "label": labels, + "color": "rgba(205, 209, 228, 0.4)", + }, + ) + ] + ) + fig.update_layout(barmode="relative") + return fig + + +page = vm.Page( + title="Sankey", + components=[ + vm.Graph( + figure=sankey( + sankey_data, + labels=["A1", "A2", "B1", "B2", "C1", "C2"], + source="Origin", + target="Destination", + value="Value", + ), + ), + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/scatter.py b/vizro-core/examples/_chart-gallery/pages/examples/scatter.py new file mode 100644 index 000000000..c82c95cc6 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/scatter.py @@ -0,0 +1,13 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +iris = px.data.iris() + +page = vm.Page( + title="Scatter", + components=[vm.Graph(figure=px.scatter(iris, x="sepal_width", y="sepal_length", color="species"))], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/treemap.py b/vizro-core/examples/_chart-gallery/pages/examples/treemap.py new file mode 100644 index 000000000..db1a36ece --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/treemap.py @@ -0,0 +1,20 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +gapminder = px.data.gapminder() +gapminder_2007 = gapminder.query("year == 2007") + +page = vm.Page( + title="Treemap", + components=[ + vm.Graph( + figure=px.treemap( + gapminder_2007, path=[px.Constant("world"), "continent", "country"], values="pop", color="lifeExp" + ) + ), + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/examples/violin.py b/vizro-core/examples/_chart-gallery/pages/examples/violin.py new file mode 100644 index 000000000..f3edcffe5 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/examples/violin.py @@ -0,0 +1,15 @@ +import vizro.models as vm +import vizro.plotly.express as px +from vizro import Vizro + +tips = px.data.tips() + +page = vm.Page( + title="Violin", + components=[ + vm.Graph(figure=px.violin(tips, y="total_bill", x="day", color="day", box=True)), + ], +) + +dashboard = vm.Dashboard(pages=[page]) +Vizro().build(dashboard).run() diff --git a/vizro-core/examples/_chart-gallery/pages/flow.py b/vizro-core/examples/_chart-gallery/pages/flow.py new file mode 100644 index 000000000..4ceaed6e1 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/flow.py @@ -0,0 +1,47 @@ +"""Flow charts.""" + +import vizro.models as vm +from custom_charts import sankey + +from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file, sankey_data + +sankey = vm.Page( + title="Sankey", + path="flow/sankey", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + #### What is a sankey chart? + + A Sankey chart is a type of flow diagram that illustrates how resources or values move between different + stages or entities. The width of the arrows in the chart is proportional to the quantity of the flow, + making it easy to see where the largest movements occur. + +   + + #### When should I use it? + + Use a Sankey chart when you want to visualize the flow of resources, energy, money, or other values from + one point to another. It is particularly useful for showing distributions and transfers within a system, + such as energy usage, cost breakdowns, or material flows. + + Be mindful that Sankey charts can become cluttered if there are too many nodes or flows. + To maintain clarity, focus on highlighting the most significant flows and keep the chart as simple as + possible. + """ + ), + vm.Graph( + figure=sankey( + sankey_data, + labels=["A1", "A2", "B1", "B2", "C1", "C2"], + source="Origin", + target="Destination", + value="Value", + ), + ), + make_code_clipboard_from_py_file("sankey.py"), + ], +) + +pages = [sankey] diff --git a/vizro-core/examples/_chart-gallery/pages/magnitude.py b/vizro-core/examples/_chart-gallery/pages/magnitude.py new file mode 100644 index 000000000..e66c818f1 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/magnitude.py @@ -0,0 +1,48 @@ +"""Magnitude charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._factories import column_factory +from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file, tips_agg + +bar = vm.Page( + title="Bar", + path="magnitude/bar", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a bar chart? + + A bar chart displays bars with lengths proportional to the values they represent. One axis shows the + categories to compare, and the other provides a value scale starting from zero. + +   + + #### When should I use it? + + Use a bar chart to help your audience compare sizes and identify patterns in categorical data, such as + **how many?** in each category. Arrange the bars in any order to fit the message you want to emphasize. + Ensure clear labeling, especially with many bars, and consider using a legend or abbreviations with fuller + descriptions below. + """ + ), + vm.Graph( + figure=px.bar( + tips_agg, + x="total_bill", + y="day", + orientation="h", + category_orders={"day": ["Thur", "Fri", "Sat", "Sun"]}, + ) + ), + make_code_clipboard_from_py_file("bar.py"), + ], +) + + +column = column_factory("magnitude") + +pages = [bar, column] diff --git a/vizro-core/examples/_chart-gallery/pages/part_to_whole.py b/vizro-core/examples/_chart-gallery/pages/part_to_whole.py new file mode 100644 index 000000000..7b341828e --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/part_to_whole.py @@ -0,0 +1,115 @@ +"""Part-to-whole charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._pages_utils import PAGE_GRID, gapminder_2007, make_code_clipboard_from_py_file, tips + +pie = vm.Page( + title="Pie", + path="part-to-whole/pie", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a pie chart? + + A pie chart is a circular chart divided into segments to show proportions and percentages between + categories. The circle represents the whole. + +   + + #### When should I use it? + + Use the pie chart when you need to show your audience a quick view of how data is distributed + proportionately, with percentages highlighted. The different values you present must add up to a total and + equal 100%. + + The downsides are that pie charts tend to occupy more space than other charts, they don't + work well with more than a few values because labeling small segments is challenging, and it can be + difficult to accurately compare the sizes of the segments. + """ + ), + vm.Graph( + figure=px.pie( + tips, + values="tip", + names="day", + ) + ), + make_code_clipboard_from_py_file("pie.py"), + ], +) + +donut = vm.Page( + title="Donut", + path="part-to-whole/donut", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a donut chart? + + A donut chart looks like a pie chart, but has a blank space in the center which may contain additional + information. + +   + + #### When should I use it? + + A donut chart can be used in place of a pie chart, particularly when you are short of space or have extra + information you would like to share about the data. It may also be more effective if you wish your audience + to focus on the length of the arcs of the sections instead of the proportions of the segment sizes. + """ + ), + vm.Graph( + figure=px.pie( + tips, + values="tip", + names="day", + hole=0.4, + ) + ), + make_code_clipboard_from_py_file("pie.py"), + ], +) + +treemap = vm.Page( + title="Treemap", + path="part-to-whole/treemap", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a treemap? + + A treemap shows hierarchical data arranged as a set of nested rectangles: rectangles are sized + proportionately to the quantity they represent, combined together to form larger parent category + rectangles. + +   + + #### When should I use it? + + It's helpful to use a treemap when you wish to display hierarchical part-to-whole relationships. You can + compare groups and single elements nested within them. Consider using them instead of Pie charts when + you have a higher number of categories. Treemaps are very compact and allow audiences to get a quick + overview of the data. + """ + ), + vm.Graph( + figure=px.treemap( + gapminder_2007, + path=[px.Constant("world"), "continent", "country"], + values="pop", + color="lifeExp", + ) + ), + make_code_clipboard_from_py_file("treemap.py"), + ], +) + +pages = [donut, pie, treemap] diff --git a/vizro-core/examples/_chart-gallery/pages/ranking.py b/vizro-core/examples/_chart-gallery/pages/ranking.py new file mode 100644 index 000000000..f57242b0a --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/ranking.py @@ -0,0 +1,79 @@ +"""Ranking charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file, tips_sorted + +ordered_bar = vm.Page( + title="Ordered bar", + path="ranking/ordered-bar", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is an ordered bar chart? + + An ordered bar chart displays bars with lengths proportional to their values, arranged in descending or + ascending order. One axis shows the categories, and the other provides a value scale starting from zero. + +   + + #### When should I use it? + + Use an ordered bar chart to help your audience compare sizes and identify patterns in categorical data, + emphasizing the order of categories. This is ideal for showing rankings or priorities. + Ensure clear labeling, especially with many bars, and consider using a legend or abbreviations with fuller + descriptions below. + """ + ), + vm.Graph( + figure=px.bar( + tips_sorted, + x="total_bill", + y="day", + orientation="h", + ) + ), + make_code_clipboard_from_py_file("ordered_bar.py"), + ], +) + +ordered_column = vm.Page( + title="Ordered column", + path="ranking/ordered-column", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is an ordered column chart? + + An ordered column chart is a vertical bar chart where columns are arranged in descending or ascending order + based on their values. The column lengths vary according to the categorical value they represent, with the + scale on the y-axis starting from zero. + +   + + #### When should I use it? + + Use an ordered column chart to help your audience compare sizes and identify patterns in categorical data, + emphasizing the order of categories. This is ideal for showing rankings or progressions. Ensure clear + labeling, especially with many columns, and consider using a legend or abbreviations with fuller + descriptions below. + """ + ), + vm.Graph( + figure=px.bar( + tips_sorted, + y="total_bill", + x="day", + ) + ), + make_code_clipboard_from_py_file("ordered_column.py"), + ], +) + + +pages = [ordered_bar, ordered_column] diff --git a/vizro-core/examples/_chart-gallery/pages/spatial.py b/vizro-core/examples/_chart-gallery/pages/spatial.py new file mode 100644 index 000000000..a61933f88 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/spatial.py @@ -0,0 +1,45 @@ +"""Spatial charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._pages_utils import PAGE_GRID, gapminder_2007, make_code_clipboard_from_py_file + +choropleth = vm.Page( + title="Choropleth", + path="spatial/choropleth", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + #### What is a choropleth map? + + A choropleth map is a map in which geographical areas are colored, shaded or patterned in relation to a + specific data variable. + +   + + #### When should I use it? + + Use a chloropleth map when you wish to show how a measurement varies across a geographic area, or to show + variability or patterns within a region. Typically, you will blend one color into another, take a color + shade from light to dark, or introduce patterns to depict the variation in the data. + + Be aware that it may be difficult for your audience to accurately read or compare values on the map + depicted by color. + + """ + ), + vm.Graph( + figure=px.choropleth( + gapminder_2007, + locations="iso_alpha", + color="lifeExp", + hover_name="country", + ) + ), + make_code_clipboard_from_py_file("choropleth.py"), + ], +) + +pages = [choropleth] diff --git a/vizro-core/examples/_chart-gallery/pages/time.py b/vizro-core/examples/_chart-gallery/pages/time.py new file mode 100644 index 000000000..526610c62 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/pages/time.py @@ -0,0 +1,39 @@ +"""Time charts.""" + +import vizro.models as vm +import vizro.plotly.express as px + +from pages._factories import column_factory +from pages._pages_utils import PAGE_GRID, make_code_clipboard_from_py_file, stocks + +line = vm.Page( + title="Line", + path="time/line", + layout=vm.Layout(grid=PAGE_GRID), + components=[ + vm.Card( + text=""" + + #### What is a line chart? + + A line chart presents a series of data points over a continuous interval or time period, joined together + with straight lines. + +   + + #### When should I use it? + + You should select a line chart when you want to show trends over time. Usually, your y-axis will show a + quantitative value and your x-axis will be marked as a timescale or a sequence of intervals. You can also + display negative values below the x-axis. If you wish to group several lines (different data series) in the + same chart, try to limit yourself to 3-4 to avoid cluttering up your chart. + """ + ), + vm.Graph(figure=px.line(stocks, x="date", y="GOOG")), + make_code_clipboard_from_py_file("line.py"), + ], +) + +column = column_factory("time") + +pages = [line, column] diff --git a/vizro-core/examples/_chart-gallery/requirements.txt b/vizro-core/examples/_chart-gallery/requirements.txt new file mode 100644 index 000000000..f8c2b9078 --- /dev/null +++ b/vizro-core/examples/_chart-gallery/requirements.txt @@ -0,0 +1,4 @@ +# This file is only used if you don't have hatch installed. +# It should be fully generated and used when the demo moves to huggingface. +black==24.4.2 +vizro diff --git a/vizro-core/examples/kpi/utils/_charts.py b/vizro-core/examples/kpi/utils/_charts.py index d6ffd5147..f084f4cee 100644 --- a/vizro-core/examples/kpi/utils/_charts.py +++ b/vizro-core/examples/kpi/utils/_charts.py @@ -60,7 +60,7 @@ def bar( color_discrete_sequence=["#1A85FF"], custom_data=custom_data, ) - fig.update_layout(xaxis_title="# of Complaints", yaxis=dict(title="", autorange="reversed")) # noqa: C408 + fig.update_layout(xaxis_title="# of Complaints", yaxis={"title": "", "autorange": "reversed"}) return fig @@ -101,7 +101,7 @@ def pie( hole=0.4, ) - fig.update_layout(legend_x=1, legend_y=1, title_pad_t=2, margin=dict(l=0, r=0, t=60, b=0)) # noqa: C408 + fig.update_layout(legend_x=1, legend_y=1, title_pad_t=2, margin={"l": 0, "r": 0, "t": 60, "b": 0}) fig.update_traces(sort=False) return fig diff --git a/vizro-core/examples/scratch_dev/app.py b/vizro-core/examples/scratch_dev/app.py index 0a083b512..b4bb910c1 100644 --- a/vizro-core/examples/scratch_dev/app.py +++ b/vizro-core/examples/scratch_dev/app.py @@ -1,34 +1,69 @@ -"""Example app to show all features of Vizro.""" +"""Dev app to try things out.""" + +from typing import List import pandas as pd +import plotly.graph_objects as go import vizro.models as vm -import vizro.plotly.express as px from vizro import Vizro -from vizro.figures import kpi_card - -gapminder = px.data.gapminder() +from vizro.models.types import capture -# data from the demo app -df_kpi = pd.DataFrame( +sankey_data = pd.DataFrame( { - "Actual": [100, 200, 700], - "Reference": [100, 300, 500], - "Category": ["A", "B", "C"], + "Origin": [0, 1, 2, 1, 2, 4, 0], # indices inside labels + "Destination": [1, 2, 3, 4, 5, 5, 6], # indices inside labels + "Value": [10, 4, 8, 6, 4, 8, 8], } ) -home = vm.Page( - title="Page Title", +@capture("graph") +def sankey( + data_frame: pd.DataFrame, + source: str, + target: str, + value: str, + labels: List[str], +) -> go.Figure: + """Creates a sankey diagram based on a go.Figure.""" + fig = go.Figure( + data=[ + go.Sankey( + node={ + "pad": 16, + "thickness": 16, + "label": labels, + }, + link={ + "source": data_frame[source], + "target": data_frame[target], + "value": data_frame[value], + "label": labels, + "color": "rgba(205, 209, 228, 0.4)", + }, + ) + ] + ) + fig.update_layout(barmode="relative") + return fig + + +page = vm.Page( + title="Sankey", components=[ - # kpi_card(data_frame=df_kpi, value_column="Actual", title="KPI with value"), - vm.Figure(figure=kpi_card(data_frame=df_kpi, value_column="Actual", title="KPI with value")), + vm.Graph( + figure=sankey( + data_frame=sankey_data, + labels=["A1", "A2", "B1", "B2", "C1", "C2", "D1"], + source="Origin", + target="Destination", + value="Value", + ), + ), ], ) - -dashboard = vm.Dashboard(pages=[home]) - +dashboard = vm.Dashboard(pages=[page]) if __name__ == "__main__": Vizro().build(dashboard).run() diff --git a/vizro-core/hatch.toml b/vizro-core/hatch.toml index 02e1cb25f..ced279b35 100644 --- a/vizro-core/hatch.toml +++ b/vizro-core/hatch.toml @@ -29,13 +29,9 @@ dependencies = [ "openpyxl" ] -[envs.default.env-vars] -DASH_DEBUG = "true" -VIZRO_LOG_LEVEL = "DEBUG" - [envs.default.scripts] -example = "cd examples/{args:scratch_dev}; python app.py" -lint = "hatch run lint:lint {args:--all-files}" +example = "hatch run examples:example {args:scratch_dev}" # shortcut script to underlying example environment script. +lint = "hatch run lint:lint {args:--all-files}" # shortcut script to underlying lint environment script. prep-release = [ "hatch version release", "hatch run changelog:collect", @@ -79,6 +75,21 @@ build = "mkdocs build --strict" link-check = "linkchecker site --check-extern --no-warnings --ignore=404.html --ignore-url=127.0.0.1 --ignore-url=https://vizro.readthedocs.io/" serve = "mkdocs serve --open" +[envs.examples] +dependencies = [ + "pyyaml", + # black is required to run the example _chart-gallery. This is completely independent of the black used in linting + # our code. When this moves to HuggingFace we can remove the requirement from here. + "black==24.4.2" +] +scripts = {example = "cd examples/{args:scratch_dev}; python app.py"} +# This environment doesn't inherit from default but does install Vizro. +template = "examples" + +[envs.examples.env-vars] +DASH_DEBUG = "true" +VIZRO_LOG_LEVEL = "DEBUG" + [envs.lint] dependencies = [ "pre-commit" diff --git a/vizro-core/pyproject.toml b/vizro-core/pyproject.toml index 83aafac7c..0e68ef5dc 100644 --- a/vizro-core/pyproject.toml +++ b/vizro-core/pyproject.toml @@ -83,4 +83,4 @@ filterwarnings = [ "ignore:Custom format string detected." ] norecursedirs = ["tests/tests_utils", "tests/js"] -pythonpath = ["tests/tests_utils", "examples/kpi"] +pythonpath = ["tests/tests_utils"] diff --git a/vizro-core/src/vizro/static/css/code.css b/vizro-core/src/vizro/static/css/code.css new file mode 100644 index 000000000..b961acfdb --- /dev/null +++ b/vizro-core/src/vizro/static/css/code.css @@ -0,0 +1,31 @@ +code.language-python.hljs { + font-family: monospace; + padding: 0; +} + +.hljs { + background: unset; + color: unset; + font-family: monospace; +} + +.hljs-string, +.hljs-params, +.hljs-params .hljs-string, +.hljs-number { + color: var(--text-code-string); + font-family: monospace; +} + +.hljs-keyword { + color: var(--text-code-keyword); + font-family: monospace; +} + +.hljs-title.function_, +.hljs-meta, +.hljs-built_in, +.hljs-type { + color: var(--text-code-meta); + font-family: monospace; +} diff --git a/vizro-core/src/vizro/static/css/scroll_bar.css b/vizro-core/src/vizro/static/css/scroll_bar.css index 41c85f9b9..3c9dc34af 100644 --- a/vizro-core/src/vizro/static/css/scroll_bar.css +++ b/vizro-core/src/vizro/static/css/scroll_bar.css @@ -16,11 +16,9 @@ border-color: var(--surfaces-bg-02); } -.card::-webkit-scrollbar-thumb { - border-color: var(--surfaces-bg-card); -} - -.card-nav::-webkit-scrollbar-thumb { +.card::-webkit-scrollbar-thumb, +.card-nav::-webkit-scrollbar-thumb, +.hljs::-webkit-scrollbar-thumb { border-color: var(--surfaces-bg-card); } diff --git a/vizro-core/src/vizro/static/css/fonts/tooltip.css b/vizro-core/src/vizro/static/css/tooltip.css similarity index 100% rename from vizro-core/src/vizro/static/css/fonts/tooltip.css rename to vizro-core/src/vizro/static/css/tooltip.css diff --git a/vizro-core/src/vizro/static/css/variables.css b/vizro-core/src/vizro/static/css/variables.css index a447adfb4..36f438035 100644 --- a/vizro-core/src/vizro/static/css/variables.css +++ b/vizro-core/src/vizro/static/css/variables.css @@ -89,6 +89,10 @@ --status-success: var(--status-dark-mode-success); --tags-text-color: var(--tags-dark-mode-text-color); --status-warning: var(--status-dark-mode-warning); + --text-code-string: #95c2e7; + --text-code-keyword: #f4766e; + --text-code-meta: #c8ace1; + --text-code-type: #f69d50; } .vizro_light { @@ -154,4 +158,8 @@ --tags-text-color: var(--tags-light-mode-text-color); --status-warning: var(--status-light-mode-warning); --inverse-color: invert(100%); + --text-code-string: #0a3069; + --text-code-keyword: #d12d39; + --text-code-meta: #6f42c1; + --text-code-type: #f69d50; } diff --git a/vizro-core/tests/integration/test_examples.py b/vizro-core/tests/integration/test_examples.py index 4627b1c9e..fe39c5a8b 100644 --- a/vizro-core/tests/integration/test_examples.py +++ b/vizro-core/tests/integration/test_examples.py @@ -1,6 +1,7 @@ # ruff: noqa: F403, F405 import os import runpy +import sys from pathlib import Path import chromedriver_autoinstaller @@ -26,9 +27,15 @@ def setup_integration_test_environment(monkeypatch_session): @pytest.fixture def dashboard(request, monkeypatch): - monkeypatch.chdir(request.getfixturevalue("example_path") / request.getfixturevalue("version")) - app = runpy.run_path("app.py") - return app["dashboard"] + example_directory = request.getfixturevalue("example_path") / request.getfixturevalue("version") + monkeypatch.chdir(example_directory) + monkeypatch.syspath_prepend(example_directory) + old_sys_modules = set(sys.modules) + yield runpy.run_path("app.py")["dashboard"] + # Both run_path and run_module contaminate sys.modules, so we need to undo this in order to avoid interference + # between tests. + for key in set(sys.modules) - old_sys_modules: + del sys.modules[key] examples_path = Path(__file__).parents[2] / "examples" @@ -45,12 +52,15 @@ def dashboard(request, monkeypatch): @pytest.mark.parametrize( "example_path, version", [ + # Chart gallery is not included since it means installing black in the testing environment. + # It will move to HuggingFace in due course anyway. (examples_path / "scratch_dev", ""), - (examples_path / "dev", ""), - (examples_path / "kpi", ""), (examples_path / "scratch_dev", "yaml_version"), + (examples_path / "dev", ""), (examples_path / "dev", "yaml_version"), + (examples_path / "kpi", ""), ], + ids=str, ) def test_dashboard(dash_duo, example_path, dashboard, version): app = Vizro(assets_folder=example_path / "assets").build(dashboard).dash