diff --git a/.gitignore b/.gitignore index d3abe60..9e03fc6 100644 --- a/.gitignore +++ b/.gitignore @@ -161,3 +161,8 @@ cython_debug/ # VSCode *DS_Store + +# Tests +test_results/ +docs/api_examples/outputs/ +docs/api_examples/iframe_figures/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index bc6495f..0713d0a 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: diff --git a/docs/.gitignore b/docs/.gitignore index f1ff8c4..4818210 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -4,5 +4,5 @@ reference _build jupyter_execute -# VS Code -.DS_Store \ No newline at end of file +# VsCode +*.DS_Store \ No newline at end of file diff --git a/docs/api_examples/scatter_plot.ipynb b/docs/api_examples/scatter_plot.ipynb new file mode 100644 index 0000000..3a98669 --- /dev/null +++ b/docs/api_examples/scatter_plot.ipynb @@ -0,0 +1,4523 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "640ba1d5", + "metadata": {}, + "source": [ + "# Scatter Plot\n", + "\n", + "![VueCore logo][vuecore_logo]\n", + "\n", + "[![Open In Colab][colab_badge]][colab_link]\n", + "\n", + "[VueCore][vuecore_repo] is a Python package for creating interactive and static visualizations of multi-omics data. \n", + "It is part of a broader ecosystem of tools—including [ACore][acore_repo] for data processing and [VueGen][vuegen_repo] for automated reporting—that together enable end-to-end workflows for omics analysis.\n", + "\n", + "This notebook demonstrates how to generate 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", + "## 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", + "[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": 2, + "id": "36246ed6", + "metadata": { + "tags": [ + "hide-output" + ] + }, + "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" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "963a9529", + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "IN_COLAB = \"COLAB_GPU\" in os.environ" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "ee2ffd40", + "metadata": { + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "# Create a directory for outputs\n", + "output_dir = \"./outputs\"\n", + "os.makedirs(output_dir, exist_ok=True)" + ] + }, + { + "cell_type": "markdown", + "id": "31638f9a", + "metadata": {}, + "source": [ + "### 0.2. Importing libraries" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "06dbf6a2", + "metadata": {}, + "outputs": [], + "source": [ + "# Imports\n", + "import pandas as pd\n", + "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\"" + ] + }, + { + "cell_type": "markdown", + "id": "ade445fe", + "metadata": {}, + "source": [ + "## 1. Basic Scatter Plot" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "19f0277d", + "metadata": {}, + "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", + " \"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", + "sample_df" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d0d34455", + "metadata": {}, + "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": [ + "# 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", + " x=\"gene_expression\",\n", + " y=\"log_p_value\",\n", + " title=\"Basic Gene Expression Scatter Plot\",\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": [ + { + "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", + "\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 Scatter 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/docs/api_examples/scatter_plot.py b/docs/api_examples/scatter_plot.py new file mode 100644 index 0000000..f2785d2 --- /dev/null +++ b/docs/api_examples/scatter_plot.py @@ -0,0 +1,116 @@ +""" +Title: Scatter Plot Examples using VueCore +Description: + This script demonstrates how to generate scatter plots using VueCore — a Python package for creating + interactive and static visualizations of multi-omics data. It is part of an ecosystem including ACore + for data processing and VueGen for automated reporting. + + We showcase basic and advanced plot configurations, highlighting customization options such as grouping, + color mapping, annotations, and export to multiple formats. + +Script Structure: + 0. Work environment setup + 1. Basic scatter plot + 2. Advanced scatter plot + +Authors: + Sebastián Ayala-Ruano +Supervisors: + Henry Webel, Alberto Santos (Multiomics Network Analytics Group, DTU Biosustain) + +Institution: + Multiomics Network Analytics Group (MoNA), + Novo Nordisk Foundation Center for Biosustainability (DTU Biosustain) + +Project Repository: + https://github.com/Multiomics-Analytics-Group/vuecore + +License: + MIT License + +Created: 2025-06-25 +Last Updated: 2025-06-25 +""" + +# %% +# 0. Work environment setup +# 0.1. Installing libraries and creating global variables for platform and working directory +# To run this notebook locally, you should create a virtual environment with the required libraries. +# pip install vuecore + +# 0.2. Importing libraries +import os +import pandas as pd +import plotly.io as pio +from vuecore.plots.basic.scatter import create_scatter_plot + +# Set the Plotly renderer based on the environment, default to notebook, but you can change it +# to "browser" if you do not want to use jupyter widgets. +pio.renderers.default = "notebook" + +# 0.3. Create a directory for outputs +output_dir = "./outputs" +os.makedirs(output_dir, exist_ok=True) + +# %% +# 1. Basic Scatter Plot +# Created sample data +sample_df = pd.DataFrame( + { + "gene_expression": [1.2, 2.5, 3.1, 4.5, 5.2, 6.8, 3.9, 2.1], + "log_p_value": [0.5, 1.5, 2.0, 3.5, 4.0, 5.5, 1.8, 0.9], + "regulation": ["Up", "Up", "None", "Down", "Down", "Down", "None", "Up"], + "significance_score": [10, 20, 5, 40, 55, 80, 15, 25], + "gene_name": [ + "GENE_A", + "GENE_B", + "GENE_C", + "GENE_D", + "GENE_E", + "GENE_F", + "GENE_G", + "GENE_H", + ], + "cell_type": ["A", "B", "A", "B", "A", "B", "A", "B"], + } +) + +# Define output path +file_path_png = os.path.join(output_dir, "scatter_basic.png") + +# Generate basic plot +fig = create_scatter_plot( + data=sample_df, + x="gene_expression", + y="log_p_value", + file_path=file_path_png, +) + +fig.show() + +# %% +# 2. Advanced Scatter Plot +# Define output path +file_path_adv_html = os.path.join(output_dir, "scatter_advanced.html") + +# Generate advanced plot +fig_advanced = create_scatter_plot( + data=sample_df, + x="gene_expression", + y="log_p_value", + group="regulation", + size="significance_score", + text="gene_name", + title="Advanced Gene Expression Plot", + x_title="Log2 Fold Change", + y_title="-Log10(P-value)", + colors={"Up": "#FF5733", "Down": "#3380FF", "None": "#33FF57"}, + marker_opacity=0.8, + marker_line_width=1, + marker_line_color="darkgray", + width=900, + height=600, + file_path=file_path_adv_html, +) + +fig_advanced.show() 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 diff --git a/pyproject.toml b/pyproject.toml index 60a1c09..8d3644e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -37,6 +37,8 @@ dependencies = [ "webweb", "acore", "dash-cytoscape", + "pydantic", + "nbformat>=4.2.0", ] [project.optional-dependencies] diff --git a/src/vuecore/__init__.py b/src/vuecore/__init__.py index fe664ca..8db0d4f 100644 --- a/src/vuecore/__init__.py +++ b/src/vuecore/__init__.py @@ -17,6 +17,10 @@ import numpy as np import pandas as pd +from .constants import PlotType, EngineType + +__all__ = ["PlotType", "EngineType"] + plt.rcParams["figure.figsize"] = [4.0, 3.0] plt.rcParams["pdf.fonttype"] = 42 plt.rcParams["ps.fonttype"] = 42 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/__init__.py b/src/vuecore/engines/__init__.py new file mode 100644 index 0000000..2834076 --- /dev/null +++ b/src/vuecore/engines/__init__.py @@ -0,0 +1,8 @@ +from vuecore.engines.registry import get_builder, get_saver + +# Import the engine modules to trigger their registration +from . import plotly # noqa: F401, E402 + +# from . import matplotlib # This is where you'd add a new engine + +__all__ = ["get_builder", "get_saver"] diff --git a/src/vuecore/engines/plotly/__init__.py b/src/vuecore/engines/plotly/__init__.py new file mode 100644 index 0000000..da8e945 --- /dev/null +++ b/src/vuecore/engines/plotly/__init__.py @@ -0,0 +1,11 @@ +from vuecore.engines.registry import register_builder, register_saver +from vuecore import PlotType, EngineType + +from .scatter import build as build_scatter +from .saver import save + +# Register the functions with the central dispatcher +register_builder( + plot_type=PlotType.SCATTER, engine=EngineType.PLOTLY, func=build_scatter +) +register_saver(engine=EngineType.PLOTLY, func=save) diff --git a/src/vuecore/engines/plotly/saver.py b/src/vuecore/engines/plotly/saver.py new file mode 100644 index 0000000..7806278 --- /dev/null +++ b/src/vuecore/engines/plotly/saver.py @@ -0,0 +1,75 @@ +import plotly.graph_objects as go +import kaleido +from pathlib import Path + + +def save(fig: go.Figure, filepath: str) -> None: + """ + Saves a Plotly figure to a file, inferring the format from the extension. + + This utility provides a single interface for exporting a figure to various + static and interactive formats. + + Parameters + ---------- + fig : go.Figure + The Plotly figure object to save. + filepath : str + The destination path for the file (e.g., 'my_plot.png', 'figure.html'). + The format is determined by the file extension. + + Returns + ------- + None + + Raises + ------ + ValueError + If the file extension is not one of the supported formats. + ImportError + If required libraries for image export (e.g., kaleido) are not installed. + + Examples + -------- + >>> import plotly.express as px + >>> fig = px.scatter(x=[1, 2, 3], y=[1, 2, 3]) + >>> # Save as an interactive HTML file + >>> save(fig, 'scatter.html') + Plot saved to scatter.html + >>> # Save as a static PNG image + >>> save(fig, 'scatter.png') + Plot saved to scatter.png + """ + path = Path(filepath) + suffix = path.suffix.lower() + + try: + if suffix in [".png", ".jpg", ".jpeg", ".webp", ".svg", ".pdf"]: + try: + fig.write_image(filepath) + except RuntimeError as e: + if "Kaleido requires Google Chrome" in str(e): + print( + "[VueCore] Chrome not found. Attempting automatic install using `kaleido.get_chrome_sync()`..." + ) + try: + kaleido.get_chrome_sync() + fig.write_image(filepath) # Retry after installing Chrome + except Exception as install_error: + raise RuntimeError( + "[VueCore] Failed to install Chrome automatically. " + "Please install it manually or run `plotly_get_chrome`." + ) from install_error + else: + raise + elif suffix == ".html": + fig.write_html(filepath, include_plotlyjs="cdn") + else: + raise ValueError( + f"Unsupported file format: '{suffix}'. " + "Supported formats: .png, .jpg, .jpeg, .webp, .svg, .pdf, .html, .json" + ) + except Exception as e: + raise RuntimeError(f"[VueCore] Failed to save plot: {filepath}") from e + + print(f"[VueCore] Plot saved to {filepath}") diff --git a/src/vuecore/engines/plotly/scatter.py b/src/vuecore/engines/plotly/scatter.py new file mode 100644 index 0000000..b02fc9d --- /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 + fig = apply_scatter_theme(fig, config) + + return fig diff --git a/src/vuecore/engines/plotly/theming.py b/src/vuecore/engines/plotly/theming.py new file mode 100644 index 0000000..d94a6ac --- /dev/null +++ b/src/vuecore/engines/plotly/theming.py @@ -0,0 +1,43 @@ +import plotly.graph_objects as go +from vuecore.schemas.distribution.scatter import ScatterConfig + + +def apply_scatter_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=config.marker_opacity, + line=dict(width=config.marker_line_width, color=config.marker_line_color), + ), + 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/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 new file mode 100644 index 0000000..e8bdb49 --- /dev/null +++ b/src/vuecore/plots/basic/scatter.py @@ -0,0 +1,91 @@ +import pandas as pd +from typing import Any + +from vuecore.schemas.distribution.scatter import ScatterConfig + +from vuecore.engines import get_builder, get_saver +from vuecore import EngineType + + +def create_scatter_plot( + data: pd.DataFrame, + engine: EngineType = EngineType.PLOTLY, + file_path: str = None, + **kwargs, +) -> Any: + """ + Creates, styles, and optionally saves a scatter plot using the specified engine. + + Parameters + ---------- + data : pd.DataFrame + The DataFrame containing the data to be plotted. + 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'). + 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 + ------- + 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 + ------ + 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'}, + ... file_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. Get the correct builder function from the registry + builder_func = get_builder(plot_type="scatter", engine=engine) + + # 3. Build the figure object (the API doesn't know or care what type it is) + figure = builder_func(data, config) + + # 4. Save the plot using the correct saver + if file_path: + saver_func = get_saver(engine=engine) + saver_func(figure, file_path) + + return figure diff --git a/src/vuecore/schemas/distribution/scatter.py b/src/vuecore/schemas/distribution/scatter.py new file mode 100644 index 0000000..12d8d6b --- /dev/null +++ b/src/vuecore/schemas/distribution/scatter.py @@ -0,0 +1,105 @@ +from vuecore import PlotType +from typing import Dict, List, Optional +from pydantic import BaseModel, Field, model_validator + + +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. + 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] + 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.") + 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." + ) + 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." + ) + 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/validation.py b/src/vuecore/utils/validation.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/vuecore/utils/validation.py @@ -0,0 +1 @@ + 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 diff --git a/tests/test_scatter.py b/tests/test_scatter.py new file mode 100644 index 0000000..e7a0114 --- /dev/null +++ b/tests/test_scatter.py @@ -0,0 +1,72 @@ +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