Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
fc43949
♻️ Add initial version of project refactoring, using scatter plot as …
sayalaruano Jun 13, 2025
9b54fc8
♻️ Refactor code to support various engines and handle this logic wit…
sayalaruano Jun 16, 2025
7d27bc8
🎨 Add enums for plot and engine types
sayalaruano Jun 16, 2025
dd49d7b
✅ Add tests for scatter plot functions
sayalaruano Jun 16, 2025
4b5b43d
📝 Add docstrings and enums in the plotly/__init__.py file
sayalaruano Jun 16, 2025
1ec04f2
🐛 Fix ruff errors during CI
sayalaruano Jun 16, 2025
d2adca7
Merge branch 'main' into refactor-scatter
sayalaruano Jun 23, 2025
58e96db
📝 Add notebook to show scatter plot examples, and remove validation t…
sayalaruano Jun 23, 2025
155c76e
🐛 Fix circular import bug, add try-except block to install chrmore to…
sayalaruano Jun 24, 2025
fffa849
➖ Remove unused dependency
sayalaruano Jun 24, 2025
4486119
✏️ Add all expected report formats on the value-error message
sayalaruano Jun 24, 2025
95bb54e
💚 Add readthedocs dependencies to download chrome, which is required …
sayalaruano Jun 25, 2025
f68a35a
✏️ Correct identation on apt dependencies for readthedocs.yaml
sayalaruano Jun 25, 2025
03eab2d
📝 Add api_example/scatter into docs, update notebook, and create pyth…
sayalaruano Jun 25, 2025
c96d9a9
🐛 Fix indentation fro apt packages in readthedocs.yaml and imporitng …
sayalaruano Jun 25, 2025
390551a
🐛 Fix import bug in scatter python script
sayalaruano Jun 25, 2025
16d6827
🐛 Fix bug on scatter python script
sayalaruano Jun 25, 2025
23e7252
📝 Update scatter notebook header
sayalaruano Jun 25, 2025
bd7e1ad
📝 Modify header of scatter notebook and add title to basic plot
sayalaruano Jun 25, 2025
275db81
📝 Show df on the scatter notebook
sayalaruano Jun 25, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,8 @@ cython_debug/

# VSCode
*DS_Store

# Tests
test_results/
docs/api_examples/outputs/
docs/api_examples/iframe_figures/
15 changes: 13 additions & 2 deletions .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,19 @@ build:
os: ubuntu-22.04
tools:
python: "3.12"
# apt_packages:
# - some-package
apt_packages:
- libnss3
- libatk-bridge2.0-0
- libcups2
- libxcomposite1
- libxdamage1
- libxfixes3
- libxrandr2
- libgbm1
- libxkbcommon0
- libpango-1.0-0
- libcairo2
- libasound2

# Build documentation in the "docs/" directory with Sphinx
sphinx:
Expand Down
4 changes: 2 additions & 2 deletions docs/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,5 @@ reference
_build
jupyter_execute

# VS Code
.DS_Store
# VsCode
*.DS_Store
4,523 changes: 4,523 additions & 0 deletions docs/api_examples/scatter_plot.ipynb

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions docs/api_examples/scatter_plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
"""
Title: Scatter Plot Examples using VueCore
Description:
This script demonstrates how to generate scatter plots using VueCore — a Python package for creating
interactive and static visualizations of multi-omics data. It is part of an ecosystem including ACore
for data processing and VueGen for automated reporting.

We showcase basic and advanced plot configurations, highlighting customization options such as grouping,
color mapping, annotations, and export to multiple formats.

Script Structure:
0. Work environment setup
1. Basic scatter plot
2. Advanced scatter plot

Authors:
Sebastián Ayala-Ruano
Supervisors:
Henry Webel, Alberto Santos (Multiomics Network Analytics Group, DTU Biosustain)

Institution:
Multiomics Network Analytics Group (MoNA),
Novo Nordisk Foundation Center for Biosustainability (DTU Biosustain)

Project Repository:
https://github.com/Multiomics-Analytics-Group/vuecore

License:
MIT License

Created: 2025-06-25
Last Updated: 2025-06-25
"""

# %%
# 0. Work environment setup
# 0.1. Installing libraries and creating global variables for platform and working directory
# To run this notebook locally, you should create a virtual environment with the required libraries.
# pip install vuecore

# 0.2. Importing libraries
import os
import pandas as pd
import plotly.io as pio
from vuecore.plots.basic.scatter import create_scatter_plot

# Set the Plotly renderer based on the environment, default to notebook, but you can change it
# to "browser" if you do not want to use jupyter widgets.
pio.renderers.default = "notebook"

# 0.3. Create a directory for outputs
output_dir = "./outputs"
os.makedirs(output_dir, exist_ok=True)

# %%
# 1. Basic Scatter Plot
# Created sample data
sample_df = pd.DataFrame(
{
"gene_expression": [1.2, 2.5, 3.1, 4.5, 5.2, 6.8, 3.9, 2.1],
"log_p_value": [0.5, 1.5, 2.0, 3.5, 4.0, 5.5, 1.8, 0.9],
"regulation": ["Up", "Up", "None", "Down", "Down", "Down", "None", "Up"],
"significance_score": [10, 20, 5, 40, 55, 80, 15, 25],
"gene_name": [
"GENE_A",
"GENE_B",
"GENE_C",
"GENE_D",
"GENE_E",
"GENE_F",
"GENE_G",
"GENE_H",
],
"cell_type": ["A", "B", "A", "B", "A", "B", "A", "B"],
}
)

# Define output path
file_path_png = os.path.join(output_dir, "scatter_basic.png")

# Generate basic plot
fig = create_scatter_plot(
data=sample_df,
x="gene_expression",
y="log_p_value",
file_path=file_path_png,
)

fig.show()

# %%
# 2. Advanced Scatter Plot
# Define output path
file_path_adv_html = os.path.join(output_dir, "scatter_advanced.html")

# Generate advanced plot
fig_advanced = create_scatter_plot(
data=sample_df,
x="gene_expression",
y="log_p_value",
group="regulation",
size="significance_score",
text="gene_name",
title="Advanced Gene Expression Plot",
x_title="Log2 Fold Change",
y_title="-Log10(P-value)",
colors={"Up": "#FF5733", "Down": "#3380FF", "None": "#33FF57"},
marker_opacity=0.8,
marker_line_width=1,
marker_line_color="darkgray",
width=900,
height=600,
file_path=file_path_adv_html,
)

fig_advanced.show()
11 changes: 9 additions & 2 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,24 @@
:relative-images:
```

```{toctree}
:maxdepth: 1
:caption: API Usage Examples

api_examples/scatter_plot
```

```{toctree}
:maxdepth: 2
:caption: Modules
:caption: API Reference
:hidden:

reference/vuecore
```

```{toctree}
:maxdepth: 1
:caption: MISC:
:caption: Extra Materials
:hidden:

README.md
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ dependencies = [
"webweb",
"acore",
"dash-cytoscape",
"pydantic",
"nbformat>=4.2.0",
]

[project.optional-dependencies]
Expand Down
4 changes: 4 additions & 0 deletions src/vuecore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
import numpy as np
import pandas as pd

from .constants import PlotType, EngineType

__all__ = ["PlotType", "EngineType"]

plt.rcParams["figure.figsize"] = [4.0, 3.0]
plt.rcParams["pdf.fonttype"] = 42
plt.rcParams["ps.fonttype"] = 42
Expand Down
17 changes: 17 additions & 0 deletions src/vuecore/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from enum import auto

try:
from enum import StrEnum
except ImportError:
from strenum import StrEnum


class PlotType(StrEnum):
SCATTER = auto()
LINE = auto()
# Add other plot types as needed


class EngineType(StrEnum):
PLOTLY = auto()
# Add other engines as needed
8 changes: 8 additions & 0 deletions src/vuecore/engines/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from vuecore.engines.registry import get_builder, get_saver

# Import the engine modules to trigger their registration
from . import plotly # noqa: F401, E402

# from . import matplotlib # This is where you'd add a new engine

__all__ = ["get_builder", "get_saver"]
11 changes: 11 additions & 0 deletions src/vuecore/engines/plotly/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from vuecore.engines.registry import register_builder, register_saver
from vuecore import PlotType, EngineType

from .scatter import build as build_scatter
from .saver import save

# Register the functions with the central dispatcher
register_builder(
plot_type=PlotType.SCATTER, engine=EngineType.PLOTLY, func=build_scatter
)
register_saver(engine=EngineType.PLOTLY, func=save)
75 changes: 75 additions & 0 deletions src/vuecore/engines/plotly/saver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import plotly.graph_objects as go
import kaleido
from pathlib import Path


def save(fig: go.Figure, filepath: str) -> None:
"""
Saves a Plotly figure to a file, inferring the format from the extension.

This utility provides a single interface for exporting a figure to various
static and interactive formats.

Parameters
----------
fig : go.Figure
The Plotly figure object to save.
filepath : str
The destination path for the file (e.g., 'my_plot.png', 'figure.html').
The format is determined by the file extension.

Returns
-------
None

Raises
------
ValueError
If the file extension is not one of the supported formats.
ImportError
If required libraries for image export (e.g., kaleido) are not installed.

Examples
--------
>>> import plotly.express as px
>>> fig = px.scatter(x=[1, 2, 3], y=[1, 2, 3])
>>> # Save as an interactive HTML file
>>> save(fig, 'scatter.html')
Plot saved to scatter.html
>>> # Save as a static PNG image
>>> save(fig, 'scatter.png')
Plot saved to scatter.png
"""
path = Path(filepath)
suffix = path.suffix.lower()

try:
if suffix in [".png", ".jpg", ".jpeg", ".webp", ".svg", ".pdf"]:
try:
fig.write_image(filepath)
except RuntimeError as e:
if "Kaleido requires Google Chrome" in str(e):
print(
"[VueCore] Chrome not found. Attempting automatic install using `kaleido.get_chrome_sync()`..."
)
try:
kaleido.get_chrome_sync()
fig.write_image(filepath) # Retry after installing Chrome
except Exception as install_error:
raise RuntimeError(
"[VueCore] Failed to install Chrome automatically. "
"Please install it manually or run `plotly_get_chrome`."
) from install_error
else:
raise
elif suffix == ".html":
fig.write_html(filepath, include_plotlyjs="cdn")
else:
raise ValueError(
f"Unsupported file format: '{suffix}'. "
"Supported formats: .png, .jpg, .jpeg, .webp, .svg, .pdf, .html, .json"
)
except Exception as e:
raise RuntimeError(f"[VueCore] Failed to save plot: {filepath}") from e

print(f"[VueCore] Plot saved to {filepath}")
53 changes: 53 additions & 0 deletions src/vuecore/engines/plotly/scatter.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from vuecore.schemas.distribution.scatter import ScatterConfig
from vuecore.utils.statistics import get_density
from .theming import apply_scatter_theme


def build(data: pd.DataFrame, config: ScatterConfig) -> go.Figure:
"""
Creates a Plotly scatter plot figure from a DataFrame and configuration.

This function acts as a bridge between the abstract plot definition and the
Plotly Express implementation. It translates the validated configuration
into the arguments for `plotly.express.scatter`.

Parameters
----------
data : pd.DataFrame
The DataFrame containing the plot data.
config : ScatterConfig
The validated Pydantic model object containing all plot configurations.

Returns
-------
go.Figure
A `plotly.graph_objects.Figure` object representing the scatter plot.
"""
plot_args = {
"x": config.x,
"y": config.y,
"size": config.size,
"symbol": config.symbol,
"text": config.text,
"hover_data": config.hover_cols,
"trendline": config.trendline,
}

if config.color_by_density:
# Calculate density and pass it to the 'color' argument
density_values = get_density(data[config.x].values, data[config.y].values)
plot_args["color"] = density_values
else:
# Use standard group-based coloring
plot_args["color"] = config.group
plot_args["color_discrete_map"] = config.colors

fig = px.scatter(data, **plot_args)

# Apply theme
fig = apply_scatter_theme(fig, config)

return fig
Loading