Skip to content
Merged
50 changes: 33 additions & 17 deletions docs/api_examples/scatter_plot.ipynb

Large diffs are not rendered by default.

25 changes: 18 additions & 7 deletions docs/api_examples/scatter_plot.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,10 @@
pio.renderers.default = "notebook"

# %% [markdown]
# ## 1. Basic Scatter Plot
# ### 0.3. Create sample data
# We create a synthetic dataset that contains simulated gene expression values, p-values, regulation status, and significance scores for 8 genes across two cell types.

# %%
# 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],
Expand All @@ -110,6 +110,10 @@

sample_df

# %% [markdown]
# ## 1. Basic Scatter Plot
# A basic scatter plot can be created by providing the `x` and `y` columns from the DataFrame, along with style options like `title`.

# %%
# Define output path
file_path_png = os.path.join(output_dir, "scatter_basic.png")
Expand All @@ -127,6 +131,7 @@

# %% [markdown]
# ## 2. Advanced Scatter Plot
# An example of an advanced scatter plot that includes grouping by a categorical variable, color mapping, text annotations, and adding several styling options.

# %%
# Define output path
Expand All @@ -137,14 +142,20 @@
data=sample_df,
x="gene_expression",
y="log_p_value",
group="regulation",
color="regulation",
size="significance_score",
text="gene_name",
title="Advanced Gene Expression Scatter Plot",
x_title="Log2 Fold Change",
y_title="-Log10(P-value)",
colors={"Up": "#FF5733", "Down": "#3380FF", "None": "#33FF57"},
marker_opacity=0.8,
subtitle="Visualizing Gene Expression with Regulation and Significance",
labels={
"gene_expression": "Log2 Fold Change",
"log_p_value": "Log P-value",
"regulation": "Regulation Status",
"significance_score": "Significance Score",
"gene_name": "Gene Name",
},
color_discrete_map={"Up": "#508AA8", "Down": "#A8505E", "None": "#838383"},
opacity=0.8,
marker_line_width=1,
marker_line_color="darkgray",
width=900,
Expand Down
55 changes: 41 additions & 14 deletions src/vuecore/engines/plotly/scatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,73 @@

def build(data: pd.DataFrame, config: ScatterConfig) -> go.Figure:
"""
Creates a Plotly scatter plot figure from a DataFrame and configuration.
Creates a Plotly scatter plot 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 configuration
into the arguments for `plotly.express.scatter`.
Plotly Express implementation. It translates the validated `ScattereConfig`
into the arguments for `plotly.express.scatter` and also forwards any
additional, unvalidated keyword arguments from plotly. The resulting figure
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you will add all plotly arguments per default?

Copy link
Collaborator Author

@sayalaruano sayalaruano Aug 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I added the most important ones, but also enabled passing extra arguments from plotly express. Do you think that would be better to just add all of them?

is then customized with layout and theme settings using `plotly.graph_objects`.
(https://plotly.com/python-api-reference/generated/plotly.express.scatter.html).
Parameters
----------
data : pd.DataFrame
The DataFrame containing the plot data.
config : ScatterConfig
The validated Pydantic model object containing all plot configurations.
The validated Pydantic model object with all plot configurations.
Returns
-------
go.Figure
A `plotly.graph_objects.Figure` object representing the scatter plot.
"""
# Get all parameters from the config model, including extras
all_config_params = config.model_dump()

# Define parameters handled by the theme script
theming_params = [
"opacity",
"log_x",
"log_y",
"range_x",
"range_y",
"title",
"subtitle",
"x_title",
"y_title",
"template",
"width",
"height",
"marker_line_width",
"marker_line_color",
"color_by_density",
]

# Create the dictionary of arguments for px.scatter
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,
k: v
for k, v in all_config_params.items()
if k not in theming_params and v is not None
}

# Handle density coloring separately
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

# Remove discrete color mapping for density plots
if "color_discrete_map" in plot_args:
del plot_args["color_discrete_map"]
else:
# Use standard group-based coloring
plot_args["color"] = config.group
plot_args["color_discrete_map"] = config.colors
plot_args["color"] = config.color

# Create the base figure using only the arguments for px.scatter
fig = px.scatter(data, **plot_args)

# Apply theme
# Apply theme and additional styling
fig = apply_scatter_theme(fig, config)

return fig
35 changes: 25 additions & 10 deletions src/vuecore/engines/plotly/theming.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,40 +5,55 @@

def apply_scatter_theme(fig: go.Figure, config: ScatterConfig) -> go.Figure:
"""
Applies a consistent layout and theme to a Plotly figure.
Applies a consistent layout and theme to a Plotly scatter plot.

This function separates styling from plot creation, allowing for a consistent
look and feel across different plot types. It updates traces and layout
properties based on the provided configuration.
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 : ScatterConfig
The configuration object containing styling info like titles and dimensions.
The configuration object containing all styling and layout info.

Returns
-------
go.Figure
The styled Plotly figure object.
"""
# Apply trace-specific updates
fig.update_traces(
marker=dict(
opacity=config.marker_opacity,
opacity=config.opacity,
line=dict(width=config.marker_line_width, color=config.marker_line_color),
),
selector=dict(mode="markers"),
)

# Use the labels dictionary to set axis titles, falling back to defaults
x_title = config.x_title or (
config.labels.get(config.x) if config.labels else None or config.x.title()
)
y_title = config.y_title or (
config.labels.get(config.y) if config.labels else None or config.y.title()
)

# Apply layout updates
fig.update_layout(
title_text=config.title,
xaxis_title=config.x_title or config.x.title(),
yaxis_title=config.y_title or config.y.title(),
title_subtitle_text=config.subtitle,
xaxis_title=x_title,
yaxis_title=y_title,
height=config.height,
width=config.width,
template="plotly_white",
legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
template=config.template,
xaxis_type="log" if config.log_x else "linear",
yaxis_type="log" if config.log_y else "linear",
xaxis_range=config.range_x,
yaxis_range=config.range_y,
# legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1),
hovermode="closest",
)
return fig
37 changes: 9 additions & 28 deletions src/vuecore/plots/basic/scatter.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,38 +41,19 @@ def create_scatter_plot(
Raises
------
pydantic.ValidationError
If the provided kwargs do not match the `ScatterConfig` schema.
If the provided keyword arguments do not conform to the `ScatterConfig` schema,
e.g., a required parameter is missing or a value has an incorrect type.
ValueError
If columns specified in the configuration do not exist in the DataFrame.
Raised by the plotting engine if a column specified in the configuration is not
found in the provided DataFrame.

Examples
--------
>>> import pandas as pd
>>> sample_df = pd.DataFrame({
... 'gene_expression': [1.2, 2.5, 3.1, 4.5, 5.2, 6.8],
... 'log_p_value': [0.5, 1.5, 2.0, 3.5, 4.0, 5.5],
... 'regulation': ['Up', 'Up', 'None', 'Down', 'Down', 'Down'],
... 'significance_score': [10, 20, 5, 40, 55, 80],
... 'gene_name': ['GENE_A', 'GENE_B', 'GENE_C', 'GENE_D', 'GENE_E', 'GENE_F']
... })
>>>
>>> # Create a simple scatter plot and save it to HTML
>>> fig = create_scatter_plot(
... data=sample_df,
... x='gene_expression',
... y='log_p_value',
... group='regulation',
... size='significance_score',
... text='gene_name',
... title="Gene Expression vs. Significance",
... x_title="Log2 Fold Change",
... y_title="-Log10(P-value)",
... colors={'Up': '#d62728', 'Down': '#1f77b4', 'None': '#7f7f7f'},
... file_path="my_scatter_plot.html"
... )
>>>
>>> # The returned `fig` object can be displayed in a notebook or further modified
>>> # fig.show()
For detailed examples and usage, please refer to the documentation:

* **Jupyter Notebook:** `docs/api_examples/scatter_plot.ipynb` -
https://vuecore.readthedocs.io/en/latest/api_examples/scatter_plot.html
* **Python Script:** `docs/api_examples/scatter_plot.py`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you want to have the link to GitHub?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will add the link for the Python Script on GitHub.

"""
# 1. Validate configuration using Pydantic
config = ScatterConfig(**kwargs)
Expand Down
Loading