From fc43949e8d3e10b884a0683284b177b95924594e Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Fri, 13 Jun 2025 13:09:59 +0200 Subject: [PATCH 01/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Add=20initial=20vers?= =?UTF-8?q?ion=20of=20project=20refactoring,=20using=20scatter=20plot=20as?= =?UTF-8?q?=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + docs/.gitignore | 3 + pyproject.toml | 3 +- src/vuecore/core/io/saver.py | 56 ++++++++++ .../core/plotting/plotly/converters.py | 40 +++++++ src/vuecore/core/plotting/plotly/utils.py | 40 +++++++ src/vuecore/plots/distribution/scatter.py | 104 ++++++++++++++++++ src/vuecore/schemas/distribution/scatter.py | 77 +++++++++++++ src/vuecore/utils/validation.py | 32 ++++++ 9 files changed, 357 insertions(+), 1 deletion(-) create mode 100644 src/vuecore/core/io/saver.py create mode 100644 src/vuecore/core/plotting/plotly/converters.py create mode 100644 src/vuecore/core/plotting/plotly/utils.py create mode 100644 src/vuecore/plots/distribution/scatter.py create mode 100644 src/vuecore/schemas/distribution/scatter.py create mode 100644 src/vuecore/utils/validation.py diff --git a/.gitignore b/.gitignore index 68bc17f..493f7fc 100644 --- a/.gitignore +++ b/.gitignore @@ -158,3 +158,6 @@ cython_debug/ # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ + +# Tests +*test* diff --git a/docs/.gitignore b/docs/.gitignore index 08a2f75..651dd1a 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -3,3 +3,6 @@ reference # builds _build jupyter_execute + +# VsCode +*.DS_Store diff --git a/pyproject.toml b/pyproject.toml index 16e5363..2f2c3bf 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,7 @@ dependencies = [ "nltk", "webweb", "acore", - "dash-cytoscape", + "dash-cytoscape" ] [project.optional-dependencies] @@ -59,6 +59,7 @@ dev = [ "wheel", "jupytext", "ipykernel", + "pydantic", ] [project.urls] diff --git a/src/vuecore/core/io/saver.py b/src/vuecore/core/io/saver.py new file mode 100644 index 0000000..652af14 --- /dev/null +++ b/src/vuecore/core/io/saver.py @@ -0,0 +1,56 @@ +import plotly.graph_objects as go +from pathlib import Path + + +def save_plot(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_plot(fig, 'scatter.html') + Plot saved to scatter.html + >>> # Save as a static PNG image + >>> save_plot(fig, 'scatter.png') + Plot saved to scatter.png + """ + path = Path(filepath) + suffix = path.suffix.lower() + + if suffix in [".png", ".jpg", ".jpeg", ".webp", ".svg", ".pdf"]: + fig.write_image(filepath) + elif suffix == ".html": + fig.write_html(filepath, include_plotlyjs="cdn") + elif suffix == ".json": + fig.write_json(filepath) + else: + raise ValueError( + f"Unsupported file format: '{suffix}'. Supported formats: .png, .svg, .pdf, .html, .json" + ) + + print(f"Plot saved to {filepath}") diff --git a/src/vuecore/core/plotting/plotly/converters.py b/src/vuecore/core/plotting/plotly/converters.py new file mode 100644 index 0000000..ccf8b9f --- /dev/null +++ b/src/vuecore/core/plotting/plotly/converters.py @@ -0,0 +1,40 @@ +# vuecore/core/plotting/plotly/converters.py +import pandas as pd +import plotly.express as px +import plotly.graph_objects as go +from ....schemas.distribution.scatter import ScatterConfig + + +def create_plotly_scatter(data: pd.DataFrame, config: ScatterConfig) -> go.Figure: + """ + Converts DataFrame and configuration into a Plotly scatter plot figure. + + 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. + """ + fig = px.scatter( + data, + x=config.x, + y=config.y, + color=config.group, + size=config.size, + symbol=config.symbol, + text=config.text, + hover_data=config.hover_cols, + color_discrete_map=config.colors, + trendline=config.trendline, + ) + return fig diff --git a/src/vuecore/core/plotting/plotly/utils.py b/src/vuecore/core/plotting/plotly/utils.py new file mode 100644 index 0000000..0f54562 --- /dev/null +++ b/src/vuecore/core/plotting/plotly/utils.py @@ -0,0 +1,40 @@ +import plotly.graph_objects as go +from ....schemas.distribution.scatter import ScatterConfig + + +def apply_plot_theme(fig: go.Figure, config: ScatterConfig) -> go.Figure: + """ + Applies a consistent layout and theme to a Plotly figure. + + 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. + + Parameters + ---------- + fig : go.Figure + The Plotly figure object to be styled. + config : ScatterConfig + The configuration object containing styling info like titles and dimensions. + + Returns + ------- + go.Figure + The styled Plotly figure object. + """ + fig.update_traces( + marker=dict(opacity=0.8, line=dict(width=0.5, color="DarkSlateGrey")), + selector=dict(mode="markers"), + ) + + 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(), + height=config.height, + width=config.width, + template="plotly_white", + legend=dict(orientation="h", yanchor="bottom", y=1.02, xanchor="right", x=1), + hovermode="closest", + ) + return fig diff --git a/src/vuecore/plots/distribution/scatter.py b/src/vuecore/plots/distribution/scatter.py new file mode 100644 index 0000000..af91127 --- /dev/null +++ b/src/vuecore/plots/distribution/scatter.py @@ -0,0 +1,104 @@ +import pandas as pd +import plotly.graph_objects as go + +from ...schemas.distribution.scatter import ScatterConfig + +# from ...utils.validation import validate_columns_exist +from ...core.plotting.plotly.converters import create_plotly_scatter +from ...core.plotting.plotly.utils import apply_plot_theme +from ...core.io.saver import save_plot + + +def create_scatter_plot( + data: pd.DataFrame, save_path: str = None, **kwargs +) -> go.Figure: + """ + Creates, styles, and optionally saves a high-level scatter plot. + + This is the main user-facing function that orchestrates the entire plotting + workflow: configuration validation, data checks, plot creation via the + engine-specific converter, and styling. + + Parameters + ---------- + data : pd.DataFrame + The DataFrame containing the data to be plotted. + save_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'). + By default None. + **kwargs + Keyword arguments for plot configuration. These are validated against + the `ScatterConfig` model. See `schemas.relational.scatter.ScatterConfig` + for all available options. + + Returns + ------- + go.Figure + The final, styled `plotly.graph_objects.Figure` object. + + Raises + ------ + pydantic.ValidationError + If the provided kwargs do not match the `ScatterConfig` schema. + ValueError + If columns specified in the configuration do not exist in the 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'}, + ... save_path="my_scatter_plot.html" + ... ) + >>> + >>> # The returned `fig` object can be displayed in a notebook or further modified + >>> # fig.show() + """ + # 1. Validate configuration using Pydantic + config = ScatterConfig(**kwargs) + + # 2. Perform data-specific validation + # required_cols = [ + # col + # for col in [ + # config.x, + # config.y, + # config.group, + # config.size, + # config.symbol, + # config.text, + # ] + # if col is not None + # ] + # validate_columns_exist(data, required_cols) + + # 3. Create the base figure (engine-specific) + fig = create_plotly_scatter(data, config) + + # 4. Apply the theme and styling + fig = apply_plot_theme(fig, config) + + # 5. Optionally save the plot + if save_path: + save_plot(fig, save_path) + + return fig diff --git a/src/vuecore/schemas/distribution/scatter.py b/src/vuecore/schemas/distribution/scatter.py new file mode 100644 index 0000000..525adfc --- /dev/null +++ b/src/vuecore/schemas/distribution/scatter.py @@ -0,0 +1,77 @@ +from typing import Dict, List, Optional +from pydantic import BaseModel, Field + + +class ScatterConfig(BaseModel): + """ + Pydantic model for validating and managing scatter plot configurations. + + This model defines all the possible parameters that can be used to customize + a scatter plot, from data mapping to styling and layout. It ensures that + user-provided configurations are type-safe and adhere to the expected structure. + + Attributes + ---------- + x : str + Column name for the x-axis. + y : str + Column name for the y-axis. + group : Optional[str] + Column for grouping data, typically used for coloring markers. + size : Optional[str] + Column to determine marker size, enabling a third dimension of data. + symbol : Optional[str] + Column to determine the shape of markers. + text : Optional[str] + Column for adding text labels directly onto markers. + hover_cols : List[str] + Additional data columns to display in the hover tooltip. + title : str + The main title of the plot. + x_title : Optional[str] + Custom title for the x-axis. If None, defaults to the `x` column name. + y_title : Optional[str] + Custom title for the y-axis. If None, defaults to the `y` column name. + height : int + Height of the plot in pixels. + width : int + Width of the plot in pixels. + colors : Optional[Dict[str, str]] + A dictionary mapping group names from the `group` column to specific colors. + trendline : Optional[str] + If specified, adds a trendline to the plot (e.g., 'ols', 'lowess'). + """ + + # Data mapping + x: str = Field(..., description="Column name for the x-axis.") + y: str = Field(..., description="Column name for the y-axis.") + group: Optional[str] = Field( + None, description="Column for grouping data, often used for color." + ) + size: Optional[str] = Field(None, description="Column to determine marker size.") + symbol: Optional[str] = Field( + None, description="Column to determine marker symbol." + ) + text: Optional[str] = Field(None, description="Column for text labels on markers.") + hover_cols: List[str] = Field( + [], description="Additional columns to show on hover." + ) + + # Styling and Layout + title: str = Field("Scatter Plot", description="The main title of the plot.") + x_title: Optional[str] = Field( + None, description="Title for the x-axis. Defaults to x column name." + ) + y_title: Optional[str] = Field( + None, description="Title for the y-axis. Defaults to y column name." + ) + height: int = Field(600, description="Height of the plot in pixels.") + width: int = Field(800, description="Width of the plot in pixels.") + colors: Optional[Dict[str, str]] = Field( + None, description="Mapping of group names to specific colors." + ) + + # Special features + trendline: Optional[str] = Field( + None, description="Adds a trendline. E.g., 'ols' for Ordinary Least Squares." + ) diff --git a/src/vuecore/utils/validation.py b/src/vuecore/utils/validation.py new file mode 100644 index 0000000..546b40f --- /dev/null +++ b/src/vuecore/utils/validation.py @@ -0,0 +1,32 @@ +import pandas as pd +from typing import List + + +def validate_columns_exist(df: pd.DataFrame, required_columns: List[str]) -> None: + """ + Validates that a list of required columns exists in a pandas DataFrame. + + This is a crucial pre-flight check before attempting to create a plot, + ensuring that all data mappings specified in the configuration are valid. + + Parameters + ---------- + df : pd.DataFrame + The pandas DataFrame to check. + required_columns : List[str] + A list of column names that must exist in the DataFrame's columns. + + Returns + ------- + None + + Raises + ------ + ValueError + If one or more columns from `required_columns` are not found in the DataFrame. + """ + missing_cols = [col for col in required_columns if col not in df.columns] + if missing_cols: + raise ValueError( + f"The following required columns are missing from the data: {missing_cols}" + ) From 9b54fc81733bd87fc2d942eb3f37f29b87078004 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 16 Jun 2025 17:17:09 +0200 Subject: [PATCH 02/19] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20code=20to?= =?UTF-8?q?=20support=20various=20engines=20and=20handle=20this=20logic=20?= =?UTF-8?q?with=20reusable=20functions=20and=20modules?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pyproject.toml | 4 +- .../core/plotting/plotly/converters.py | 40 ------------ src/vuecore/engines/__init__.py | 39 +++++++++++ src/vuecore/engines/plotly/__init__.py | 7 ++ .../{core/io => engines/plotly}/saver.py | 6 +- src/vuecore/engines/plotly/scatter.py | 53 +++++++++++++++ .../utils.py => engines/plotly/theming.py} | 9 ++- src/vuecore/plots/distribution/scatter.py | 65 +++++++++---------- src/vuecore/schemas/distribution/scatter.py | 24 ++++++- src/vuecore/utils/statistics.py | 24 +++++++ src/vuecore/{utils.py => utils_old.py} | 0 11 files changed, 188 insertions(+), 83 deletions(-) delete mode 100644 src/vuecore/core/plotting/plotly/converters.py create mode 100644 src/vuecore/engines/__init__.py create mode 100644 src/vuecore/engines/plotly/__init__.py rename src/vuecore/{core/io => engines/plotly}/saver.py (92%) create mode 100644 src/vuecore/engines/plotly/scatter.py rename src/vuecore/{core/plotting/plotly/utils.py => engines/plotly/theming.py} (78%) create mode 100644 src/vuecore/utils/statistics.py rename src/vuecore/{utils.py => utils_old.py} (100%) diff --git a/pyproject.toml b/pyproject.toml index 2f2c3bf..6d99997 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,7 +36,8 @@ dependencies = [ "nltk", "webweb", "acore", - "dash-cytoscape" + "dash-cytoscape", + "pydantic", ] [project.optional-dependencies] @@ -59,7 +60,6 @@ dev = [ "wheel", "jupytext", "ipykernel", - "pydantic", ] [project.urls] diff --git a/src/vuecore/core/plotting/plotly/converters.py b/src/vuecore/core/plotting/plotly/converters.py deleted file mode 100644 index ccf8b9f..0000000 --- a/src/vuecore/core/plotting/plotly/converters.py +++ /dev/null @@ -1,40 +0,0 @@ -# vuecore/core/plotting/plotly/converters.py -import pandas as pd -import plotly.express as px -import plotly.graph_objects as go -from ....schemas.distribution.scatter import ScatterConfig - - -def create_plotly_scatter(data: pd.DataFrame, config: ScatterConfig) -> go.Figure: - """ - Converts DataFrame and configuration into a Plotly scatter plot figure. - - 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. - """ - fig = px.scatter( - data, - x=config.x, - y=config.y, - color=config.group, - size=config.size, - symbol=config.symbol, - text=config.text, - hover_data=config.hover_cols, - color_discrete_map=config.colors, - trendline=config.trendline, - ) - return fig diff --git a/src/vuecore/engines/__init__.py b/src/vuecore/engines/__init__.py new file mode 100644 index 0000000..981aff0 --- /dev/null +++ b/src/vuecore/engines/__init__.py @@ -0,0 +1,39 @@ +from typing import Callable, Any + +# Registries to hold the functions from each backend +PLOT_BUILDERS = {} +PLOT_SAVERS = {} + + +def register_builder(plot_type: str, engine: str, func: Callable): + """Registers a plot builder function for a given type and engine.""" + if engine not in PLOT_BUILDERS: + PLOT_BUILDERS[engine] = {} + PLOT_BUILDERS[engine][plot_type] = func + + +def register_saver(engine: str, func: Callable): + """Registers a save function for a given engine.""" + PLOT_SAVERS[engine] = func + + +def get_builder(plot_type: str, engine: str) -> Callable: + """Retrieves a plot builder function from the registry.""" + try: + return PLOT_BUILDERS[engine][plot_type] + except KeyError: + raise ValueError(f"No '{plot_type}' builder found for engine '{engine}'") + + +def get_saver(engine: str) -> Callable: + """Retrieves a save function from the registry.""" + try: + return PLOT_SAVERS[engine] + except KeyError: + raise ValueError(f"No saver found for engine '{engine}'") + + +# Import the engine modules to trigger their registration +from . import plotly + +# from . import matplotlib # This is where you'd add a new engine diff --git a/src/vuecore/engines/plotly/__init__.py b/src/vuecore/engines/plotly/__init__.py new file mode 100644 index 0000000..b2c4951 --- /dev/null +++ b/src/vuecore/engines/plotly/__init__.py @@ -0,0 +1,7 @@ +from vuecore.engines import register_builder, register_saver +from .scatter import build as build_scatter +from .saver import save + +# Register the functions with the central dispatcher +register_builder(plot_type="scatter", engine="plotly", func=build_scatter) +register_saver(engine="plotly", func=save) diff --git a/src/vuecore/core/io/saver.py b/src/vuecore/engines/plotly/saver.py similarity index 92% rename from src/vuecore/core/io/saver.py rename to src/vuecore/engines/plotly/saver.py index 652af14..c529a3e 100644 --- a/src/vuecore/core/io/saver.py +++ b/src/vuecore/engines/plotly/saver.py @@ -2,7 +2,7 @@ from pathlib import Path -def save_plot(fig: go.Figure, filepath: str) -> None: +def save(fig: go.Figure, filepath: str) -> None: """ Saves a Plotly figure to a file, inferring the format from the extension. @@ -33,10 +33,10 @@ def save_plot(fig: go.Figure, filepath: str) -> None: >>> import plotly.express as px >>> fig = px.scatter(x=[1, 2, 3], y=[1, 2, 3]) >>> # Save as an interactive HTML file - >>> save_plot(fig, 'scatter.html') + >>> save(fig, 'scatter.html') Plot saved to scatter.html >>> # Save as a static PNG image - >>> save_plot(fig, 'scatter.png') + >>> save(fig, 'scatter.png') Plot saved to scatter.png """ path = Path(filepath) diff --git a/src/vuecore/engines/plotly/scatter.py b/src/vuecore/engines/plotly/scatter.py new file mode 100644 index 0000000..b09608c --- /dev/null +++ b/src/vuecore/engines/plotly/scatter.py @@ -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 right after building + fig = apply_scatter_theme(fig, config) + + return fig diff --git a/src/vuecore/core/plotting/plotly/utils.py b/src/vuecore/engines/plotly/theming.py similarity index 78% rename from src/vuecore/core/plotting/plotly/utils.py rename to src/vuecore/engines/plotly/theming.py index 0f54562..d94a6ac 100644 --- a/src/vuecore/core/plotting/plotly/utils.py +++ b/src/vuecore/engines/plotly/theming.py @@ -1,8 +1,8 @@ import plotly.graph_objects as go -from ....schemas.distribution.scatter import ScatterConfig +from vuecore.schemas.distribution.scatter import ScatterConfig -def apply_plot_theme(fig: go.Figure, config: ScatterConfig) -> go.Figure: +def apply_scatter_theme(fig: go.Figure, config: ScatterConfig) -> go.Figure: """ Applies a consistent layout and theme to a Plotly figure. @@ -23,7 +23,10 @@ def apply_plot_theme(fig: go.Figure, config: ScatterConfig) -> go.Figure: The styled Plotly figure object. """ fig.update_traces( - marker=dict(opacity=0.8, line=dict(width=0.5, color="DarkSlateGrey")), + marker=dict( + opacity=config.marker_opacity, + line=dict(width=config.marker_line_width, color=config.marker_line_color), + ), selector=dict(mode="markers"), ) diff --git a/src/vuecore/plots/distribution/scatter.py b/src/vuecore/plots/distribution/scatter.py index af91127..21bb44a 100644 --- a/src/vuecore/plots/distribution/scatter.py +++ b/src/vuecore/plots/distribution/scatter.py @@ -1,29 +1,25 @@ import pandas as pd import plotly.graph_objects as go -from ...schemas.distribution.scatter import ScatterConfig +from vuecore.schemas.distribution.scatter import ScatterConfig -# from ...utils.validation import validate_columns_exist -from ...core.plotting.plotly.converters import create_plotly_scatter -from ...core.plotting.plotly.utils import apply_plot_theme -from ...core.io.saver import save_plot +from vuecore.utils.validation import validate_columns_exist +from vuecore.engines import get_builder, get_saver def create_scatter_plot( - data: pd.DataFrame, save_path: str = None, **kwargs + data: pd.DataFrame, engine: str = "plotly", file_path: str = None, **kwargs ) -> go.Figure: """ - Creates, styles, and optionally saves a high-level scatter plot. - - This is the main user-facing function that orchestrates the entire plotting - workflow: configuration validation, data checks, plot creation via the - engine-specific converter, and styling. + Creates, styles, and optionally saves a scatter plot using the specified engine. Parameters ---------- data : pd.DataFrame The DataFrame containing the data to be plotted. - save_path : str, optional + engine : str, optional + The plotting engine to use (e.g., 'plotly'). Defaults to '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'). By default None. @@ -67,7 +63,7 @@ def create_scatter_plot( ... x_title="Log2 Fold Change", ... y_title="-Log10(P-value)", ... colors={'Up': '#d62728', 'Down': '#1f77b4', 'None': '#7f7f7f'}, - ... save_path="my_scatter_plot.html" + ... file_path="my_scatter_plot.html" ... ) >>> >>> # The returned `fig` object can be displayed in a notebook or further modified @@ -77,28 +73,29 @@ def create_scatter_plot( config = ScatterConfig(**kwargs) # 2. Perform data-specific validation - # required_cols = [ - # col - # for col in [ - # config.x, - # config.y, - # config.group, - # config.size, - # config.symbol, - # config.text, - # ] - # if col is not None - # ] - # validate_columns_exist(data, required_cols) + required_cols = [ + col + for col in [ + config.x, + config.y, + config.group, + config.size, + config.symbol, + config.text, + ] + if col is not None + ] + validate_columns_exist(data, required_cols) - # 3. Create the base figure (engine-specific) - fig = create_plotly_scatter(data, config) + # 2. Get the correct builder function from the registry + builder_func = get_builder(plot_type="scatter", engine=engine) - # 4. Apply the theme and styling - fig = apply_plot_theme(fig, config) + # 3. Build the figure object (the API doesn't know or care what type it is) + figure = builder_func(data, config) - # 5. Optionally save the plot - if save_path: - save_plot(fig, save_path) + # 4. Save the plot using the correct saver + if file_path: + saver_func = get_saver(engine=engine) + saver_func(figure, file_path) - return fig + return figure diff --git a/src/vuecore/schemas/distribution/scatter.py b/src/vuecore/schemas/distribution/scatter.py index 525adfc..eea798a 100644 --- a/src/vuecore/schemas/distribution/scatter.py +++ b/src/vuecore/schemas/distribution/scatter.py @@ -1,5 +1,5 @@ from typing import Dict, List, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, Field, model_validator class ScatterConfig(BaseModel): @@ -70,8 +70,30 @@ class ScatterConfig(BaseModel): colors: Optional[Dict[str, str]] = Field( None, description="Mapping of group names to specific colors." ) + marker_opacity: float = Field( + 0.8, ge=0, le=1, description="Opacity of the markers." + ) + marker_line_width: float = Field( + 0.5, ge=0, description="Width of the line surrounding each marker." + ) + marker_line_color: str = Field( + "DarkSlateGrey", description="Color of the line surrounding each marker." + ) # Special features trendline: Optional[str] = Field( None, description="Adds a trendline. E.g., 'ols' for Ordinary Least Squares." ) + color_by_density: bool = Field( + False, description="If True, color points by density instead of group." + ) + + @model_validator(mode="after") + def check_exclusive_coloring(self) -> "ScatterConfig": + """Ensure that coloring by group and by density are mutually exclusive.""" + if self.color_by_density and self.group: + raise ValueError( + "Cannot set 'group' when 'color_by_density' is True. " + "Coloring is mutually exclusive." + ) + return self diff --git a/src/vuecore/utils/statistics.py b/src/vuecore/utils/statistics.py new file mode 100644 index 0000000..cfec07b --- /dev/null +++ b/src/vuecore/utils/statistics.py @@ -0,0 +1,24 @@ +import numpy as np +from scipy import stats + + +def get_density(x: np.ndarray, y: np.ndarray) -> np.ndarray: + """ + Calculates the kernel density estimate for each point in a 2D dataset. + + Parameters + ---------- + x : np.ndarray + The x-coordinates of the data points. + y : np.ndarray + The y-coordinates of the data points. + + Returns + ------- + np.ndarray + An array of density values, one for each input (x, y) point. + """ + values = np.vstack([x, y]) + kernel = stats.gaussian_kde(values) + density = kernel(values) + return density diff --git a/src/vuecore/utils.py b/src/vuecore/utils_old.py similarity index 100% rename from src/vuecore/utils.py rename to src/vuecore/utils_old.py From 7d27bc89402239955312cce3c86481f322056d1d Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 16 Jun 2025 18:18:51 +0200 Subject: [PATCH 03/19] =?UTF-8?q?=F0=9F=8E=A8=20Add=20enums=20for=20plot?= =?UTF-8?q?=20and=20engine=20types?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vuecore/__init__.py | 2 ++ src/vuecore/constants.py | 17 +++++++++++++++++ src/vuecore/engines/plotly/__init__.py | 8 ++++++-- src/vuecore/engines/plotly/scatter.py | 2 +- src/vuecore/plots/distribution/scatter.py | 10 +++++++--- src/vuecore/schemas/distribution/scatter.py | 8 +++++++- 6 files changed, 40 insertions(+), 7 deletions(-) create mode 100644 src/vuecore/constants.py diff --git a/src/vuecore/__init__.py b/src/vuecore/__init__.py index fe664ca..3e8cf08 100644 --- a/src/vuecore/__init__.py +++ b/src/vuecore/__init__.py @@ -17,6 +17,8 @@ import numpy as np import pandas as pd +from .constants import PlotType, EngineType + plt.rcParams["figure.figsize"] = [4.0, 3.0] plt.rcParams["pdf.fonttype"] = 42 plt.rcParams["ps.fonttype"] = 42 diff --git a/src/vuecore/constants.py b/src/vuecore/constants.py new file mode 100644 index 0000000..b5e8519 --- /dev/null +++ b/src/vuecore/constants.py @@ -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 diff --git a/src/vuecore/engines/plotly/__init__.py b/src/vuecore/engines/plotly/__init__.py index b2c4951..b8cfae2 100644 --- a/src/vuecore/engines/plotly/__init__.py +++ b/src/vuecore/engines/plotly/__init__.py @@ -1,7 +1,11 @@ from vuecore.engines 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="scatter", engine="plotly", func=build_scatter) -register_saver(engine="plotly", func=save) +register_builder( + plot_type=PlotType.SCATTER, engine=EngineType.PLOTLY, func=build_scatter +) +register_saver(engine=EngineType.PLOTLY, func=save) diff --git a/src/vuecore/engines/plotly/scatter.py b/src/vuecore/engines/plotly/scatter.py index b09608c..b02fc9d 100644 --- a/src/vuecore/engines/plotly/scatter.py +++ b/src/vuecore/engines/plotly/scatter.py @@ -47,7 +47,7 @@ def build(data: pd.DataFrame, config: ScatterConfig) -> go.Figure: fig = px.scatter(data, **plot_args) - # Apply theme right after building + # Apply theme fig = apply_scatter_theme(fig, config) return fig diff --git a/src/vuecore/plots/distribution/scatter.py b/src/vuecore/plots/distribution/scatter.py index 21bb44a..6e7d99a 100644 --- a/src/vuecore/plots/distribution/scatter.py +++ b/src/vuecore/plots/distribution/scatter.py @@ -5,10 +5,14 @@ from vuecore.utils.validation import validate_columns_exist from vuecore.engines import get_builder, get_saver +from vuecore import EngineType def create_scatter_plot( - data: pd.DataFrame, engine: str = "plotly", file_path: str = None, **kwargs + data: pd.DataFrame, + engine: EngineType = EngineType.PLOTLY, + file_path: str = None, + **kwargs, ) -> go.Figure: """ Creates, styles, and optionally saves a scatter plot using the specified engine. @@ -17,8 +21,8 @@ def create_scatter_plot( ---------- data : pd.DataFrame The DataFrame containing the data to be plotted. - engine : str, optional - The plotting engine to use (e.g., 'plotly'). Defaults to 'plotly'. + 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'). diff --git a/src/vuecore/schemas/distribution/scatter.py b/src/vuecore/schemas/distribution/scatter.py index eea798a..12d8d6b 100644 --- a/src/vuecore/schemas/distribution/scatter.py +++ b/src/vuecore/schemas/distribution/scatter.py @@ -1,3 +1,4 @@ +from vuecore import PlotType from typing import Dict, List, Optional from pydantic import BaseModel, Field, model_validator @@ -11,11 +12,13 @@ class ScatterConfig(BaseModel): user-provided configurations are type-safe and adhere to the expected structure. Attributes - ---------- + --------- x : str Column name for the x-axis. y : str Column name for the y-axis. + type : Optional[PlotType] + The type of plot. Defaults to `SCATTER`. group : Optional[str] Column for grouping data, typically used for coloring markers. size : Optional[str] @@ -45,6 +48,9 @@ class ScatterConfig(BaseModel): # Data mapping x: str = Field(..., description="Column name for the x-axis.") y: str = Field(..., description="Column name for the y-axis.") + type: Optional[PlotType] = Field( + PlotType.SCATTER, description="Type of plot, defaults to SCATTER." + ) group: Optional[str] = Field( None, description="Column for grouping data, often used for color." ) From dd49d7b87562dc70aec0b4e90ad152155170b3d2 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 16 Jun 2025 19:05:13 +0200 Subject: [PATCH 04/19] =?UTF-8?q?=E2=9C=85=20Add=20tests=20for=20scatter?= =?UTF-8?q?=20plot=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- tests/test_scatter.py | 79 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 tests/test_scatter.py diff --git a/.gitignore b/.gitignore index 493f7fc..7c969c7 100644 --- a/.gitignore +++ b/.gitignore @@ -160,4 +160,4 @@ cython_debug/ #.idea/ # Tests -*test* +test_results/ diff --git a/tests/test_scatter.py b/tests/test_scatter.py new file mode 100644 index 0000000..f41603f --- /dev/null +++ b/tests/test_scatter.py @@ -0,0 +1,79 @@ +import pandas as pd +import pytest +from vuecore.plots.distribution.scatter import create_scatter_plot + + +@pytest.fixture +def sample_df(): + return 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"], + } + ) + + +@pytest.mark.parametrize("ext", ["png", "svg", "pdf", "html", "json"]) +def test_basic_scatter_plot(sample_df, tmp_path, ext): + """Test basic scatter plot creation and file output for multiple formats.""" + output_path = tmp_path / f"scatter_test.{ext}" + + fig = create_scatter_plot( + data=sample_df, + x="gene_expression", + y="log_p_value", + file_path=str(output_path), + ) + + assert fig is not None + assert output_path.exists() + assert output_path.stat().st_size > 0 + + +@pytest.mark.parametrize("ext", ["png", "svg", "pdf", "html", "json"]) +def test_advanced_scatter_plot(sample_df, tmp_path, ext): + """Test advanced scatter plot creation with multiple parameters and file output.""" + output_path = tmp_path / f"scatter_test.{ext}" + + fig = 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=str(output_path), + ) + + assert fig is not None + assert output_path.exists() + assert output_path.stat().st_size > 0 + + +def test_missing_column_error(sample_df): + """Test error raised when required column is missing.""" + with pytest.raises(Exception) as exc_info: + create_scatter_plot(data=sample_df, x="non_existent_column", y="log_p_value") + assert "non_existent_column" in str(exc_info.value) From 4b5b43dde4352e8c709778fc734b43afb81a733e Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 16 Jun 2025 19:14:44 +0200 Subject: [PATCH 05/19] =?UTF-8?q?=F0=9F=93=9D=20Add=20docstrings=20and=20e?= =?UTF-8?q?nums=20in=20the=20plotly/=5F=5Finit=5F=5F.py=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vuecore/engines/__init__.py | 93 +++++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 9 deletions(-) diff --git a/src/vuecore/engines/__init__.py b/src/vuecore/engines/__init__.py index 981aff0..d857888 100644 --- a/src/vuecore/engines/__init__.py +++ b/src/vuecore/engines/__init__.py @@ -1,32 +1,107 @@ -from typing import Callable, Any +from typing import Callable +from vuecore import PlotType, EngineType # Registries to hold the functions from each backend PLOT_BUILDERS = {} PLOT_SAVERS = {} -def register_builder(plot_type: str, engine: str, func: Callable): - """Registers a plot builder function for a given type and engine.""" +def register_builder(plot_type: PlotType, engine: EngineType, func: Callable): + """ + Registers a plot builder function for a given plot type and engine. + + This allows dynamic dispatch of plotting functions depending on the desired + plot type (e.g., scatter, histogram) and backend engine (e.g., Plotly, Matplotlib). + + Parameters + ---------- + plot_type : PlotType + The type of plot (e.g., SCATTER). + engine : EngineType + The rendering engine (e.g., PLOTLY). + func : Callable + The plotting function to register for this type and engine. + + Returns + ------- + None + """ if engine not in PLOT_BUILDERS: PLOT_BUILDERS[engine] = {} PLOT_BUILDERS[engine][plot_type] = func -def register_saver(engine: str, func: Callable): - """Registers a save function for a given engine.""" +def register_saver(engine: EngineType, func: Callable): + """ + Registers a save function for a given engine. + + This allows saving plots using engine-specific logic (e.g., Plotly's `write_image`, + Matplotlib's `savefig`, etc.). + + Parameters + ---------- + engine : EngineType + The rendering engine for which to register the saver function. + func : Callable + The saving function to use for this engine. + + Returns + ------- + None + """ PLOT_SAVERS[engine] = func -def get_builder(plot_type: str, engine: str) -> Callable: - """Retrieves a plot builder function from the registry.""" +def get_builder(plot_type: PlotType, engine: EngineType) -> Callable: + """ + Retrieves a plot builder function from the registry. + + Looks up the plotting function based on the specified plot type and engine. + + Parameters + ---------- + plot_type : PlotType + The type of plot to retrieve. + engine : EngineType + The engine used to render the plot. + + Returns + ------- + Callable + The registered plotting function. + + Raises + ------ + ValueError + If no function is found for the given plot type and engine. + """ try: return PLOT_BUILDERS[engine][plot_type] except KeyError: raise ValueError(f"No '{plot_type}' builder found for engine '{engine}'") -def get_saver(engine: str) -> Callable: - """Retrieves a save function from the registry.""" +def get_saver(engine: EngineType) -> Callable: + """ + Retrieves a save function from the registry. + + Returns the function used to save plots for the specified engine. + + Parameters + ---------- + engine : EngineType + The engine for which the saving function should be retrieved. + + Returns + ------- + Callable + The registered saving function. + + Raises + ------ + ValueError + If no saver function is registered for the given engine. + """ try: return PLOT_SAVERS[engine] except KeyError: From 1ec04f20018a75f5ddd4bc129702c4012ba3bff9 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 16 Jun 2025 22:54:12 +0200 Subject: [PATCH 06/19] =?UTF-8?q?=F0=9F=90=9B=20Fix=20ruff=20errors=20duri?= =?UTF-8?q?ng=20CI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vuecore/__init__.py | 2 ++ src/vuecore/engines/__init__.py | 11 +++++------ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/vuecore/__init__.py b/src/vuecore/__init__.py index 3e8cf08..8db0d4f 100644 --- a/src/vuecore/__init__.py +++ b/src/vuecore/__init__.py @@ -19,6 +19,8 @@ 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 diff --git a/src/vuecore/engines/__init__.py b/src/vuecore/engines/__init__.py index d857888..85c59fc 100644 --- a/src/vuecore/engines/__init__.py +++ b/src/vuecore/engines/__init__.py @@ -1,6 +1,11 @@ from typing import Callable from vuecore import PlotType, EngineType +# 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 + # Registries to hold the functions from each backend PLOT_BUILDERS = {} PLOT_SAVERS = {} @@ -106,9 +111,3 @@ def get_saver(engine: EngineType) -> Callable: return PLOT_SAVERS[engine] except KeyError: raise ValueError(f"No saver found for engine '{engine}'") - - -# Import the engine modules to trigger their registration -from . import plotly - -# from . import matplotlib # This is where you'd add a new engine From 58e96dba4e135a5a5697f13eaf4d0712a80d0b57 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Mon, 23 Jun 2025 16:33:15 +0200 Subject: [PATCH 07/19] =?UTF-8?q?=F0=9F=93=9D=20Add=20notebook=20to=20show?= =?UTF-8?q?=20scatter=20plot=20examples,=20and=20remove=20validation=20tha?= =?UTF-8?q?t=20will=20be=20handle=20with=20pandera?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/api_examples/scatter_plot.ipynb | 235 ++++++++++++++++++ .../plots/{distribution => basic}/scatter.py | 10 +- src/vuecore/utils/validation.py | 31 --- tests/test_scatter.py | 7 - 5 files changed, 242 insertions(+), 42 deletions(-) create mode 100644 docs/api_examples/scatter_plot.ipynb rename src/vuecore/plots/{distribution => basic}/scatter.py (93%) diff --git a/.gitignore b/.gitignore index 0159aa5..1eafbcf 100644 --- a/.gitignore +++ b/.gitignore @@ -164,3 +164,4 @@ cython_debug/ # Tests test_results/ +docs/api_examples/outputs/ diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb new file mode 100644 index 0000000..773ab29 --- /dev/null +++ b/docs/api_examples/scatter_plot.ipynb @@ -0,0 +1,235 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "640ba1d5", + "metadata": {}, + "source": [ + "# Scatter Plot Examples\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 scatter plots using plotting functions from VueCore. We showcase basic and \n", + "advanced plot configurations, highlighting key customization options such as grouping, color mapping, text annotations, and export \n", + "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 \n", + "basic and advanced scatter plots.\n", + "\n", + "0. [Work environment setup](#0-work-environment-setup)\n", + "1. [Basic scatter plot](#1-basic-scatter-plot)\n", + "2. [Advanced scatter plot](#2-advanced-scatter-plot)\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/scatter_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": null, + "id": "963a9529", + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "IN_COLAB = \"COLAB_GPU\" in os.environ" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "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": null, + "id": "06dbf6a2", + "metadata": {}, + "outputs": [], + "source": [ + "# Imports\n", + "import pandas as pd\n", + "from vuecore.plots.basic.scatter import create_scatter_plot" + ] + }, + { + "cell_type": "markdown", + "id": "ade445fe", + "metadata": {}, + "source": [ + "## 1. Basic Scatter Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "19f0277d", + "metadata": {}, + "outputs": [], + "source": [ + "# Created sample data\n", + "sample_df = pd.DataFrame({\n", + " \"gene_expression\": [1.2, 2.5, 3.1, 4.5, 5.2, 6.8, 3.9, 2.1],\n", + " \"log_p_value\": [0.5, 1.5, 2.0, 3.5, 4.0, 5.5, 1.8, 0.9],\n", + " \"regulation\": [\"Up\", \"Up\", \"None\", \"Down\", \"Down\", \"Down\", \"None\", \"Up\"],\n", + " \"significance_score\": [10, 20, 5, 40, 55, 80, 15, 25],\n", + " \"gene_name\": [\"GENE_A\", \"GENE_B\", \"GENE_C\", \"GENE_D\", \"GENE_E\", \"GENE_F\", \"GENE_G\", \"GENE_H\"],\n", + " \"cell_type\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n", + "})\n", + "\n", + "# Define output path\n", + "file_path_png = os.path.join(output_dir, \"scatter_basic.png\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d34455", + "metadata": {}, + "outputs": [], + "source": [ + "# Generate basic plot\n", + "fig = create_scatter_plot(\n", + " data=sample_df,\n", + " x=\"gene_expression\",\n", + " y=\"log_p_value\",\n", + " file_path=file_path_png,\n", + ")\n", + "\n", + "fig.show()" + ] + }, + { + "cell_type": "markdown", + "id": "f5e16637", + "metadata": {}, + "source": [ + "## 2. Advanced Scatter Plot" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f9307e85", + "metadata": {}, + "outputs": [], + "source": [ + "# Define output path\n", + "file_path_adv_html = os.path.join(output_dir, \"scatter_advanced.html\")\n", + "\n", + "# Generate advanced plot\n", + "fig_advanced = create_scatter_plot(\n", + " data=sample_df,\n", + " x=\"gene_expression\",\n", + " y=\"log_p_value\",\n", + " group=\"regulation\",\n", + " size=\"significance_score\",\n", + " text=\"gene_name\",\n", + " title=\"Advanced Gene Expression Plot\",\n", + " x_title=\"Log2 Fold Change\",\n", + " y_title=\"-Log10(P-value)\",\n", + " colors={\"Up\": \"#FF5733\", \"Down\": \"#3380FF\", \"None\": \"#33FF57\"},\n", + " marker_opacity=0.8,\n", + " marker_line_width=1,\n", + " marker_line_color=\"darkgray\",\n", + " width=900,\n", + " height=600,\n", + " file_path=file_path_adv_html,\n", + ")\n", + "\n", + "fig_advanced.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/src/vuecore/plots/distribution/scatter.py b/src/vuecore/plots/basic/scatter.py similarity index 93% rename from src/vuecore/plots/distribution/scatter.py rename to src/vuecore/plots/basic/scatter.py index 6e7d99a..f29a2a3 100644 --- a/src/vuecore/plots/distribution/scatter.py +++ b/src/vuecore/plots/basic/scatter.py @@ -1,5 +1,5 @@ import pandas as pd -import plotly.graph_objects as go +from typing import Any from vuecore.schemas.distribution.scatter import ScatterConfig @@ -13,7 +13,7 @@ def create_scatter_plot( engine: EngineType = EngineType.PLOTLY, file_path: str = None, **kwargs, -) -> go.Figure: +) -> Any: """ Creates, styles, and optionally saves a scatter plot using the specified engine. @@ -34,8 +34,10 @@ def create_scatter_plot( Returns ------- - go.Figure - The final, styled `plotly.graph_objects.Figure` object. + Any + The final plot object returned by the selected engine. + For Plotly, this will be a `plotly.graph_objects.Figure`. + For Matplotlib, a `matplotlib.figure.Figure`, etc. Raises ------ diff --git a/src/vuecore/utils/validation.py b/src/vuecore/utils/validation.py index 546b40f..8b13789 100644 --- a/src/vuecore/utils/validation.py +++ b/src/vuecore/utils/validation.py @@ -1,32 +1 @@ -import pandas as pd -from typing import List - -def validate_columns_exist(df: pd.DataFrame, required_columns: List[str]) -> None: - """ - Validates that a list of required columns exists in a pandas DataFrame. - - This is a crucial pre-flight check before attempting to create a plot, - ensuring that all data mappings specified in the configuration are valid. - - Parameters - ---------- - df : pd.DataFrame - The pandas DataFrame to check. - required_columns : List[str] - A list of column names that must exist in the DataFrame's columns. - - Returns - ------- - None - - Raises - ------ - ValueError - If one or more columns from `required_columns` are not found in the DataFrame. - """ - missing_cols = [col for col in required_columns if col not in df.columns] - if missing_cols: - raise ValueError( - f"The following required columns are missing from the data: {missing_cols}" - ) diff --git a/tests/test_scatter.py b/tests/test_scatter.py index f41603f..e7a0114 100644 --- a/tests/test_scatter.py +++ b/tests/test_scatter.py @@ -70,10 +70,3 @@ def test_advanced_scatter_plot(sample_df, tmp_path, ext): assert fig is not None assert output_path.exists() assert output_path.stat().st_size > 0 - - -def test_missing_column_error(sample_df): - """Test error raised when required column is missing.""" - with pytest.raises(Exception) as exc_info: - create_scatter_plot(data=sample_df, x="non_existent_column", y="log_p_value") - assert "non_existent_column" in str(exc_info.value) From 155c76e615a1d2ef09ab07aaa4970afecc93d9ab Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Tue, 24 Jun 2025 15:49:33 +0200 Subject: [PATCH 08/19] =?UTF-8?q?=F0=9F=90=9B=20Fix=20circular=20import=20?= =?UTF-8?q?bug,=20add=20try-except=20block=20to=20install=20chrmore=20to?= =?UTF-8?q?=20export=20images=20using=20kaleido,=20and=20update=20sactter?= =?UTF-8?q?=20plot=20notebook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + docs/api_examples/scatter_plot.ipynb | 4170 +++++++++++++++++++++++- pyproject.toml | 1 + src/vuecore/engines/__init__.py | 109 +- src/vuecore/engines/plotly/__init__.py | 2 +- src/vuecore/engines/plotly/saver.py | 44 +- src/vuecore/engines/registry.py | 108 + src/vuecore/plots/basic/scatter.py | 16 - 8 files changed, 4304 insertions(+), 147 deletions(-) create mode 100644 src/vuecore/engines/registry.py diff --git a/.gitignore b/.gitignore index 1eafbcf..9e03fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -165,3 +165,4 @@ cython_debug/ # Tests test_results/ docs/api_examples/outputs/ +docs/api_examples/iframe_figures/ diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb index 773ab29..85494d9 100644 --- a/docs/api_examples/scatter_plot.ipynb +++ b/docs/api_examples/scatter_plot.ipynb @@ -64,7 +64,177 @@ "hide-output" ] }, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Processing /Users/asaru/Documents/DTU/MoNA/VueCore/vuecore\n", + " Installing build dependencies ... \u001b[?25ldone\n", + "\u001b[?25h Getting requirements to build wheel ... \u001b[?25ldone\n", + "\u001b[?25h Preparing metadata (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25hRequirement already satisfied: numpy in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (2.2.6)\n", + "Requirement already satisfied: pandas in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (2.3.0)\n", + "Requirement already satisfied: scipy in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (1.16.0)\n", + "Requirement already satisfied: plotly in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (6.1.2)\n", + "Requirement already satisfied: beautifulsoup4 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (4.13.4)\n", + "Requirement already satisfied: requests in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (2.32.4)\n", + "Requirement already satisfied: dash in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.4)\n", + "Requirement already satisfied: networkx in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (3.5)\n", + "Requirement already satisfied: matplotlib in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (3.10.3)\n", + "Requirement already satisfied: kaleido in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (1.0.0)\n", + "Requirement already satisfied: pyvis in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (0.3.1)\n", + "Requirement already satisfied: wordcloud in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (1.9.4)\n", + "Requirement already satisfied: cyjupyter in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (0.2.0)\n", + "Requirement already satisfied: nltk in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (3.9.1)\n", + "Requirement already satisfied: webweb in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (0.0.37)\n", + "Requirement already satisfied: acore in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (0.1.3)\n", + "Requirement already satisfied: dash-cytoscape in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (1.0.2)\n", + "Requirement already satisfied: pydantic in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from vuecore==0.0.6.dev19+g58e96db.d20250624) (2.11.7)\n", + "Collecting nbformat>=4.2.0 (from vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached nbformat-5.10.4-py3-none-any.whl.metadata (3.6 kB)\n", + "Collecting fastjsonschema>=2.15 (from nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached fastjsonschema-2.21.1-py3-none-any.whl.metadata (2.2 kB)\n", + "Collecting jsonschema>=2.6 (from nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached jsonschema-4.24.0-py3-none-any.whl.metadata (7.8 kB)\n", + "Requirement already satisfied: jupyter-core!=5.0.*,>=4.12 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624) (5.8.1)\n", + "Requirement already satisfied: traitlets>=5.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624) (5.14.3)\n", + "Collecting attrs>=22.2.0 (from jsonschema>=2.6->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached attrs-25.3.0-py3-none-any.whl.metadata (10 kB)\n", + "Collecting jsonschema-specifications>=2023.03.6 (from jsonschema>=2.6->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached jsonschema_specifications-2025.4.1-py3-none-any.whl.metadata (2.9 kB)\n", + "Collecting referencing>=0.28.4 (from jsonschema>=2.6->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached referencing-0.36.2-py3-none-any.whl.metadata (2.8 kB)\n", + "Collecting rpds-py>=0.7.1 (from jsonschema>=2.6->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624)\n", + " Using cached rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl.metadata (4.1 kB)\n", + "Requirement already satisfied: platformdirs>=2.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from jupyter-core!=5.0.*,>=4.12->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.3.8)\n", + "Requirement already satisfied: typing-extensions>=4.4.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from referencing>=0.28.4->jsonschema>=2.6->nbformat>=4.2.0->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.14.0)\n", + "Requirement already satisfied: dsp-pandas in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.0.5)\n", + "Requirement already satisfied: scikit-learn>=1.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.7.0)\n", + "Requirement already satisfied: biopython in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.85)\n", + "Requirement already satisfied: combat in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.3.3)\n", + "Requirement already satisfied: gseapy!=1.1.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.1.9)\n", + "Requirement already satisfied: kmapper in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.1.0)\n", + "Requirement already satisfied: lifelines in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.30.0)\n", + "Requirement already satisfied: pingouin in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.5.5)\n", + "Requirement already satisfied: python-louvain in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.16)\n", + "Requirement already satisfied: PyWGCNA!=2.2.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.2.1)\n", + "Requirement already satisfied: snfpy in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.2.2)\n", + "Requirement already satisfied: umap-learn in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.5.7)\n", + "Requirement already satisfied: statsmodels in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.14.4)\n", + "Requirement already satisfied: inmoose in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.7.7)\n", + "Requirement already satisfied: rarfile in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.2)\n", + "Requirement already satisfied: contourpy>=1.0.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.3.2)\n", + "Requirement already satisfied: cycler>=0.10 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.12.1)\n", + "Requirement already satisfied: fonttools>=4.22.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.58.4)\n", + "Requirement already satisfied: kiwisolver>=1.3.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.4.8)\n", + "Requirement already satisfied: packaging>=20.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (25.0)\n", + "Requirement already satisfied: pillow>=8 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (11.2.1)\n", + "Requirement already satisfied: pyparsing>=2.3.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.2.3)\n", + "Requirement already satisfied: python-dateutil>=2.7 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.9.0.post0)\n", + "Requirement already satisfied: six>=1.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from python-dateutil>=2.7->matplotlib->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.17.0)\n", + "Requirement already satisfied: seaborn>=0.11.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.13.2)\n", + "Requirement already satisfied: biomart>=0.9.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.9.2)\n", + "Requirement already satisfied: setuptools>=67.4.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (80.9.0)\n", + "Requirement already satisfied: reactome2py>=3.0.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.0)\n", + "Requirement already satisfied: anndata>=0.10.8 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.11.4)\n", + "Requirement already satisfied: rsrc>=0.1.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.1.3)\n", + "Requirement already satisfied: psutil>=5.9.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (7.0.0)\n", + "Requirement already satisfied: jinja2>=2.9.6 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.1.6)\n", + "Requirement already satisfied: ipython>=5.3.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (9.3.0)\n", + "Requirement already satisfied: jsonpickle>=1.4.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.1.1)\n", + "Requirement already satisfied: array-api-compat!=1.5,>1.4 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from anndata>=0.10.8->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.12.0)\n", + "Requirement already satisfied: h5py>=3.7 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from anndata>=0.10.8->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.14.0)\n", + "Requirement already satisfied: natsort in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from anndata>=0.10.8->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (8.4.0)\n", + "Requirement already satisfied: decorator in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (5.2.1)\n", + "Requirement already satisfied: ipython-pygments-lexers in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.1.1)\n", + "Requirement already satisfied: jedi>=0.16 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.19.2)\n", + "Requirement already satisfied: matplotlib-inline in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.1.7)\n", + "Requirement already satisfied: pexpect>4.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.9.0)\n", + "Requirement already satisfied: prompt_toolkit<3.1.0,>=3.0.41 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.51)\n", + "Requirement already satisfied: pygments>=2.4.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.19.2)\n", + "Requirement already satisfied: stack_data in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.6.3)\n", + "Requirement already satisfied: wcwidth in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from prompt_toolkit<3.1.0,>=3.0.41->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.2.13)\n", + "Requirement already satisfied: parso<0.9.0,>=0.8.4 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from jedi>=0.16->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.8.4)\n", + "Requirement already satisfied: MarkupSafe>=2.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from jinja2>=2.9.6->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.2)\n", + "Requirement already satisfied: pytz>=2020.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pandas->vuecore==0.0.6.dev19+g58e96db.d20250624) (2025.2)\n", + "Requirement already satisfied: tzdata>=2022.7 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pandas->vuecore==0.0.6.dev19+g58e96db.d20250624) (2025.2)\n", + "Requirement already satisfied: ptyprocess>=0.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pexpect>4.3->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.7.0)\n", + "Requirement already satisfied: json5>=0.8.4 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from reactome2py>=3.0.0->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.12.0)\n", + "Requirement already satisfied: charset_normalizer<4,>=2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from requests->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.4.2)\n", + "Requirement already satisfied: idna<4,>=2.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from requests->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.10)\n", + "Requirement already satisfied: urllib3<3,>=1.21.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from requests->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.5.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from requests->vuecore==0.0.6.dev19+g58e96db.d20250624) (2025.6.15)\n", + "Requirement already satisfied: memoir>=0.0.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from rsrc>=0.1.3->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.0.3)\n", + "Requirement already satisfied: reprit>=0.3.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from rsrc>=0.1.3->PyWGCNA!=2.2.0->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.9.0)\n", + "Requirement already satisfied: joblib>=1.2.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from scikit-learn>=1.2->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.5.1)\n", + "Requirement already satisfied: threadpoolctl>=3.1.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from scikit-learn>=1.2->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.6.0)\n", + "Requirement already satisfied: patsy>=0.5.6 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from statsmodels->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.0.1)\n", + "Requirement already satisfied: soupsieve>1.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from beautifulsoup4->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.7)\n", + "Requirement already satisfied: mpmath>=1.1.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from combat->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.3.0)\n", + "Requirement already satisfied: ipywidgets>=7.0.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from cyjupyter->vuecore==0.0.6.dev19+g58e96db.d20250624) (8.1.7)\n", + "Requirement already satisfied: comm>=0.1.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipywidgets>=7.0.0->cyjupyter->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.2.2)\n", + "Requirement already satisfied: widgetsnbextension~=4.0.14 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipywidgets>=7.0.0->cyjupyter->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.0.14)\n", + "Requirement already satisfied: jupyterlab_widgets~=3.0.15 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from ipywidgets>=7.0.0->cyjupyter->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.15)\n", + "Requirement already satisfied: Flask<3.1,>=1.0.4 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.3)\n", + "Requirement already satisfied: Werkzeug<3.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.6)\n", + "Requirement already satisfied: importlib-metadata in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (8.7.0)\n", + "Requirement already satisfied: retrying in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.3.5)\n", + "Requirement already satisfied: nest-asyncio in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.6.0)\n", + "Requirement already satisfied: itsdangerous>=2.1.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from Flask<3.1,>=1.0.4->dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.2.0)\n", + "Requirement already satisfied: click>=8.1.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from Flask<3.1,>=1.0.4->dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (8.2.1)\n", + "Requirement already satisfied: blinker>=1.6.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from Flask<3.1,>=1.0.4->dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.9.0)\n", + "Requirement already satisfied: narwhals>=1.15.1 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from plotly->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.44.0)\n", + "Requirement already satisfied: openpyxl in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from dsp-pandas->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.1.5)\n", + "Requirement already satisfied: zipp>=3.20 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from importlib-metadata->dash->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.23.0)\n", + "Requirement already satisfied: fastcluster in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from inmoose->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.3.0)\n", + "Requirement already satisfied: choreographer>=1.0.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from kaleido->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.0.9)\n", + "Requirement already satisfied: logistro>=1.0.8 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from kaleido->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.1.0)\n", + "Requirement already satisfied: orjson>=3.10.15 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from kaleido->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.10.18)\n", + "Requirement already satisfied: simplejson>=3.19.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from choreographer>=1.0.5->kaleido->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.20.1)\n", + "Requirement already satisfied: autograd>=1.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from lifelines->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.8.0)\n", + "Requirement already satisfied: autograd-gamma>=0.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from lifelines->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.5.0)\n", + "Requirement already satisfied: formulaic>=0.2.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from lifelines->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.1.1)\n", + "Requirement already satisfied: interface-meta>=1.2.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from formulaic>=0.2.2->lifelines->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.3.0)\n", + "Requirement already satisfied: wrapt>=1.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from formulaic>=0.2.2->lifelines->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (1.17.2)\n", + "Requirement already satisfied: regex>=2021.8.3 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from nltk->vuecore==0.0.6.dev19+g58e96db.d20250624) (2024.11.6)\n", + "Requirement already satisfied: tqdm in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from nltk->vuecore==0.0.6.dev19+g58e96db.d20250624) (4.67.1)\n", + "Requirement already satisfied: et-xmlfile in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from openpyxl->dsp-pandas->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.0.0)\n", + "Requirement already satisfied: pandas-flavor in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pingouin->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.7.0)\n", + "Requirement already satisfied: tabulate in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pingouin->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.9.0)\n", + "Requirement already satisfied: xarray in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pandas-flavor->pingouin->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (2025.6.1)\n", + "Requirement already satisfied: annotated-types>=0.6.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pydantic->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.7.0)\n", + "Requirement already satisfied: pydantic-core==2.33.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pydantic->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.33.2)\n", + "Requirement already satisfied: typing-inspection>=0.4.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from pydantic->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.4.1)\n", + "Requirement already satisfied: executing>=1.2.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from stack_data->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (2.2.0)\n", + "Requirement already satisfied: asttokens>=2.1.0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from stack_data->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (3.0.0)\n", + "Requirement already satisfied: pure_eval in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from stack_data->ipython>=5.3.0->pyvis->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.2.3)\n", + "Requirement already satisfied: numba>=0.51.2 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from umap-learn->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.61.2)\n", + "Requirement already satisfied: pynndescent>=0.5 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from umap-learn->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.5.13)\n", + "Requirement already satisfied: llvmlite<0.45,>=0.44.0dev0 in /Users/asaru/miniconda3/envs/vuecore-dev/lib/python3.12/site-packages (from numba>=0.51.2->umap-learn->acore->vuecore==0.0.6.dev19+g58e96db.d20250624) (0.44.0)\n", + "Using cached nbformat-5.10.4-py3-none-any.whl (78 kB)\n", + "Using cached fastjsonschema-2.21.1-py3-none-any.whl (23 kB)\n", + "Using cached jsonschema-4.24.0-py3-none-any.whl (88 kB)\n", + "Using cached attrs-25.3.0-py3-none-any.whl (63 kB)\n", + "Using cached jsonschema_specifications-2025.4.1-py3-none-any.whl (18 kB)\n", + "Using cached referencing-0.36.2-py3-none-any.whl (26 kB)\n", + "Using cached rpds_py-0.25.1-cp312-cp312-macosx_11_0_arm64.whl (350 kB)\n", + "Building wheels for collected packages: vuecore\n", + " Building wheel for vuecore (pyproject.toml) ... \u001b[?25ldone\n", + "\u001b[?25h Created wheel for vuecore: filename=vuecore-0.0.6.dev19+g58e96db.d20250624-py3-none-any.whl size=55625 sha256=dbbfec57f735203a8482a18fcaf73e20e081423669249c13003a42369f4aff2c\n", + " Stored in directory: /private/var/folders/64/3nt358294s9cjcj1qbtv1g_h0000gp/T/pip-ephem-wheel-cache-8mu7llb7/wheels/b7/c2/2a/80a17c0f7fe0587d17a3dc604f395e41401dc2c1e23b377f0e\n", + "Successfully built vuecore\n", + "Installing collected packages: fastjsonschema, rpds-py, attrs, referencing, jsonschema-specifications, jsonschema, nbformat, vuecore\n", + "\u001b[2K Attempting uninstall: vuecore━━━━━━━\u001b[0m\u001b[90m╺\u001b[0m\u001b[90m━━━━━━━━━\u001b[0m \u001b[32m6/8\u001b[0m [nbformat]a]]\n", + "\u001b[2K Found existing installation: vuecore 0.0.6.dev19+g58e96db.d20250624m6/8\u001b[0m [nbformat]\n", + "\u001b[2K Uninstalling vuecore-0.0.6.dev19+g58e96db.d20250624:━━━━━━\u001b[0m \u001b[32m6/8\u001b[0m [nbformat]\n", + "\u001b[2K Successfully uninstalled vuecore-0.0.6.dev19+g58e96db.d2025062432m6/8\u001b[0m [nbformat]\n", + "\u001b[2K \u001b[90m━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\u001b[0m \u001b[32m8/8\u001b[0m [vuecore]m7/8\u001b[0m [vuecore]\n", + "\u001b[1A\u001b[2KSuccessfully installed attrs-25.3.0 fastjsonschema-2.21.1 jsonschema-4.24.0 jsonschema-specifications-2025.4.1 nbformat-5.10.4 referencing-0.36.2 rpds-py-0.25.1 vuecore-0.0.6.dev19+g58e96db.d20250624\n", + "Note: you may need to restart the kernel to use updated packages.\n" + ] + } + ], "source": [ "# VueCore library\n", "%pip install vuecore" @@ -72,7 +242,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "963a9529", "metadata": { "tags": [ @@ -88,7 +258,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "ee2ffd40", "metadata": { "tags": [ @@ -98,7 +268,7 @@ "outputs": [], "source": [ "# Create a directory for outputs\n", - "output_dir = \"outputs\"\n", + "output_dir = \"./outputs\"\n", "os.makedirs(output_dir, exist_ok=True)" ] }, @@ -112,14 +282,18 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "06dbf6a2", "metadata": {}, "outputs": [], "source": [ "# Imports\n", "import pandas as pd\n", - "from vuecore.plots.basic.scatter import create_scatter_plot" + "import plotly.io as pio\n", + "from vuecore.plots.basic.scatter import create_scatter_plot\n", + "\n", + "# Set the Plotly renderer based on the environment\n", + "pio.renderers.default = \"notebook\"" ] }, { @@ -132,7 +306,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "19f0277d", "metadata": {}, "outputs": [], @@ -153,10 +327,3943 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "d0d34455", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[VueCore] Plot saved to ./outputs/scatter_basic.png\n" + ] + }, + { + "data": { + "text/html": [ + " \n", + " \n", + " " + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# Generate basic plot\n", "fig = create_scatter_plot(\n", @@ -179,10 +4286,51 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "f9307e85", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "[VueCore] Plot saved to ./outputs/scatter_advanced.html\n" + ] + }, + { + "data": { + "text/html": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# Define output path\n", "file_path_adv_html = os.path.join(output_dir, \"scatter_advanced.html\")\n", diff --git a/pyproject.toml b/pyproject.toml index aa89434..8d3644e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -38,6 +38,7 @@ dependencies = [ "acore", "dash-cytoscape", "pydantic", + "nbformat>=4.2.0", ] [project.optional-dependencies] diff --git a/src/vuecore/engines/__init__.py b/src/vuecore/engines/__init__.py index 85c59fc..2834076 100644 --- a/src/vuecore/engines/__init__.py +++ b/src/vuecore/engines/__init__.py @@ -1,113 +1,8 @@ -from typing import Callable -from vuecore import PlotType, EngineType +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 -# Registries to hold the functions from each backend -PLOT_BUILDERS = {} -PLOT_SAVERS = {} - - -def register_builder(plot_type: PlotType, engine: EngineType, func: Callable): - """ - Registers a plot builder function for a given plot type and engine. - - This allows dynamic dispatch of plotting functions depending on the desired - plot type (e.g., scatter, histogram) and backend engine (e.g., Plotly, Matplotlib). - - Parameters - ---------- - plot_type : PlotType - The type of plot (e.g., SCATTER). - engine : EngineType - The rendering engine (e.g., PLOTLY). - func : Callable - The plotting function to register for this type and engine. - - Returns - ------- - None - """ - if engine not in PLOT_BUILDERS: - PLOT_BUILDERS[engine] = {} - PLOT_BUILDERS[engine][plot_type] = func - - -def register_saver(engine: EngineType, func: Callable): - """ - Registers a save function for a given engine. - - This allows saving plots using engine-specific logic (e.g., Plotly's `write_image`, - Matplotlib's `savefig`, etc.). - - Parameters - ---------- - engine : EngineType - The rendering engine for which to register the saver function. - func : Callable - The saving function to use for this engine. - - Returns - ------- - None - """ - PLOT_SAVERS[engine] = func - - -def get_builder(plot_type: PlotType, engine: EngineType) -> Callable: - """ - Retrieves a plot builder function from the registry. - - Looks up the plotting function based on the specified plot type and engine. - - Parameters - ---------- - plot_type : PlotType - The type of plot to retrieve. - engine : EngineType - The engine used to render the plot. - - Returns - ------- - Callable - The registered plotting function. - - Raises - ------ - ValueError - If no function is found for the given plot type and engine. - """ - try: - return PLOT_BUILDERS[engine][plot_type] - except KeyError: - raise ValueError(f"No '{plot_type}' builder found for engine '{engine}'") - - -def get_saver(engine: EngineType) -> Callable: - """ - Retrieves a save function from the registry. - - Returns the function used to save plots for the specified engine. - - Parameters - ---------- - engine : EngineType - The engine for which the saving function should be retrieved. - - Returns - ------- - Callable - The registered saving function. - - Raises - ------ - ValueError - If no saver function is registered for the given engine. - """ - try: - return PLOT_SAVERS[engine] - except KeyError: - raise ValueError(f"No saver found for engine '{engine}'") +__all__ = ["get_builder", "get_saver"] diff --git a/src/vuecore/engines/plotly/__init__.py b/src/vuecore/engines/plotly/__init__.py index b8cfae2..da8e945 100644 --- a/src/vuecore/engines/plotly/__init__.py +++ b/src/vuecore/engines/plotly/__init__.py @@ -1,4 +1,4 @@ -from vuecore.engines import register_builder, register_saver +from vuecore.engines.registry import register_builder, register_saver from vuecore import PlotType, EngineType from .scatter import build as build_scatter diff --git a/src/vuecore/engines/plotly/saver.py b/src/vuecore/engines/plotly/saver.py index c529a3e..d33ce7e 100644 --- a/src/vuecore/engines/plotly/saver.py +++ b/src/vuecore/engines/plotly/saver.py @@ -1,4 +1,6 @@ import plotly.graph_objects as go +import plotly.io as pio +import kaleido from pathlib import Path @@ -42,15 +44,33 @@ def save(fig: go.Figure, filepath: str) -> None: path = Path(filepath) suffix = path.suffix.lower() - if suffix in [".png", ".jpg", ".jpeg", ".webp", ".svg", ".pdf"]: - fig.write_image(filepath) - elif suffix == ".html": - fig.write_html(filepath, include_plotlyjs="cdn") - elif suffix == ".json": - fig.write_json(filepath) - else: - raise ValueError( - f"Unsupported file format: '{suffix}'. Supported formats: .png, .svg, .pdf, .html, .json" - ) - - print(f"Plot saved to {filepath}") + 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, .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}") diff --git a/src/vuecore/engines/registry.py b/src/vuecore/engines/registry.py new file mode 100644 index 0000000..fac2e9b --- /dev/null +++ b/src/vuecore/engines/registry.py @@ -0,0 +1,108 @@ +from typing import Callable +from vuecore import PlotType, EngineType + +# Registries to hold the functions from each backend +PLOT_BUILDERS = {} +PLOT_SAVERS = {} + + +def register_builder(plot_type: PlotType, engine: EngineType, func: Callable): + """ + Registers a plot builder function for a given plot type and engine. + + This allows dynamic dispatch of plotting functions depending on the desired + plot type (e.g., scatter, histogram) and backend engine (e.g., Plotly, Matplotlib). + + Parameters + ---------- + plot_type : PlotType + The type of plot (e.g., SCATTER). + engine : EngineType + The rendering engine (e.g., PLOTLY). + func : Callable + The plotting function to register for this type and engine. + + Returns + ------- + None + """ + if engine not in PLOT_BUILDERS: + PLOT_BUILDERS[engine] = {} + PLOT_BUILDERS[engine][plot_type] = func + + +def register_saver(engine: EngineType, func: Callable): + """ + Registers a save function for a given engine. + + This allows saving plots using engine-specific logic (e.g., Plotly's `write_image`, + Matplotlib's `savefig`, etc.). + + Parameters + ---------- + engine : EngineType + The rendering engine for which to register the saver function. + func : Callable + The saving function to use for this engine. + + Returns + ------- + None + """ + PLOT_SAVERS[engine] = func + + +def get_builder(plot_type: PlotType, engine: EngineType) -> Callable: + """ + Retrieves a plot builder function from the registry. + + Looks up the plotting function based on the specified plot type and engine. + + Parameters + ---------- + plot_type : PlotType + The type of plot to retrieve. + engine : EngineType + The engine used to render the plot. + + Returns + ------- + Callable + The registered plotting function. + + Raises + ------ + ValueError + If no function is found for the given plot type and engine. + """ + try: + return PLOT_BUILDERS[engine][plot_type] + except KeyError: + raise ValueError(f"No '{plot_type}' builder found for engine '{engine}'") + + +def get_saver(engine: EngineType) -> Callable: + """ + Retrieves a save function from the registry. + + Returns the function used to save plots for the specified engine. + + Parameters + ---------- + engine : EngineType + The engine for which the saving function should be retrieved. + + Returns + ------- + Callable + The registered saving function. + + Raises + ------ + ValueError + If no saver function is registered for the given engine. + """ + try: + return PLOT_SAVERS[engine] + except KeyError: + raise ValueError(f"No saver found for engine '{engine}'") diff --git a/src/vuecore/plots/basic/scatter.py b/src/vuecore/plots/basic/scatter.py index f29a2a3..e8bdb49 100644 --- a/src/vuecore/plots/basic/scatter.py +++ b/src/vuecore/plots/basic/scatter.py @@ -3,7 +3,6 @@ from vuecore.schemas.distribution.scatter import ScatterConfig -from vuecore.utils.validation import validate_columns_exist from vuecore.engines import get_builder, get_saver from vuecore import EngineType @@ -78,21 +77,6 @@ def create_scatter_plot( # 1. Validate configuration using Pydantic config = ScatterConfig(**kwargs) - # 2. Perform data-specific validation - required_cols = [ - col - for col in [ - config.x, - config.y, - config.group, - config.size, - config.symbol, - config.text, - ] - if col is not None - ] - validate_columns_exist(data, required_cols) - # 2. Get the correct builder function from the registry builder_func = get_builder(plot_type="scatter", engine=engine) From fffa849955e90ef5a5bb873ce398ea97789acda1 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Tue, 24 Jun 2025 15:52:14 +0200 Subject: [PATCH 09/19] =?UTF-8?q?=E2=9E=96=20Remove=20unused=20dependency?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vuecore/engines/plotly/saver.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/vuecore/engines/plotly/saver.py b/src/vuecore/engines/plotly/saver.py index d33ce7e..2e8a60e 100644 --- a/src/vuecore/engines/plotly/saver.py +++ b/src/vuecore/engines/plotly/saver.py @@ -1,5 +1,4 @@ import plotly.graph_objects as go -import plotly.io as pio import kaleido from pathlib import Path From 44861193b7d7f1a70a22274f4d4d2cf341586626 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Tue, 24 Jun 2025 15:58:01 +0200 Subject: [PATCH 10/19] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Add=20all=20expected?= =?UTF-8?q?=20report=20formats=20on=20the=20value-error=20message?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/vuecore/engines/plotly/saver.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vuecore/engines/plotly/saver.py b/src/vuecore/engines/plotly/saver.py index 2e8a60e..7806278 100644 --- a/src/vuecore/engines/plotly/saver.py +++ b/src/vuecore/engines/plotly/saver.py @@ -67,7 +67,7 @@ def save(fig: go.Figure, filepath: str) -> None: else: raise ValueError( f"Unsupported file format: '{suffix}'. " - "Supported formats: .png, .svg, .pdf, .html, .json" + "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 From 95bb54e597cd69bc484ddc73186f84f8e5d3512f Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 10:48:27 +0200 Subject: [PATCH 11/19] =?UTF-8?q?=F0=9F=92=9A=20Add=20readthedocs=20depend?= =?UTF-8?q?encies=20to=20download=20chrome,=20which=20is=20required=20for?= =?UTF-8?q?=20kaleido?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index bc6495f..b835b43 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -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: From f68a35a059dc0481c62a13f5f546ac451bdcb88e Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 10:54:09 +0200 Subject: [PATCH 12/19] =?UTF-8?q?=E2=9C=8F=EF=B8=8F=20Correct=20identation?= =?UTF-8?q?=20on=20apt=20dependencies=20for=20readthedocs.yaml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index b835b43..96b810d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -11,18 +11,18 @@ build: tools: python: "3.12" apt_packages: - - libnss3 - - libatk-bridge2.0-0 - - libcups2 - - libxcomposite1 - - libxdamage1 - - libxfixes3 - - libxrandr2 - - libgbm1 - - libxkbcommon0 - - libpango-1.0-0 - - libcairo2 - - libasound2 + - 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: From 03eab2dfb1fd4757b5a6b7973ad4ff6790a069e6 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 12:16:26 +0200 Subject: [PATCH 13/19] =?UTF-8?q?=F0=9F=93=9D=20Add=20api=5Fexample/scatte?= =?UTF-8?q?r=20into=20docs,=20update=20notebook,=20and=20create=20python?= =?UTF-8?q?=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.py | 117 ++++++++++++++++++++++++++++++ docs/index.md | 11 ++- 2 files changed, 126 insertions(+), 2 deletions(-) create mode 100644 docs/api_examples/scatter_plot.py diff --git a/docs/api_examples/scatter_plot.py b/docs/api_examples/scatter_plot.py new file mode 100644 index 0000000..6f14410 --- /dev/null +++ b/docs/api_examples/scatter_plot.py @@ -0,0 +1,117 @@ +""" +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 + +# Create a directory for outputs +output_dir = "./outputs" +os.makedirs(output_dir, exist_ok=True) + +# %% +# 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" + +# %% +# 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() diff --git a/docs/index.md b/docs/index.md index 30c1e32..6b7fa9b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -7,9 +7,16 @@ :relative-images: ``` +```{toctree} +:maxdepth: 1 +:caption: API Usage Examples + +api_examples/scatter_plot +``` + ```{toctree} :maxdepth: 2 -:caption: Modules +:caption: API Reference :hidden: reference/vuecore @@ -17,7 +24,7 @@ reference/vuecore ```{toctree} :maxdepth: 1 -:caption: MISC: +:caption: Extra Materials :hidden: README.md From c96d9a989d5da0cdc921d4d60f65a6fd9d461bcc Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 12:22:39 +0200 Subject: [PATCH 14/19] =?UTF-8?q?=F0=9F=90=9B=20Fix=20indentation=20fro=20?= =?UTF-8?q?apt=20packages=20in=20readthedocs.yaml=20and=20imporitng=20issu?= =?UTF-8?q?e=20on=20scatter=20python=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .readthedocs.yaml | 26 +++++++++++++------------- docs/api_examples/scatter_plot.py | 5 ++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 96b810d..0713d0a 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -10,19 +10,19 @@ build: os: ubuntu-22.04 tools: python: "3.12" - apt_packages: - - libnss3 - - libatk-bridge2.0-0 - - libcups2 - - libxcomposite1 - - libxdamage1 - - libxfixes3 - - libxrandr2 - - libgbm1 - - libxkbcommon0 - - libpango-1.0-0 - - libcairo2 - - libasound2 + 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: diff --git a/docs/api_examples/scatter_plot.py b/docs/api_examples/scatter_plot.py index 6f14410..cef795f 100644 --- a/docs/api_examples/scatter_plot.py +++ b/docs/api_examples/scatter_plot.py @@ -37,15 +37,14 @@ # 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 +import os # Create a directory for outputs output_dir = "./outputs" os.makedirs(output_dir, exist_ok=True) # %% -# 0.2. Importing libraries -import os -import pandas as pd +# 0.2. Importing librariesimport pandas as pd import plotly.io as pio from vuecore.plots.basic.scatter import create_scatter_plot From 390551a3f8e1aa9df931dd470af70e722e145c32 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 12:25:35 +0200 Subject: [PATCH 15/19] =?UTF-8?q?=F0=9F=90=9B=20Fix=20import=20bug=20in=20?= =?UTF-8?q?scatter=20python=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/docs/api_examples/scatter_plot.py b/docs/api_examples/scatter_plot.py index cef795f..254471d 100644 --- a/docs/api_examples/scatter_plot.py +++ b/docs/api_examples/scatter_plot.py @@ -37,14 +37,21 @@ # 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 import os -# Create a directory for outputs +# 0.3. Create a directory for outputs output_dir = "./outputs" os.makedirs(output_dir, exist_ok=True) # %% -# 0.2. Importing librariesimport pandas as pd +# 0.2. Importing libraries +import pandas as pd import plotly.io as pio from vuecore.plots.basic.scatter import create_scatter_plot From 16d6827ead1bddec63265ff6c591c9e66c4ed5fc Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 12:28:51 +0200 Subject: [PATCH 16/19] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20on=20scatter?= =?UTF-8?q?=20python=20script?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.py | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/docs/api_examples/scatter_plot.py b/docs/api_examples/scatter_plot.py index 254471d..f2785d2 100644 --- a/docs/api_examples/scatter_plot.py +++ b/docs/api_examples/scatter_plot.py @@ -43,22 +43,15 @@ import pandas as pd import plotly.io as pio from vuecore.plots.basic.scatter import create_scatter_plot -import os - -# 0.3. Create a directory for outputs -output_dir = "./outputs" -os.makedirs(output_dir, exist_ok=True) - -# %% -# 0.2. Importing libraries -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 From 23e7252c9f0c8ea054e0faae38eec39e70b5c9bb Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 12:33:31 +0200 Subject: [PATCH 17/19] =?UTF-8?q?=F0=9F=93=9D=20Update=20scatter=20noteboo?= =?UTF-8?q?k=20header?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.ipynb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb index 85494d9..32f1a03 100644 --- a/docs/api_examples/scatter_plot.ipynb +++ b/docs/api_examples/scatter_plot.ipynb @@ -5,7 +5,7 @@ "id": "640ba1d5", "metadata": {}, "source": [ - "# Scatter Plot Examples\n", + "# Scatter Plot Examples using VueCore\n", "\n", "![VueCore logo][vuecore_logo]\n", "\n", @@ -27,6 +27,10 @@ "1. [Basic scatter plot](#1-basic-scatter-plot)\n", "2. [Advanced scatter plot](#2-advanced-scatter-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/scatter_plot.ipynb\n", "[vuecore_logo]: https://raw.githubusercontent.com/Multiomics-Analytics-Group/vuecore/main/docs/images/logo/vuecore_logo.svg\n", @@ -57,7 +61,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "36246ed6", "metadata": { "tags": [ From bd7e1ad02333d7976c19e99eab85ed4f2b86a210 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 13:28:17 +0200 Subject: [PATCH 18/19] =?UTF-8?q?=F0=9F=93=9D=20Modify=20header=20of=20sca?= =?UTF-8?q?tter=20notebook=20and=20add=20title=20to=20basic=20plot?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.ipynb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb index 32f1a03..6663eba 100644 --- a/docs/api_examples/scatter_plot.ipynb +++ b/docs/api_examples/scatter_plot.ipynb @@ -5,7 +5,7 @@ "id": "640ba1d5", "metadata": {}, "source": [ - "# Scatter Plot Examples using VueCore\n", + "# Scatter Plot\n", "\n", "![VueCore logo][vuecore_logo]\n", "\n", @@ -331,7 +331,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "d0d34455", "metadata": {}, "outputs": [ @@ -4274,6 +4274,7 @@ " data=sample_df,\n", " x=\"gene_expression\",\n", " y=\"log_p_value\",\n", + " title=\"Basic Gene Expression Scatter Plot\",\n", " file_path=file_path_png,\n", ")\n", "\n", @@ -4290,7 +4291,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "f9307e85", "metadata": {}, "outputs": [ @@ -4347,7 +4348,7 @@ " group=\"regulation\",\n", " size=\"significance_score\",\n", " text=\"gene_name\",\n", - " title=\"Advanced Gene Expression Plot\",\n", + " title=\"Advanced Gene Expression Scatter Plot\",\n", " x_title=\"Log2 Fold Change\",\n", " y_title=\"-Log10(P-value)\",\n", " colors={\"Up\": \"#FF5733\", \"Down\": \"#3380FF\", \"None\": \"#33FF57\"},\n", From 275db81ec0c9304bcd0f3d30cf66f079e6e60e13 Mon Sep 17 00:00:00 2001 From: sayalaruano Date: Wed, 25 Jun 2025 14:40:11 +0200 Subject: [PATCH 19/19] =?UTF-8?q?=F0=9F=93=9D=20Show=20df=20on=20the=20sca?= =?UTF-8?q?tter=20notebook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/api_examples/scatter_plot.ipynb | 143 ++++++++++++++++++++++++++- 1 file changed, 139 insertions(+), 4 deletions(-) diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb index 6663eba..3a98669 100644 --- a/docs/api_examples/scatter_plot.ipynb +++ b/docs/api_examples/scatter_plot.ipynb @@ -310,10 +310,143 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 11, "id": "19f0277d", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
gene_expressionlog_p_valueregulationsignificance_scoregene_namecell_type
01.20.5Up10GENE_AA
12.51.5Up20GENE_BB
23.12.0None5GENE_CA
34.53.5Down40GENE_DB
45.24.0Down55GENE_EA
56.85.5Down80GENE_FB
63.91.8None15GENE_GA
72.10.9Up25GENE_HB
\n", + "
" + ], + "text/plain": [ + " gene_expression log_p_value regulation significance_score gene_name \\\n", + "0 1.2 0.5 Up 10 GENE_A \n", + "1 2.5 1.5 Up 20 GENE_B \n", + "2 3.1 2.0 None 5 GENE_C \n", + "3 4.5 3.5 Down 40 GENE_D \n", + "4 5.2 4.0 Down 55 GENE_E \n", + "5 6.8 5.5 Down 80 GENE_F \n", + "6 3.9 1.8 None 15 GENE_G \n", + "7 2.1 0.9 Up 25 GENE_H \n", + "\n", + " cell_type \n", + "0 A \n", + "1 B \n", + "2 A \n", + "3 B \n", + "4 A \n", + "5 B \n", + "6 A \n", + "7 B " + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Created sample data\n", "sample_df = pd.DataFrame({\n", @@ -325,8 +458,7 @@ " \"cell_type\": [\"A\", \"B\", \"A\", \"B\", \"A\", \"B\", \"A\", \"B\"],\n", "})\n", "\n", - "# Define output path\n", - "file_path_png = os.path.join(output_dir, \"scatter_basic.png\")" + "sample_df" ] }, { @@ -4269,6 +4401,9 @@ } ], "source": [ + "# Define output path\n", + "file_path_png = os.path.join(output_dir, \"scatter_basic.png\")\n", + "\n", "# Generate basic plot\n", "fig = create_scatter_plot(\n", " data=sample_df,\n",