Skip to content

Commit 6e19f17

Browse files
authored
✨ Feat: Add scripts, test, and example for violin plot (#39)
* ✨ Feat(vuecore/schemas/basic/violin.py): Create Pydantic schema of the violin plot * ✨ Feat(vuecore/engines/plotly/violin.py): Create script with build function for violin plot * ✨ Feat(vuecore/engines/plotly/theming.py): Add apply_violin_theme function to the script * ✨ Feat(vuecore/engines/plotly/__init__.py): Register violin plot builder and add it to the PlotType StrEnum * ✨ Feat(vuecore/plots/violin.py): Create script with the user-facing function for the violin plot, and update box plot api examples names docstring * 🐛 Fix(vuecore/utils/docs_utils.py): add code to handle annotations with more than one type (e.g., str | bool) * 🐛 Fix(vuecore/engines/plotly/theming.py): add code to handle box parameter from violin config as expected by go.Figure in the apply_violin_theme function * ✨ Feat(docs/api_examples/box_violin_plot.ipynb): Update notebook api example for box plot to also include the violin plot, and sync it with a python script * 📝 Docs: update index.md to add box and violin plot example * ✅ Feat(tests/test_violinplot.py): Create violin plot test based on the api example code * 📝 Doc: update notebook name on the docstring of the test_boxplot.py file and rename test_scatterplot.py file to follow the naming standard of the other scripts * 🐛 Fix(docs/api_examples/box_violin_plot.py): apply black formatting
1 parent 0ed9b37 commit 6e19f17

File tree

15 files changed

+4853
-383
lines changed

15 files changed

+4853
-383
lines changed

docs/api_examples/box_plot.ipynb

Lines changed: 0 additions & 366 deletions
This file was deleted.

docs/api_examples/box_violin_plot.ipynb

Lines changed: 4426 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 72 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# ---
1414

1515
# %% [markdown]
16-
# # Box Plot
16+
# # Box and Violin Plots
1717
#
1818
# ![VueCore logo][vuecore_logo]
1919
#
@@ -22,15 +22,17 @@
2222
# [VueCore][vuecore_repo] is a Python package for creating interactive and static visualizations of multi-omics data.
2323
# It is part of a broader ecosystem of tools—including [ACore][acore_repo] for data processing and [VueGen][vuegen_repo] for automated reporting—that together enable end-to-end workflows for omics analysis.
2424
#
25-
# This notebook demonstrates how to generate box plots using plotting functions from VueCore. We showcase basic and advanced plot configurations, highlighting key customization options such as grouping, color mapping, text annotations, and export to multiple file formats.
25+
# This notebook demonstrates how to generate box and violin plots using plotting functions from VueCore. We showcase basic and advanced plot configurations, highlighting key customization options such as grouping, color mapping, text annotations, and export to multiple file formats.
2626
#
2727
# ## Notebook structure
2828
#
2929
# First, we will set up the work environment by installing the necessary packages and importing the required libraries. Next, we will create basic and advanced box plots.
3030
#
3131
# 0. [Work environment setup](#0-work-environment-setup)
3232
# 1. [Basic box plot](#1-basic-box-plot)
33-
# 2. [Advanced box plot](#2-advanced-box-plot)
33+
# 2. [Basic violin plot](#2-basic-violin-plot)
34+
# 3. [Advanced box plot](#3-advanced-box-plot)
35+
# 4. [Advanced violin plot](#3-advanced-violin-plot)
3436
#
3537
# ## Credits and Contributors
3638
# - This notebook was created by Sebastián Ayala-Ruano under the supervision of Henry Webel and Alberto Santos, head of the [Multiomics Network Analytics Group (MoNA)][Mona] at the [Novo Nordisk Foundation Center for Biosustainability (DTU Biosustain)][Biosustain].
@@ -75,8 +77,13 @@
7577
import pandas as pd
7678
import numpy as np
7779
from pathlib import Path
80+
import plotly.io as pio
7881

7982
from vuecore.plots.basic.box import create_box_plot
83+
from vuecore.plots.basic.violin import create_violin_plot
84+
85+
# Set the Plotly renderer based on the environment
86+
pio.renderers.default = "notebook"
8087

8188
# %% [markdown]
8289
# ### 0.3. Create sample data
@@ -127,27 +134,46 @@
127134
# A basic box plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`.
128135

129136
# %%
130-
# Define output file path for the PNG plot
131-
file_path_basic_png = Path(output_dir) / "box_plot_basic.png"
137+
# Define output file path for the PNG basic box plot
138+
file_path_basic_box_png = Path(output_dir) / "box_plot_basic.png"
132139

133140
# Generate the basic box plot
134141
box_plot_basic = create_box_plot(
135142
data=gene_exp_df,
136143
x="Treatment",
137144
y="Expression",
138145
title="Gene Expression Levels by Treatment",
139-
file_path=file_path_basic_png,
146+
file_path=file_path_basic_box_png,
140147
)
141148

142149
box_plot_basic.show()
143150

144151
# %% [markdown]
145-
# ## 2. Advanced Box Plot
152+
# ## 2. Basic Violin Plot
153+
# A basic violin plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`.
154+
155+
# %%
156+
# Define output file path for the PNG basic violin plot
157+
file_path_basic_violin_png = Path(output_dir) / "violin_plot_basic.png"
158+
159+
# Generate the basic violin plot
160+
violin_plot_basic = create_violin_plot(
161+
data=gene_exp_df,
162+
x="Treatment",
163+
y="Expression",
164+
title="Gene Expression Levels by Treatment",
165+
file_path=file_path_basic_violin_png,
166+
)
167+
168+
violin_plot_basic.show()
169+
170+
# %% [markdown]
171+
# ## 3. Advanced Box Plot
146172
# Here is an example of an advanced box plot with more descriptive parameters, including `color and box grouping`, `text annotations`, `hover tooltips`, and export to `HTML`.
147173

148174
# %%
149-
# Define the output file path for the advanced HTML plot
150-
file_path_adv_html = Path(output_dir) / "box_plot_advanced.html"
175+
# Define the output file path for the advanced HTML box plot
176+
file_path_adv_box_html = Path(output_dir) / "box_plot_advanced.html"
151177

152178
# Generate the advanced box plot
153179
box_plot_adv = create_box_plot(
@@ -172,7 +198,43 @@
172198
},
173199
category_orders={"Sample_ID": ["Patient A", "Patient B", "Patient C", "Patient D"]},
174200
hover_data=["Gene_ID"],
175-
file_path=file_path_adv_html,
201+
file_path=file_path_adv_box_html,
176202
)
177203

178204
box_plot_adv.show()
205+
206+
# %% [markdown]
207+
# ## 4. Advanced Violin Plot
208+
# Here is an example of an advanced violin plot with more descriptive parameters, including `color and box grouping`, `text annotations`, `hover tooltips`, and export to `HTML`.
209+
210+
# %%
211+
# Define the output file path for the advanced HTML violin plot
212+
file_path_adv_violin_html = Path(output_dir) / "violin_plot_advanced.html"
213+
214+
# Generate the advanced box plot
215+
violin_plot_adv = create_violin_plot(
216+
data=gene_exp_df,
217+
x="Treatment",
218+
y="Expression",
219+
color="Sample_ID",
220+
violinmode="group",
221+
points="outliers",
222+
title="Gene Expression Levels with Control and Treatment Condition",
223+
subtitle="Distribution of gene expression across different treatments and patient samples",
224+
labels={
225+
"Treatment": "Treatment",
226+
"Expression": "Gene Expression",
227+
"Sample_ID": "Patient Sample ID",
228+
},
229+
color_discrete_map={
230+
"Patient A": "#508AA8",
231+
"Patient B": "#A8505E",
232+
"Patient C": "#86BF84",
233+
"Patient D": "#A776AF",
234+
},
235+
category_orders={"Sample_ID": ["Patient A", "Patient B", "Patient C", "Patient D"]},
236+
hover_data=["Gene_ID"],
237+
file_path=file_path_adv_violin_html,
238+
)
239+
240+
violin_plot_adv.show()

docs/index.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ sections_readme/license
2323
api_examples/scatter_plot
2424
api_examples/line_plot
2525
api_examples/bar_plot
26-
api_examples/box_plot
26+
api_examples/box_violin_plot
2727
```
2828

2929
```{toctree}

src/vuecore/constants.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class PlotType(StrEnum):
1414
LINE = auto()
1515
BAR = auto()
1616
BOX = auto()
17+
VIOLIN = auto()
1718

1819

1920
class EngineType(StrEnum):

src/vuecore/engines/plotly/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from .line import build as build_line
66
from .bar import build as build_bar
77
from .box import build as build_box
8+
from .violin import build as build_violin
89
from .saver import save
910

1011
# Import build_utils to ensure it's available
@@ -17,5 +18,6 @@
1718
register_builder(plot_type=PlotType.LINE, engine=EngineType.PLOTLY, func=build_line)
1819
register_builder(plot_type=PlotType.BAR, engine=EngineType.PLOTLY, func=build_bar)
1920
register_builder(plot_type=PlotType.BOX, engine=EngineType.PLOTLY, func=build_box)
21+
register_builder(plot_type=PlotType.VIOLIN, engine=EngineType.PLOTLY, func=build_violin)
2022

2123
register_saver(engine=EngineType.PLOTLY, func=save)

src/vuecore/engines/plotly/theming.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from vuecore.schemas.basic.line import LineConfig
55
from vuecore.schemas.basic.bar import BarConfig
66
from vuecore.schemas.basic.box import BoxConfig
7+
from vuecore.schemas.basic.violin import ViolinConfig
78

89

910
def _get_axis_title(config, axis: str) -> str:
@@ -213,3 +214,35 @@ def apply_box_theme(fig: go.Figure, config: BoxConfig) -> go.Figure:
213214
fig = _apply_common_layout(fig, config)
214215

215216
return fig
217+
218+
219+
def apply_violin_theme(fig: go.Figure, config: ViolinConfig) -> go.Figure:
220+
"""
221+
Applies a consistent layout and theme to a Plotly violin plot.
222+
223+
This function handles all styling and layout adjustments, such as titles,
224+
dimensions, templates, and trace properties, separating these concerns
225+
from the initial data mapping.
226+
227+
Parameters
228+
----------
229+
fig : go.Figure
230+
The Plotly figure object to be styled.
231+
config : ViolinConfig
232+
The configuration object containing all styling and layout info.
233+
234+
Returns
235+
-------
236+
go.Figure
237+
The styled Plotly figure object.
238+
"""
239+
# Convert the box boolean parameter from the config to the go.Figure expected format
240+
box_dict = {"visible": config.box}
241+
242+
# Apply trace-specific updates for violin plots
243+
fig.update_traces(points=config.points, box=box_dict, selector=dict(type="violin"))
244+
245+
# Apply common layout
246+
fig = _apply_common_layout(fig, config)
247+
248+
return fig
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import pandas as pd
2+
import plotly.express as px
3+
import plotly.graph_objects as go
4+
5+
from vuecore.schemas.basic.violin import ViolinConfig
6+
from .theming import apply_violin_theme
7+
from .plot_builder import build_plot
8+
9+
# Define parameters handled by the theme script
10+
THEMING_PARAMS = [
11+
"violinmode",
12+
"log_x",
13+
"log_y",
14+
"range_x",
15+
"range_y",
16+
"points",
17+
"box",
18+
"title",
19+
"x_title",
20+
"y_title",
21+
"subtitle",
22+
"template",
23+
"width",
24+
"height",
25+
]
26+
27+
28+
def build(data: pd.DataFrame, config: ViolinConfig) -> go.Figure:
29+
"""
30+
Creates a Plotly violin plot figure from a DataFrame and a Pydantic configuration.
31+
32+
This function acts as a bridge between the abstract plot definition and the
33+
Plotly Express implementation. It translates the validated `ViolinConfig`
34+
into the arguments for `plotly.express.violin` and also forwards any
35+
additional, unvalidated keyword arguments from Plotly. The resulting figure
36+
is then customized with layout and theme settings using `plotly.graph_objects`.
37+
(https://plotly.com/python-api-reference/generated/plotly.express.violin.html).
38+
39+
Parameters
40+
----------
41+
data : pd.DataFrame
42+
The DataFrame containing the plot data.
43+
config : ViolinConfig
44+
The validated Pydantic model with all plot configurations.
45+
46+
Returns
47+
-------
48+
go.Figure
49+
A `plotly.graph_objects.Figure` object representing the violin plot.
50+
"""
51+
return build_plot(
52+
data=data,
53+
config=config,
54+
px_function=px.violin,
55+
theming_function=apply_violin_theme,
56+
theming_params=THEMING_PARAMS,
57+
)

src/vuecore/plots/basic/box.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,10 @@ def create_box_plot(
5858
--------
5959
For detailed examples and usage, please refer to the documentation:
6060
61-
* **Jupyter Notebook:** `docs/api_examples/box_plot.ipynb` -
62-
https://vuecore.readthedocs.io/en/latest/api_examples/box_plot.html
63-
* **Python Script:** `docs/api_examples/box_plot.py` -
64-
https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_plot.py
61+
* **Jupyter Notebook:** `docs/api_examples/box_violin_plot.ipynb` -
62+
https://vuecore.readthedocs.io/en/latest/api_examples/box_violin_plot.html
63+
* **Python Script:** `docs/api_examples/box_violin_plot.py` -
64+
https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_violin_plot.py
6565
"""
6666
return create_plot(
6767
data=data,

src/vuecore/plots/basic/violin.py

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
from typing import Any
2+
3+
import pandas as pd
4+
5+
from vuecore import EngineType, PlotType
6+
from vuecore.schemas.basic.violin import ViolinConfig
7+
from vuecore.plots.plot_factory import create_plot
8+
from vuecore.utils.docs_utils import document_pydant_params
9+
10+
11+
@document_pydant_params(ViolinConfig)
12+
def create_violin_plot(
13+
data: pd.DataFrame,
14+
engine: EngineType = EngineType.PLOTLY,
15+
file_path: str = None,
16+
**kwargs,
17+
) -> Any:
18+
"""
19+
Creates, styles, and optionally saves a violin plot using the specified engine.
20+
21+
This function serves as the main entry point for users to generate violin plots.
22+
It validates the provided configuration against the `ViolinConfig` schema,
23+
retrieves the appropriate plotting builder and saver functions based on the
24+
selected engine, builds the plot, and optionally saves it to a file.
25+
26+
Parameters
27+
----------
28+
data : pd.DataFrame
29+
The DataFrame containing the data to be plotted. Each row represents
30+
an observation, and columns correspond to variables.
31+
engine : EngineType, optional
32+
The plotting engine to use for rendering the plot.
33+
Defaults to `EngineType.PLOTLY`.
34+
file_path : str, optional
35+
If provided, the path where the final plot will be saved.
36+
The file format is automatically inferred from the file extension
37+
(e.g., '.html', '.png', '.jpeg', '.svg'). Defaults to None, meaning
38+
the plot will not be saved.
39+
40+
Returns
41+
-------
42+
Any
43+
The final plot object returned by the selected engine.
44+
For Plotly, this will typically be a `plotly.graph_objects.Figure`.
45+
The exact type depends on the chosen engine.
46+
47+
Raises
48+
------
49+
pydantic.ValidationError
50+
If the provided keyword arguments do not conform to the `ViolinConfig` schema.
51+
e.g., a required parameter is missing or a value has an incorrect type.
52+
ValueError
53+
Raised by the plotting engine (e.g., Plotly Express) if a
54+
column specified in the configuration (e.g., 'x', 'y', 'color') is
55+
not found in the provided DataFrame.
56+
57+
Examples
58+
--------
59+
For detailed examples and usage, please refer to the documentation:
60+
61+
* **Jupyter Notebook:** `docs/api_examples/box_violin_plot.ipynb` -
62+
https://vuecore.readthedocs.io/en/latest/api_examples/box_violin_plot.html
63+
* **Python Script:** `docs/api_examples/box_violin_plot.py` -
64+
https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_violin_plot.py
65+
"""
66+
return create_plot(
67+
data=data,
68+
config=ViolinConfig,
69+
plot_type=PlotType.VIOLIN,
70+
engine=engine,
71+
file_path=file_path,
72+
**kwargs,
73+
)

0 commit comments

Comments
 (0)