diff --git a/docs/api_examples/box_plot.ipynb b/docs/api_examples/box_plot.ipynb
deleted file mode 100644
index 00dcb25..0000000
--- a/docs/api_examples/box_plot.ipynb
+++ /dev/null
@@ -1,366 +0,0 @@
-{
- "cells": [
- {
- "cell_type": "markdown",
- "id": "640ba1d5",
- "metadata": {},
- "source": [
- "# Box Plot\n",
- "\n",
- "![VueCore logo][vuecore_logo]\n",
- "\n",
- "[![Open In Colab][colab_badge]][colab_link]\n",
- "\n",
- "[VueCore][vuecore_repo] is a Python package for creating interactive and static visualizations of multi-omics data.\n",
- "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.\n",
- "\n",
- "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.\n",
- "\n",
- "## Notebook structure\n",
- "\n",
- "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.\n",
- "\n",
- "0. [Work environment setup](#0-work-environment-setup)\n",
- "1. [Basic box plot](#1-basic-box-plot)\n",
- "2. [Advanced box plot](#2-advanced-box-plot)\n",
- "\n",
- "## Credits and Contributors\n",
- "- 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].\n",
- "- You can find more details about the project in this [GitHub repository][vuecore_repo].\n",
- "\n",
- "[colab_badge]: https://colab.research.google.com/assets/colab-badge.svg\n",
- "[colab_link]: https://colab.research.google.com/github/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_plot.ipynb\n",
- "[vuecore_logo]: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuecore/main/docs/images/logo/vuecore_logo.svg\n",
- "[Mona]: https://multiomics-analytics-group.github.io/\n",
- "[Biosustain]: https://www.biosustain.dtu.dk/\n",
- "[vuecore_repo]: https://github.com/Multiomics-Analytics-Group/vuecore\n",
- "[vuegen_repo]: https://github.com/Multiomics-Analytics-Group/vuegen\n",
- "[acore_repo]: https://github.com/Multiomics-Analytics-Group/acore"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "3b504dfb",
- "metadata": {},
- "source": [
- "## 0. Work environment setup"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f0c056a7",
- "metadata": {},
- "source": [
- "### 0.1. Installing libraries and creating global variables for platform and working directory\n",
- "\n",
- "To run this notebook locally, you should create a virtual environment with the required libraries. If you are running this notebook on Google Colab, everything should be set."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": null,
- "id": "36246ed6",
- "metadata": {
- "tags": [
- "hide-output"
- ]
- },
- "outputs": [],
- "source": [
- "# VueCore library\n",
- "%pip install vuecore"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 1,
- "id": "963a9529",
- "metadata": {
- "tags": [
- "hide-cell"
- ]
- },
- "outputs": [],
- "source": [
- "import os\n",
- "\n",
- "IN_COLAB = \"COLAB_GPU\" in os.environ"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 2,
- "id": "ee2ffd40",
- "metadata": {
- "tags": [
- "hide-cell"
- ]
- },
- "outputs": [],
- "source": [
- "# Create a directory for outputs\n",
- "output_dir = \"./outputs\"\n",
- "os.makedirs(output_dir, exist_ok=True)"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "31638f9a",
- "metadata": {},
- "source": [
- "### 0.2. Importing libraries"
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 3,
- "id": "06dbf6a2",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Imports\n",
- "import pandas as pd\n",
- "import numpy as np\n",
- "from pathlib import Path\n",
- "\n",
- "from vuecore.plots.basic.box import create_box_plot"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "5cc60050",
- "metadata": {},
- "source": [
- "### 0.3. Create sample data\n",
- "We create a synthetic dataset simulating gene expression levels across different patient samples and treatment conditions, with each data point representing a unique gene's expression level under a specific treatment for a particular patient."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 47,
- "id": "ac2db647",
- "metadata": {},
- "outputs": [],
- "source": [
- "# Set a random seed for reproducibility of the synthetic data\n",
- "np.random.seed(42)\n",
- "\n",
- "# Parameters\n",
- "num_samples = 200\n",
- "sample_groups = [\"Patient A\", \"Patient B\", \"Patient C\", \"Patient D\"]\n",
- "treatments = [\"Control\", \"Treated\"]\n",
- "\n",
- "# Sample metadata\n",
- "sample_ids = np.random.choice(sample_groups, size=num_samples)\n",
- "treatment_assignments = np.random.choice(treatments, size=num_samples)\n",
- "gene_ids = [f\"Gene_{g}\" for g in np.random.randint(1, 1500, size=num_samples)]\n",
- "\n",
- "# Base expression values\n",
- "base_expr = np.random.normal(loc=100, scale=35, size=num_samples)\n",
- "\n",
- "# Treatment effect simulation\n",
- "treatment_effect = np.where(\n",
- " treatment_assignments == \"Treated\",\n",
- " np.random.normal(loc=50, scale=30, size=num_samples),\n",
- " 0\n",
- ")\n",
- "\n",
- "# Small random per-gene offset for extra variability\n",
- "gene_offset = np.random.normal(loc=0, scale=20, size=num_samples)\n",
- "\n",
- "# Final expression\n",
- "expr = base_expr + treatment_effect + gene_offset\n",
- "\n",
- "# Construct DataFrame\n",
- "gene_exp_df = pd.DataFrame({\n",
- " \"Sample_ID\": sample_ids,\n",
- " \"Treatment\": treatment_assignments,\n",
- " \"Gene_ID\": gene_ids,\n",
- " \"Expression\": expr\n",
- "})"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "ade445fe",
- "metadata": {},
- "source": [
- "## 1. Basic Box Plot\n",
- "A basic box plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 48,
- "id": "d0d34455",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[VueCore] Plot saved to outputs/box_plot_basic.png\n"
- ]
- },
- {
- "data": {
- "text/html": [
- "
"
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "# Define output file path for the PNG plot\n",
- "file_path_basic_png = Path(output_dir) / \"box_plot_basic.png\"\n",
- "\n",
- "# Generate the basic box plot\n",
- "box_plot_basic = create_box_plot(\n",
- " data=gene_exp_df,\n",
- " x=\"Treatment\",\n",
- " y=\"Expression\",\n",
- " title=\"Gene Expression Levels by Treatment\",\n",
- " file_path=file_path_basic_png,\n",
- ")\n",
- "\n",
- "box_plot_basic.show()"
- ]
- },
- {
- "cell_type": "markdown",
- "id": "f5e16637",
- "metadata": {},
- "source": [
- "## 2. Advanced Box Plot\n",
- "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`."
- ]
- },
- {
- "cell_type": "code",
- "execution_count": 49,
- "id": "358e45fe",
- "metadata": {},
- "outputs": [
- {
- "name": "stdout",
- "output_type": "stream",
- "text": [
- "[VueCore] Plot saved to outputs/box_plot_advanced.html\n"
- ]
- },
- {
- "data": {
- "text/html": [
- ""
- ]
- },
- "metadata": {},
- "output_type": "display_data"
- }
- ],
- "source": [
- "# Define the output file path for the advanced HTML plot\n",
- "file_path_adv_html = Path(output_dir) / \"box_plot_advanced.html\"\n",
- "\n",
- "# Generate the advanced box plot\n",
- "box_plot_adv = create_box_plot(\n",
- " data=gene_exp_df,\n",
- " x=\"Treatment\",\n",
- " y=\"Expression\",\n",
- " color=\"Sample_ID\",\n",
- " boxmode=\"group\",\n",
- " notched=True,\n",
- " title=\"Gene Expression Levels with Control and Treatment Condition\",\n",
- " subtitle=\"Distribution of gene expression across different treatments and patient samples\",\n",
- " labels={\n",
- " \"Treatment\": \"Treatment\",\n",
- " \"Expression\": \"Gene Expression\",\n",
- " \"Sample_ID\": \"Patient Sample ID\",\n",
- " },\n",
- " color_discrete_map={\n",
- " \"Patient A\": \"#508AA8\",\n",
- " \"Patient B\": \"#A8505E\",\n",
- " \"Patient C\": \"#86BF84\",\n",
- " \"Patient D\": \"#A776AF\",\n",
- " },\n",
- " category_orders={\"Sample_ID\": [\"Patient A\", \"Patient B\", \"Patient C\", \"Patient D\"]},\n",
- " hover_data=[\"Gene_ID\"],\n",
- " file_path=file_path_adv_html,\n",
- ")\n",
- "\n",
- "box_plot_adv.show()"
- ]
- }
- ],
- "metadata": {
- "kernelspec": {
- "display_name": "vuecore-dev",
- "language": "python",
- "name": "python3"
- },
- "language_info": {
- "codemirror_mode": {
- "name": "ipython",
- "version": 3
- },
- "file_extension": ".py",
- "mimetype": "text/x-python",
- "name": "python",
- "nbconvert_exporter": "python",
- "pygments_lexer": "ipython3",
- "version": "3.12.11"
- }
- },
- "nbformat": 4,
- "nbformat_minor": 5
-}
diff --git a/docs/api_examples/box_violin_plot.ipynb b/docs/api_examples/box_violin_plot.ipynb
new file mode 100644
index 0000000..4572c46
--- /dev/null
+++ b/docs/api_examples/box_violin_plot.ipynb
@@ -0,0 +1,4426 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "640ba1d5",
+ "metadata": {},
+ "source": [
+ "# Box and Violin Plots\n",
+ "\n",
+ "![VueCore logo][vuecore_logo]\n",
+ "\n",
+ "[![Open In Colab][colab_badge]][colab_link]\n",
+ "\n",
+ "[VueCore][vuecore_repo] is a Python package for creating interactive and static visualizations of multi-omics data.\n",
+ "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.\n",
+ "\n",
+ "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.\n",
+ "\n",
+ "## Notebook structure\n",
+ "\n",
+ "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.\n",
+ "\n",
+ "0. [Work environment setup](#0-work-environment-setup)\n",
+ "1. [Basic box plot](#1-basic-box-plot)\n",
+ "2. [Basic violin plot](#2-basic-violin-plot)\n",
+ "3. [Advanced box plot](#3-advanced-box-plot)\n",
+ "4. [Advanced violin plot](#3-advanced-violin-plot)\n",
+ "\n",
+ "## Credits and Contributors\n",
+ "- 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].\n",
+ "- You can find more details about the project in this [GitHub repository][vuecore_repo].\n",
+ "\n",
+ "[colab_badge]: https://colab.research.google.com/assets/colab-badge.svg\n",
+ "[colab_link]: https://colab.research.google.com/github/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_plot.ipynb\n",
+ "[vuecore_logo]: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuecore/main/docs/images/logo/vuecore_logo.svg\n",
+ "[Mona]: https://multiomics-analytics-group.github.io/\n",
+ "[Biosustain]: https://www.biosustain.dtu.dk/\n",
+ "[vuecore_repo]: https://github.com/Multiomics-Analytics-Group/vuecore\n",
+ "[vuegen_repo]: https://github.com/Multiomics-Analytics-Group/vuegen\n",
+ "[acore_repo]: https://github.com/Multiomics-Analytics-Group/acore"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3b504dfb",
+ "metadata": {},
+ "source": [
+ "## 0. Work environment setup"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f0c056a7",
+ "metadata": {},
+ "source": [
+ "### 0.1. Installing libraries and creating global variables for platform and working directory\n",
+ "\n",
+ "To run this notebook locally, you should create a virtual environment with the required libraries. If you are running this notebook on Google Colab, everything should be set."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "36246ed6",
+ "metadata": {
+ "tags": [
+ "hide-output"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "# VueCore library\n",
+ "%pip install vuecore"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 1,
+ "id": "963a9529",
+ "metadata": {
+ "tags": [
+ "hide-cell"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "IN_COLAB = \"COLAB_GPU\" in os.environ"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 2,
+ "id": "ee2ffd40",
+ "metadata": {
+ "tags": [
+ "hide-cell"
+ ]
+ },
+ "outputs": [],
+ "source": [
+ "# Create a directory for outputs\n",
+ "output_dir = \"./outputs\"\n",
+ "os.makedirs(output_dir, exist_ok=True)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "31638f9a",
+ "metadata": {},
+ "source": [
+ "### 0.2. Importing libraries"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 3,
+ "id": "06dbf6a2",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Imports\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "from pathlib import Path\n",
+ "import plotly.io as pio\n",
+ "\n",
+ "from vuecore.plots.basic.box import create_box_plot\n",
+ "from vuecore.plots.basic.violin import create_violin_plot\n",
+ "\n",
+ "# Set the Plotly renderer based on the environment\n",
+ "pio.renderers.default = \"notebook\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "5cc60050",
+ "metadata": {},
+ "source": [
+ "### 0.3. Create sample data\n",
+ "We create a synthetic dataset simulating gene expression levels across different patient samples and treatment conditions, with each data point representing a unique gene's expression level under a specific treatment for a particular patient."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 4,
+ "id": "ac2db647",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Set a random seed for reproducibility of the synthetic data\n",
+ "np.random.seed(42)\n",
+ "\n",
+ "# Parameters\n",
+ "num_samples = 200\n",
+ "sample_groups = [\"Patient A\", \"Patient B\", \"Patient C\", \"Patient D\"]\n",
+ "treatments = [\"Control\", \"Treated\"]\n",
+ "\n",
+ "# Sample metadata\n",
+ "sample_ids = np.random.choice(sample_groups, size=num_samples)\n",
+ "treatment_assignments = np.random.choice(treatments, size=num_samples)\n",
+ "gene_ids = [f\"Gene_{g}\" for g in np.random.randint(1, 1500, size=num_samples)]\n",
+ "\n",
+ "# Base expression values\n",
+ "base_expr = np.random.normal(loc=100, scale=35, size=num_samples)\n",
+ "\n",
+ "# Treatment effect simulation\n",
+ "treatment_effect = np.where(\n",
+ " treatment_assignments == \"Treated\",\n",
+ " np.random.normal(loc=50, scale=30, size=num_samples),\n",
+ " 0\n",
+ ")\n",
+ "\n",
+ "# Small random per-gene offset for extra variability\n",
+ "gene_offset = np.random.normal(loc=0, scale=20, size=num_samples)\n",
+ "\n",
+ "# Final expression\n",
+ "expr = base_expr + treatment_effect + gene_offset\n",
+ "\n",
+ "# Construct DataFrame\n",
+ "gene_exp_df = pd.DataFrame({\n",
+ " \"Sample_ID\": sample_ids,\n",
+ " \"Treatment\": treatment_assignments,\n",
+ " \"Gene_ID\": gene_ids,\n",
+ " \"Expression\": expr\n",
+ "})"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ade445fe",
+ "metadata": {},
+ "source": [
+ "## 1. Basic Box Plot\n",
+ "A basic box plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "d0d34455",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[VueCore] Plot saved to outputs/box_plot_basic.png\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ " \n",
+ " \n",
+ " "
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Define output file path for the PNG basic box plot\n",
+ "file_path_basic_box_png = Path(output_dir) / \"box_plot_basic.png\"\n",
+ "\n",
+ "# Generate the basic box plot\n",
+ "box_plot_basic = create_box_plot(\n",
+ " data=gene_exp_df,\n",
+ " x=\"Treatment\",\n",
+ " y=\"Expression\",\n",
+ " title=\"Gene Expression Levels by Treatment\",\n",
+ " file_path=file_path_basic_box_png,\n",
+ ")\n",
+ "\n",
+ "box_plot_basic.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "727d4fff",
+ "metadata": {},
+ "source": [
+ "## 2. Basic Violin Plot\n",
+ "A basic violin plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "42c4e79f",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[VueCore] Plot saved to outputs/violin_plot_basic.png\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Define output file path for the PNG basic violin plot\n",
+ "file_path_basic_violin_png = Path(output_dir) / \"violin_plot_basic.png\"\n",
+ "\n",
+ "# Generate the basic violin plot\n",
+ "violin_plot_basic = create_violin_plot(\n",
+ " data=gene_exp_df,\n",
+ " x=\"Treatment\",\n",
+ " y=\"Expression\",\n",
+ " title=\"Gene Expression Levels by Treatment\",\n",
+ " file_path=file_path_basic_violin_png,\n",
+ ")\n",
+ "\n",
+ "violin_plot_basic.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f5e16637",
+ "metadata": {},
+ "source": [
+ "## 3. Advanced Box Plot\n",
+ "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`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "358e45fe",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[VueCore] Plot saved to outputs/box_plot_advanced.html\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Define the output file path for the advanced HTML box plot\n",
+ "file_path_adv_box_html = Path(output_dir) / \"box_plot_advanced.html\"\n",
+ "\n",
+ "# Generate the advanced box plot\n",
+ "box_plot_adv = create_box_plot(\n",
+ " data=gene_exp_df,\n",
+ " x=\"Treatment\",\n",
+ " y=\"Expression\",\n",
+ " color=\"Sample_ID\",\n",
+ " boxmode=\"group\",\n",
+ " notched=True,\n",
+ " title=\"Gene Expression Levels with Control and Treatment Condition\",\n",
+ " subtitle=\"Distribution of gene expression across different treatments and patient samples\",\n",
+ " labels={\n",
+ " \"Treatment\": \"Treatment\",\n",
+ " \"Expression\": \"Gene Expression\",\n",
+ " \"Sample_ID\": \"Patient Sample ID\",\n",
+ " },\n",
+ " color_discrete_map={\n",
+ " \"Patient A\": \"#508AA8\",\n",
+ " \"Patient B\": \"#A8505E\",\n",
+ " \"Patient C\": \"#86BF84\",\n",
+ " \"Patient D\": \"#A776AF\",\n",
+ " },\n",
+ " category_orders={\"Sample_ID\": [\"Patient A\", \"Patient B\", \"Patient C\", \"Patient D\"]},\n",
+ " hover_data=[\"Gene_ID\"],\n",
+ " file_path=file_path_adv_box_html,\n",
+ ")\n",
+ "\n",
+ "box_plot_adv.show()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "ae75b950",
+ "metadata": {},
+ "source": [
+ "## 4. Advanced Violin Plot\n",
+ "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`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": 17,
+ "id": "e03b8b67",
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "[VueCore] Plot saved to outputs/violin_plot_advanced.html\n"
+ ]
+ },
+ {
+ "data": {
+ "text/html": [
+ ""
+ ]
+ },
+ "metadata": {},
+ "output_type": "display_data"
+ }
+ ],
+ "source": [
+ "# Define the output file path for the advanced HTML violin plot\n",
+ "file_path_adv_violin_html = Path(output_dir) / \"violin_plot_advanced.html\"\n",
+ "\n",
+ "# Generate the advanced box plot\n",
+ "violin_plot_adv = create_violin_plot(\n",
+ " data=gene_exp_df,\n",
+ " x=\"Treatment\",\n",
+ " y=\"Expression\",\n",
+ " color=\"Sample_ID\",\n",
+ " violinmode=\"group\",\n",
+ " points=\"outliers\",\n",
+ " title=\"Gene Expression Levels with Control and Treatment Condition\",\n",
+ " subtitle=\"Distribution of gene expression across different treatments and patient samples\",\n",
+ " labels={\n",
+ " \"Treatment\": \"Treatment\",\n",
+ " \"Expression\": \"Gene Expression\",\n",
+ " \"Sample_ID\": \"Patient Sample ID\",\n",
+ " },\n",
+ " color_discrete_map={\n",
+ " \"Patient A\": \"#508AA8\",\n",
+ " \"Patient B\": \"#A8505E\",\n",
+ " \"Patient C\": \"#86BF84\",\n",
+ " \"Patient D\": \"#A776AF\",\n",
+ " },\n",
+ " category_orders={\"Sample_ID\": [\"Patient A\", \"Patient B\", \"Patient C\", \"Patient D\"]},\n",
+ " hover_data=[\"Gene_ID\"],\n",
+ " file_path=file_path_adv_violin_html,\n",
+ ")\n",
+ "\n",
+ "violin_plot_adv.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "vuecore-dev",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.12.11"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/api_examples/box_plot.py b/docs/api_examples/box_violin_plot.py
similarity index 66%
rename from docs/api_examples/box_plot.py
rename to docs/api_examples/box_violin_plot.py
index 6989b27..145a363 100644
--- a/docs/api_examples/box_plot.py
+++ b/docs/api_examples/box_violin_plot.py
@@ -13,7 +13,7 @@
# ---
# %% [markdown]
-# # Box Plot
+# # Box and Violin Plots
#
# ![VueCore logo][vuecore_logo]
#
@@ -22,7 +22,7 @@
# [VueCore][vuecore_repo] is a Python package for creating interactive and static visualizations of multi-omics data.
# 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.
#
-# 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.
+# 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.
#
# ## Notebook structure
#
@@ -30,7 +30,9 @@
#
# 0. [Work environment setup](#0-work-environment-setup)
# 1. [Basic box plot](#1-basic-box-plot)
-# 2. [Advanced box plot](#2-advanced-box-plot)
+# 2. [Basic violin plot](#2-basic-violin-plot)
+# 3. [Advanced box plot](#3-advanced-box-plot)
+# 4. [Advanced violin plot](#3-advanced-violin-plot)
#
# ## Credits and Contributors
# - 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 @@
import pandas as pd
import numpy as np
from pathlib import Path
+import plotly.io as pio
from vuecore.plots.basic.box import create_box_plot
+from vuecore.plots.basic.violin import create_violin_plot
+
+# Set the Plotly renderer based on the environment
+pio.renderers.default = "notebook"
# %% [markdown]
# ### 0.3. Create sample data
@@ -127,8 +134,8 @@
# A basic box plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`.
# %%
-# Define output file path for the PNG plot
-file_path_basic_png = Path(output_dir) / "box_plot_basic.png"
+# Define output file path for the PNG basic box plot
+file_path_basic_box_png = Path(output_dir) / "box_plot_basic.png"
# Generate the basic box plot
box_plot_basic = create_box_plot(
@@ -136,18 +143,37 @@
x="Treatment",
y="Expression",
title="Gene Expression Levels by Treatment",
- file_path=file_path_basic_png,
+ file_path=file_path_basic_box_png,
)
box_plot_basic.show()
# %% [markdown]
-# ## 2. Advanced Box Plot
+# ## 2. Basic Violin Plot
+# A basic violin plot can be created by simply providing the `x` and `y` columns from the DataFrame, along with style options like `title`.
+
+# %%
+# Define output file path for the PNG basic violin plot
+file_path_basic_violin_png = Path(output_dir) / "violin_plot_basic.png"
+
+# Generate the basic violin plot
+violin_plot_basic = create_violin_plot(
+ data=gene_exp_df,
+ x="Treatment",
+ y="Expression",
+ title="Gene Expression Levels by Treatment",
+ file_path=file_path_basic_violin_png,
+)
+
+violin_plot_basic.show()
+
+# %% [markdown]
+# ## 3. Advanced Box Plot
# 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`.
# %%
-# Define the output file path for the advanced HTML plot
-file_path_adv_html = Path(output_dir) / "box_plot_advanced.html"
+# Define the output file path for the advanced HTML box plot
+file_path_adv_box_html = Path(output_dir) / "box_plot_advanced.html"
# Generate the advanced box plot
box_plot_adv = create_box_plot(
@@ -172,7 +198,43 @@
},
category_orders={"Sample_ID": ["Patient A", "Patient B", "Patient C", "Patient D"]},
hover_data=["Gene_ID"],
- file_path=file_path_adv_html,
+ file_path=file_path_adv_box_html,
)
box_plot_adv.show()
+
+# %% [markdown]
+# ## 4. Advanced Violin Plot
+# 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`.
+
+# %%
+# Define the output file path for the advanced HTML violin plot
+file_path_adv_violin_html = Path(output_dir) / "violin_plot_advanced.html"
+
+# Generate the advanced box plot
+violin_plot_adv = create_violin_plot(
+ data=gene_exp_df,
+ x="Treatment",
+ y="Expression",
+ color="Sample_ID",
+ violinmode="group",
+ points="outliers",
+ title="Gene Expression Levels with Control and Treatment Condition",
+ subtitle="Distribution of gene expression across different treatments and patient samples",
+ labels={
+ "Treatment": "Treatment",
+ "Expression": "Gene Expression",
+ "Sample_ID": "Patient Sample ID",
+ },
+ color_discrete_map={
+ "Patient A": "#508AA8",
+ "Patient B": "#A8505E",
+ "Patient C": "#86BF84",
+ "Patient D": "#A776AF",
+ },
+ category_orders={"Sample_ID": ["Patient A", "Patient B", "Patient C", "Patient D"]},
+ hover_data=["Gene_ID"],
+ file_path=file_path_adv_violin_html,
+)
+
+violin_plot_adv.show()
diff --git a/docs/index.md b/docs/index.md
index 0286f9b..b4967aa 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -23,7 +23,7 @@ sections_readme/license
api_examples/scatter_plot
api_examples/line_plot
api_examples/bar_plot
-api_examples/box_plot
+api_examples/box_violin_plot
```
```{toctree}
diff --git a/src/vuecore/constants.py b/src/vuecore/constants.py
index 6a45481..0d80806 100644
--- a/src/vuecore/constants.py
+++ b/src/vuecore/constants.py
@@ -14,6 +14,7 @@ class PlotType(StrEnum):
LINE = auto()
BAR = auto()
BOX = auto()
+ VIOLIN = auto()
class EngineType(StrEnum):
diff --git a/src/vuecore/engines/plotly/__init__.py b/src/vuecore/engines/plotly/__init__.py
index e6ef41b..326c1bc 100644
--- a/src/vuecore/engines/plotly/__init__.py
+++ b/src/vuecore/engines/plotly/__init__.py
@@ -5,6 +5,7 @@
from .line import build as build_line
from .bar import build as build_bar
from .box import build as build_box
+from .violin import build as build_violin
from .saver import save
# Import build_utils to ensure it's available
@@ -17,5 +18,6 @@
register_builder(plot_type=PlotType.LINE, engine=EngineType.PLOTLY, func=build_line)
register_builder(plot_type=PlotType.BAR, engine=EngineType.PLOTLY, func=build_bar)
register_builder(plot_type=PlotType.BOX, engine=EngineType.PLOTLY, func=build_box)
+register_builder(plot_type=PlotType.VIOLIN, engine=EngineType.PLOTLY, func=build_violin)
register_saver(engine=EngineType.PLOTLY, func=save)
diff --git a/src/vuecore/engines/plotly/theming.py b/src/vuecore/engines/plotly/theming.py
index 562f88c..619e227 100644
--- a/src/vuecore/engines/plotly/theming.py
+++ b/src/vuecore/engines/plotly/theming.py
@@ -4,6 +4,7 @@
from vuecore.schemas.basic.line import LineConfig
from vuecore.schemas.basic.bar import BarConfig
from vuecore.schemas.basic.box import BoxConfig
+from vuecore.schemas.basic.violin import ViolinConfig
def _get_axis_title(config, axis: str) -> str:
@@ -213,3 +214,35 @@ def apply_box_theme(fig: go.Figure, config: BoxConfig) -> go.Figure:
fig = _apply_common_layout(fig, config)
return fig
+
+
+def apply_violin_theme(fig: go.Figure, config: ViolinConfig) -> go.Figure:
+ """
+ Applies a consistent layout and theme to a Plotly violin plot.
+
+ This function handles all styling and layout adjustments, such as titles,
+ dimensions, templates, and trace properties, separating these concerns
+ from the initial data mapping.
+
+ Parameters
+ ----------
+ fig : go.Figure
+ The Plotly figure object to be styled.
+ config : ViolinConfig
+ The configuration object containing all styling and layout info.
+
+ Returns
+ -------
+ go.Figure
+ The styled Plotly figure object.
+ """
+ # Convert the box boolean parameter from the config to the go.Figure expected format
+ box_dict = {"visible": config.box}
+
+ # Apply trace-specific updates for violin plots
+ fig.update_traces(points=config.points, box=box_dict, selector=dict(type="violin"))
+
+ # Apply common layout
+ fig = _apply_common_layout(fig, config)
+
+ return fig
diff --git a/src/vuecore/engines/plotly/violin.py b/src/vuecore/engines/plotly/violin.py
new file mode 100644
index 0000000..9859119
--- /dev/null
+++ b/src/vuecore/engines/plotly/violin.py
@@ -0,0 +1,57 @@
+import pandas as pd
+import plotly.express as px
+import plotly.graph_objects as go
+
+from vuecore.schemas.basic.violin import ViolinConfig
+from .theming import apply_violin_theme
+from .plot_builder import build_plot
+
+# Define parameters handled by the theme script
+THEMING_PARAMS = [
+ "violinmode",
+ "log_x",
+ "log_y",
+ "range_x",
+ "range_y",
+ "points",
+ "box",
+ "title",
+ "x_title",
+ "y_title",
+ "subtitle",
+ "template",
+ "width",
+ "height",
+]
+
+
+def build(data: pd.DataFrame, config: ViolinConfig) -> go.Figure:
+ """
+ Creates a Plotly violin plot figure from a DataFrame and a Pydantic configuration.
+
+ This function acts as a bridge between the abstract plot definition and the
+ Plotly Express implementation. It translates the validated `ViolinConfig`
+ into the arguments for `plotly.express.violin` and also forwards any
+ additional, unvalidated keyword arguments from Plotly. The resulting figure
+ is then customized with layout and theme settings using `plotly.graph_objects`.
+ (https://plotly.com/python-api-reference/generated/plotly.express.violin.html).
+
+ Parameters
+ ----------
+ data : pd.DataFrame
+ The DataFrame containing the plot data.
+ config : ViolinConfig
+ The validated Pydantic model with all plot configurations.
+
+ Returns
+ -------
+ go.Figure
+ A `plotly.graph_objects.Figure` object representing the violin plot.
+ """
+ return build_plot(
+ data=data,
+ config=config,
+ px_function=px.violin,
+ theming_function=apply_violin_theme,
+ theming_params=THEMING_PARAMS,
+ )
diff --git a/src/vuecore/plots/basic/box.py b/src/vuecore/plots/basic/box.py
index 9ece072..e888be2 100644
--- a/src/vuecore/plots/basic/box.py
+++ b/src/vuecore/plots/basic/box.py
@@ -58,10 +58,10 @@ def create_box_plot(
--------
For detailed examples and usage, please refer to the documentation:
- * **Jupyter Notebook:** `docs/api_examples/box_plot.ipynb` -
- https://vuecore.readthedocs.io/en/latest/api_examples/box_plot.html
- * **Python Script:** `docs/api_examples/box_plot.py` -
- https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_plot.py
+ * **Jupyter Notebook:** `docs/api_examples/box_violin_plot.ipynb` -
+ https://vuecore.readthedocs.io/en/latest/api_examples/box_violin_plot.html
+ * **Python Script:** `docs/api_examples/box_violin_plot.py` -
+ https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_violin_plot.py
"""
return create_plot(
data=data,
diff --git a/src/vuecore/plots/basic/violin.py b/src/vuecore/plots/basic/violin.py
new file mode 100644
index 0000000..e6cff57
--- /dev/null
+++ b/src/vuecore/plots/basic/violin.py
@@ -0,0 +1,73 @@
+from typing import Any
+
+import pandas as pd
+
+from vuecore import EngineType, PlotType
+from vuecore.schemas.basic.violin import ViolinConfig
+from vuecore.plots.plot_factory import create_plot
+from vuecore.utils.docs_utils import document_pydant_params
+
+
+@document_pydant_params(ViolinConfig)
+def create_violin_plot(
+ data: pd.DataFrame,
+ engine: EngineType = EngineType.PLOTLY,
+ file_path: str = None,
+ **kwargs,
+) -> Any:
+ """
+ Creates, styles, and optionally saves a violin plot using the specified engine.
+
+ This function serves as the main entry point for users to generate violin plots.
+ It validates the provided configuration against the `ViolinConfig` schema,
+ retrieves the appropriate plotting builder and saver functions based on the
+ selected engine, builds the plot, and optionally saves it to a file.
+
+ Parameters
+ ----------
+ data : pd.DataFrame
+ The DataFrame containing the data to be plotted. Each row represents
+ an observation, and columns correspond to variables.
+ engine : EngineType, optional
+ The plotting engine to use for rendering the plot.
+ Defaults to `EngineType.PLOTLY`.
+ file_path : str, optional
+ If provided, the path where the final plot will be saved.
+ The file format is automatically inferred from the file extension
+ (e.g., '.html', '.png', '.jpeg', '.svg'). Defaults to None, meaning
+ the plot will not be saved.
+
+ Returns
+ -------
+ Any
+ The final plot object returned by the selected engine.
+ For Plotly, this will typically be a `plotly.graph_objects.Figure`.
+ The exact type depends on the chosen engine.
+
+ Raises
+ ------
+ pydantic.ValidationError
+ If the provided keyword arguments do not conform to the `ViolinConfig` schema.
+ e.g., a required parameter is missing or a value has an incorrect type.
+ ValueError
+ Raised by the plotting engine (e.g., Plotly Express) if a
+ column specified in the configuration (e.g., 'x', 'y', 'color') is
+ not found in the provided DataFrame.
+
+ Examples
+ --------
+ For detailed examples and usage, please refer to the documentation:
+
+ * **Jupyter Notebook:** `docs/api_examples/box_violin_plot.ipynb` -
+ https://vuecore.readthedocs.io/en/latest/api_examples/box_violin_plot.html
+ * **Python Script:** `docs/api_examples/box_violin_plot.py` -
+ https://github.com/Multiomics-Analytics-Group/vuecore/blob/main/docs/api_examples/box_violin_plot.py
+ """
+ return create_plot(
+ data=data,
+ config=ViolinConfig,
+ plot_type=PlotType.VIOLIN,
+ engine=engine,
+ file_path=file_path,
+ **kwargs,
+ )
diff --git a/src/vuecore/schemas/basic/violin.py b/src/vuecore/schemas/basic/violin.py
new file mode 100644
index 0000000..65651f2
--- /dev/null
+++ b/src/vuecore/schemas/basic/violin.py
@@ -0,0 +1,38 @@
+from typing import Optional
+from pydantic import Field, ConfigDict
+from vuecore.schemas.plotly_base import PlotlyBaseConfig
+
+
+class ViolinConfig(PlotlyBaseConfig):
+ """
+ Pydantic model for validating and managing violin plot configurations,
+ which extends PlotlyBaseConfig.
+
+ This model serves as a curated API for the most relevant parameters
+ for violin plots, closely aligned with the `plotly.express.violin` API
+ (https://plotly.com/python-api-reference/generated/plotly.express.violin.html).
+
+ It includes key parameters for data mapping, styling, and layout. It ensures
+ that user-provided configurations are type-safe and adhere to the expected
+ structure. The plotting function handles parameters defined here, and also
+ accepts additional Plotly keyword arguments, forwarding them to the
+ appropriate `plotly.express.violin` or `plotly.graph_objects.Figure` call.
+ """
+
+ # General Configuration
+ # Allow extra parameters to pass through to Plotly
+ model_config = ConfigDict(extra="allow")
+
+ # Styling and Layout
+ orientation: Optional[str] = Field(
+ None,
+ description="Orientation of the violin plots ('v' for vertical, 'h' for horizontal).",
+ )
+ violinmode: str = Field(
+ "group", description="Mode for grouping violins ('group' or 'overlay')."
+ )
+ points: str | bool = Field(
+ "outliers",
+ description="Method to display sample points ('outliers', 'all', 'suspectedoutliers', False).",
+ )
+ box: bool = Field(False, description="If True, boxes are drawn inside the violins.")
diff --git a/src/vuecore/utils/docs_utils.py b/src/vuecore/utils/docs_utils.py
index 8ebf563..8ddb9ac 100644
--- a/src/vuecore/utils/docs_utils.py
+++ b/src/vuecore/utils/docs_utils.py
@@ -44,6 +44,18 @@ def get_type_string(annotation: Any) -> str:
A simplified string representation of the type.
"""
origin = get_origin(annotation)
+
+ # Handle annotation with more than one type (e.g., str | bool)
+ if origin is None and hasattr(annotation, "__args__"):
+ args = get_args(annotation)
+ if args:
+ arg_strings = [get_type_string(arg) for arg in args]
+ # Exclude NoneType from the list if it's an Optional
+ if type(None) in args:
+ return f"Optional[{' | '.join(s for s in arg_strings if s != 'None')}]"
+ return " | ".join(arg_strings)
+
+ # Handle Optional and Union types with single non-None type
if origin is Union or origin is Optional:
args = get_args(annotation)
non_none_arg = next((arg for arg in args if arg is not type(None)), None)
@@ -63,7 +75,12 @@ def get_type_string(annotation: Any) -> str:
key_type_str = get_type_string(args[0])
value_type_str = get_type_string(args[1])
return f"Dict[{key_type_str}, {value_type_str}]"
- return annotation.__name__
+
+ # Fallback for primitives and other types
+ if hasattr(annotation, "__name__"):
+ return annotation.__name__
+
+ return str(annotation)
def document_pydant_params(model: Type[BaseModel]):
diff --git a/tests/test_boxplot.py b/tests/test_boxplot.py
index 9beb2d7..baab4be 100644
--- a/tests/test_boxplot.py
+++ b/tests/test_boxplot.py
@@ -10,7 +10,7 @@
def sample_box_data() -> pd.DataFrame:
"""
Fixture for generating synthetic data for box plots, replicating
- the code used in the docs/api_examples/box_plot.ipynb example.
+ the code used in the docs/api_examples/box_violin_plot.ipynb example.
"""
# Set a random seed for reproducibility of the synthetic data
np.random.seed(42)
diff --git a/tests/test_scatter.py b/tests/test_scatterplot.py
similarity index 100%
rename from tests/test_scatter.py
rename to tests/test_scatterplot.py
diff --git a/tests/test_violinplot.py b/tests/test_violinplot.py
new file mode 100644
index 0000000..ad0aef1
--- /dev/null
+++ b/tests/test_violinplot.py
@@ -0,0 +1,127 @@
+import pandas as pd
+import numpy as np
+import pytest
+from pathlib import Path
+
+from vuecore.plots.basic.violin import create_violin_plot
+
+
+@pytest.fixture
+def sample_violin_data() -> pd.DataFrame:
+ """
+ Fixture for generating synthetic data for violin plots, replicating
+ the code used in the docs/api_examples/box_violin_plot.ipynb example.
+ """
+ # Set a random seed for reproducibility of the synthetic data
+ np.random.seed(42)
+
+ # Parameters
+ num_samples = 200
+ sample_groups = ["Patient A", "Patient B", "Patient C", "Patient D"]
+ treatments = ["Control", "Treated"]
+
+ # Sample metadata
+ sample_ids = np.random.choice(sample_groups, size=num_samples)
+ treatment_assignments = np.random.choice(treatments, size=num_samples)
+ gene_ids = [f"Gene_{g}" for g in np.random.randint(1, 1500, size=num_samples)]
+
+ # Base expression values
+ base_expr = np.random.normal(loc=100, scale=35, size=num_samples)
+
+ # Treatment effect simulation
+ treatment_effect = np.where(
+ treatment_assignments == "Treated",
+ np.random.normal(loc=50, scale=30, size=num_samples),
+ 0,
+ )
+
+ # Small random per-gene offset for extra variability
+ gene_offset = np.random.normal(loc=0, scale=20, size=num_samples)
+
+ # Final expression
+ expr = base_expr + treatment_effect + gene_offset
+
+ # Construct DataFrame
+ gene_exp_df = pd.DataFrame(
+ {
+ "Sample_ID": sample_ids,
+ "Treatment": treatment_assignments,
+ "Gene_ID": gene_ids,
+ "Expression": expr,
+ }
+ )
+
+ return gene_exp_df
+
+
+@pytest.mark.parametrize("ext", ["png", "svg", "html", "json"])
+def test_basic_violin_plot(sample_violin_data: pd.DataFrame, tmp_path: Path, ext: str):
+ """
+ Test basic violin plot creation, ensuring the figure is returned,
+ and output files are generated correctly for various formats.
+ """
+ # Define the output path using tmp_path fixture for temporary files
+ output_path = tmp_path / f"basic_violin_test.{ext}"
+
+ # Create the basic violin plot using the VueCore function
+ fig = create_violin_plot(
+ data=sample_violin_data,
+ x="Treatment",
+ y="Expression",
+ title="Gene Expression Levels by Treatment",
+ file_path=str(output_path),
+ )
+
+ # Assertions to verify plot creation and file output
+ assert fig is not None, "Figure object should not be None."
+ assert output_path.exists(), f"Output file should exist: {output_path}"
+ assert (
+ output_path.stat().st_size > 0
+ ), f"Output file should not be empty: {output_path}"
+
+
+@pytest.mark.parametrize("ext", ["png", "svg", "html", "json"])
+def test_advanced_violin_plot(
+ sample_violin_data: pd.DataFrame, tmp_path: Path, ext: str
+):
+ """
+ Test advanced violin plot creation with multiple parameters,
+ ensuring the figure is returned and output files are generated.
+ """
+ # Define the output path for the advanced plot
+ output_path = tmp_path / f"advanced_violin_test.{ext}"
+
+ # Create the advanced violin plot using the VueCore function
+ fig = create_violin_plot(
+ data=sample_violin_data,
+ x="Treatment",
+ y="Expression",
+ color="Sample_ID",
+ violinmode="group",
+ points="outliers",
+ title="Gene Expression Levels with Control and Treatment Condition",
+ subtitle="Distribution of gene expression across different treatments and patient samples",
+ labels={
+ "Treatment": "Treatment",
+ "Expression": "Gene Expression",
+ "Sample_ID": "Patient Sample ID",
+ },
+ color_discrete_map={
+ "Patient A": "#508AA8",
+ "Patient B": "#A8505E",
+ "Patient C": "#86BF84",
+ "Patient D": "#A776AF",
+ },
+ category_orders={
+ "Sample_ID": ["Patient A", "Patient B", "Patient C", "Patient D"]
+ },
+ hover_data=["Gene_ID"],
+ file_path=str(output_path),
+ )
+
+ # Assertions to verify plot creation and file output
+ assert fig is not None, "Figure object should not be None."
+ assert output_path.exists(), f"Output file should exist: {output_path}"
+ assert (
+ output_path.stat().st_size > 0
+ ), f"Output file should not be empty: {output_path}"