diff --git a/.gitignore b/.gitignore index 7ad008943..07431f992 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,7 @@ docs/generated docs/gallery_scripts docs/_build .DS_Store +venv/ +.venv/ +_build/ +tmpclaude-c0db-cwd diff --git a/README.md b/README.md index 6afda21ed..8c0651f09 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,19 @@ pyOpenMS-Viz is a Python library that provides a simple interface for extending | PeakMap 3D | x, y, z | peakmap (plot3d=True) | ✓ | | ✓ | +### Note on Polars DataFrames +`pyopenms_viz` can work with [Polars](https://pola.rs/) DataFrames by internally converting them to pandas, often via an Arrow-backed representation where possible. This conversion may involve data copies depending on data types and configuration, and plotting is ultimately delegated to the pandas-based plotting backends. Due to API differences, you must use the `.ms_plot()` namespace instead of `.plot()` when working with Polars. + +**Pandas:** +```python +df_pandas.plot(kind="spectrum", x="m/z", y="intensity", backend="ms_plotly") +``` + +**Polars:** +```python +df_polars.ms_plot(kind="spectrum", x="m/z", y="intensity", backend="ms_plotly") +``` + ## (Recommended) Pip Installation The recommended way of installing pyopenms_viz is through the Python Package Index (PyPI). We recommend installing pyopenms_viz in its own virtual environment using Anaconda to avoid packaging conflicts. diff --git a/docs/Getting Started.ipynb b/docs/Getting Started.ipynb index 1fb03be3b..78692c703 100644 --- a/docs/Getting Started.ipynb +++ b/docs/Getting Started.ipynb @@ -3227,6 +3227,40 @@ " x_kind='chromatogram', # specify on x marginal axis to plot as a chromatogram rather than a spectrum \n", " grid=False) " ] + }, + { + "cell_type": "markdown", + "id": "4b971b57", + "metadata": {}, + "source": [ + "Using Polars DataFrames\n", + "\n", + "pyopenms_viz is deeply optimized for Pandas. While you can use Polars for high-speed data manipulation, downstream plotting libraries often force silent C-API memory materialization when handling PyArrow extension arrays.\n", + "\n", + "To maintain a strictly native, memory-safe footprint for large datasets, the recommended approach is to process your data in Polars, and then explicitly convert it to Pandas before calling the .ms_plot accessor." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12fa9e5a", + "metadata": {}, + "outputs": [], + "source": [ + "import polars as pl\n", + "\n", + "# Convert the existing 'spectrum_df' to Polars\n", + "df_polars = pl.from_pandas(spectrum_df)\n", + "\n", + "# ... perform your high-speed Polars operations here ...\n", + "\n", + "# Explicitly convert back to Pandas for memory-safe rendering\n", + "df_safe = df_polars.to_pandas()\n", + "\n", + "# Plot using the native pyopenms_viz plotting method\n", + "# (Using the 'mz' and 'intensity' columns from this notebook)\n", + "df_safe.plot(kind='spectrum', x='mz', y='intensity', backend='ms_bokeh')" + ] } ], "metadata": { diff --git a/docs/conf.py b/docs/conf.py index c6ca73fd4..9f591e5b5 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -312,7 +312,8 @@ def bokeh_scraper(block, block_vars, gallery_conf, **kwargs): "binder": { "org": "OpenMS", "repo": "pyopenms_viz", - "branch": "gh_pages", + # Use the source branch for Binder launches; gh_pages is a deploy artifact branch. + "branch": "main", "binderhub_url": "https://notebooks.gesis.org/binder", "dependencies": "./requirements.txt", }, diff --git a/pyopenms_viz/__init__.py b/pyopenms_viz/__init__.py index 7d24a4725..eb33fb4ba 100644 --- a/pyopenms_viz/__init__.py +++ b/pyopenms_viz/__init__.py @@ -1,7 +1,7 @@ """ init """ - +from . import _polars_accessor # Import for side effects: registers pandas accessors. noqa: F401 from pandas.plotting._core import PlotAccessor from pandas.core.frame import DataFrame from typing import Any diff --git a/pyopenms_viz/_polars_accessor.py b/pyopenms_viz/_polars_accessor.py new file mode 100644 index 000000000..a3fb8883c --- /dev/null +++ b/pyopenms_viz/_polars_accessor.py @@ -0,0 +1,30 @@ +""" +Polars namespace extension for pyopenms_viz. +""" +try: + import polars as pl + POLARS_AVAILABLE = True +except ImportError: + POLARS_AVAILABLE = False + +if POLARS_AVAILABLE: + @pl.api.register_dataframe_namespace("ms_plot") + class PolarsPlotAccessor: + def __init__(self, df: pl.DataFrame): + self._df = df + + def __call__(self, *args, **kwargs): + try: + # 1. Safety Catch: Use Arrow-backed arrays with a clear error fallback + pandas_df = self._df.to_pandas(use_pyarrow_extension_array=True) + except (ImportError, ModuleNotFoundError) as exc: + raise ImportError( + "Converting a Polars DataFrame to pandas failed because required " + "optional dependencies are missing. Please install 'pyarrow':\n" + " pip install pyarrow" + ) from exc + + # 2. Delegate to pandas' plotting API. + # This lets pandas resolve ms_* backends via entry points and respect + # the global `pd.options.plotting.backend` setting. + return pandas_df.plot(*args, **kwargs) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 28e8c040c..dda5d3d9f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -177,6 +177,8 @@ plotly==5.24.1 # via pyopenms_viz (pyproject.toml) pluggy==1.6.0 # via pytest +polars==1.38.1 + prompt-toolkit==3.0.52 # via ipython psutil==7.2.2 @@ -185,6 +187,8 @@ ptyprocess==0.7.0 # via pexpect pure-eval==0.2.3 # via stack-data +pyarrow==23.0.1 + # via polars pycparser==3.0 # via cffi pydata-sphinx-theme==0.16.1 @@ -215,6 +219,7 @@ pytz==2025.2 # via pandas pyyaml==6.0.3 # via bokeh + pyzmq==27.1.0 # via # ipykernel @@ -307,3 +312,4 @@ webencodings==0.5.1 # tinycss2 xyzservices==2025.11.0 # via bokeh + diff --git a/test/__snapshots__/test_polars_accessor/test_polars_ms_plot_snapshot.json b/test/__snapshots__/test_polars_accessor/test_polars_ms_plot_snapshot.json new file mode 100644 index 000000000..5408197e5 --- /dev/null +++ b/test/__snapshots__/test_polars_accessor/test_polars_ms_plot_snapshot.json @@ -0,0 +1,1090 @@ +{ + "data": [ + { + "line": { + "color": "#4575B4" + }, + "mode": "lines", + "x": [ + 100.1, + 100.1, + 100.1, + 100.2, + 100.2, + 100.2, + 105.0, + 105.0, + 105.0, + 105.1, + 105.1, + 105.1 + ], + "y": [ + 0.0, + 10.0, + 0.0, + 0.0, + 50.0, + 0.0, + 0.0, + 100.0, + 0.0, + 0.0, + 90.0, + 0.0 + ], + "type": "scatter", + "customdata": [ + [ + 100.1, + 10.0 + ], + [ + 100.1, + 10.0 + ], + [ + 100.1, + 10.0 + ], + [ + 100.2, + 50.0 + ], + [ + 100.2, + 50.0 + ], + [ + 100.2, + 50.0 + ], + [ + 105.0, + 100.0 + ], + [ + 105.0, + 100.0 + ], + [ + 105.0, + 100.0 + ], + [ + 105.1, + 90.0 + ], + [ + 105.1, + 90.0 + ], + [ + 105.1, + 90.0 + ] + ], + "hovertemplate": "m\u002fz: %{customdata[0]}\u003cbr\u003eintensity: %{customdata[1]}" + } + ], + "layout": { + "template": { + "data": { + "barpolar": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "barpolar" + } + ], + "bar": [ + { + "error_x": { + "color": "rgb(36,36,36)" + }, + "error_y": { + "color": "rgb(36,36,36)" + }, + "marker": { + "line": { + "color": "white", + "width": 0.5 + }, + "pattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + } + }, + "type": "bar" + } + ], + "carpet": [ + { + "aaxis": { + "endlinecolor": "rgb(36,36,36)", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "rgb(36,36,36)" + }, + "baxis": { + "endlinecolor": "rgb(36,36,36)", + "gridcolor": "white", + "linecolor": "white", + "minorgridcolor": "white", + "startlinecolor": "rgb(36,36,36)" + }, + "type": "carpet" + } + ], + "choropleth": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "choropleth" + } + ], + "contourcarpet": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "contourcarpet" + } + ], + "contour": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "contour" + } + ], + "heatmapgl": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "heatmapgl" + } + ], + "heatmap": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "heatmap" + } + ], + "histogram2dcontour": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "histogram2dcontour" + } + ], + "histogram2d": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "histogram2d" + } + ], + "histogram": [ + { + "marker": { + "line": { + "color": "white", + "width": 0.6 + } + }, + "type": "histogram" + } + ], + "mesh3d": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "type": "mesh3d" + } + ], + "parcoords": [ + { + "line": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "parcoords" + } + ], + "pie": [ + { + "automargin": true, + "type": "pie" + } + ], + "scatter3d": [ + { + "line": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatter3d" + } + ], + "scattercarpet": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattercarpet" + } + ], + "scattergeo": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattergeo" + } + ], + "scattergl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattergl" + } + ], + "scattermapbox": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scattermapbox" + } + ], + "scatterpolargl": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterpolargl" + } + ], + "scatterpolar": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterpolar" + } + ], + "scatter": [ + { + "fillpattern": { + "fillmode": "overlay", + "size": 10, + "solidity": 0.2 + }, + "type": "scatter" + } + ], + "scatterternary": [ + { + "marker": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "type": "scatterternary" + } + ], + "surface": [ + { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + }, + "colorscale": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "type": "surface" + } + ], + "table": [ + { + "cells": { + "fill": { + "color": "rgb(237,237,237)" + }, + "line": { + "color": "white" + } + }, + "header": { + "fill": { + "color": "rgb(217,217,217)" + }, + "line": { + "color": "white" + } + }, + "type": "table" + } + ] + }, + "layout": { + "annotationdefaults": { + "arrowhead": 0, + "arrowwidth": 1 + }, + "autotypenumbers": "strict", + "coloraxis": { + "colorbar": { + "outlinewidth": 1, + "tickcolor": "rgb(36,36,36)", + "ticks": "outside" + } + }, + "colorscale": { + "diverging": [ + [ + 0.0, + "rgb(103,0,31)" + ], + [ + 0.1, + "rgb(178,24,43)" + ], + [ + 0.2, + "rgb(214,96,77)" + ], + [ + 0.3, + "rgb(244,165,130)" + ], + [ + 0.4, + "rgb(253,219,199)" + ], + [ + 0.5, + "rgb(247,247,247)" + ], + [ + 0.6, + "rgb(209,229,240)" + ], + [ + 0.7, + "rgb(146,197,222)" + ], + [ + 0.8, + "rgb(67,147,195)" + ], + [ + 0.9, + "rgb(33,102,172)" + ], + [ + 1.0, + "rgb(5,48,97)" + ] + ], + "sequential": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ], + "sequentialminus": [ + [ + 0.0, + "#440154" + ], + [ + 0.1111111111111111, + "#482878" + ], + [ + 0.2222222222222222, + "#3e4989" + ], + [ + 0.3333333333333333, + "#31688e" + ], + [ + 0.4444444444444444, + "#26828e" + ], + [ + 0.5555555555555556, + "#1f9e89" + ], + [ + 0.6666666666666666, + "#35b779" + ], + [ + 0.7777777777777778, + "#6ece58" + ], + [ + 0.8888888888888888, + "#b5de2b" + ], + [ + 1.0, + "#fde725" + ] + ] + }, + "colorway": [ + "#1F77B4", + "#FF7F0E", + "#2CA02C", + "#D62728", + "#9467BD", + "#8C564B", + "#E377C2", + "#7F7F7F", + "#BCBD22", + "#17BECF" + ], + "font": { + "color": "rgb(36,36,36)" + }, + "geo": { + "bgcolor": "white", + "lakecolor": "white", + "landcolor": "white", + "showlakes": true, + "showland": true, + "subunitcolor": "white" + }, + "hoverlabel": { + "align": "left" + }, + "hovermode": "closest", + "mapbox": { + "style": "light" + }, + "paper_bgcolor": "white", + "plot_bgcolor": "white", + "polar": { + "angularaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "bgcolor": "white", + "radialaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + } + }, + "scene": { + "xaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "yaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "zaxis": { + "backgroundcolor": "white", + "gridcolor": "rgb(232,232,232)", + "gridwidth": 2, + "linecolor": "rgb(36,36,36)", + "showbackground": true, + "showgrid": false, + "showline": true, + "ticks": "outside", + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + } + }, + "shapedefaults": { + "fillcolor": "black", + "line": { + "width": 0 + }, + "opacity": 0.3 + }, + "ternary": { + "aaxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "baxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + }, + "bgcolor": "white", + "caxis": { + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside" + } + }, + "title": { + "x": 0.05 + }, + "xaxis": { + "automargin": true, + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside", + "title": { + "standoff": 15 + }, + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + }, + "yaxis": { + "automargin": true, + "gridcolor": "rgb(232,232,232)", + "linecolor": "rgb(36,36,36)", + "showgrid": false, + "showline": true, + "ticks": "outside", + "title": { + "standoff": 15 + }, + "zeroline": false, + "zerolinecolor": "rgb(36,36,36)" + } + } + }, + "title": { + "text": "Mass Spectrum", + "font": { + "family": "Helvetica", + "size": 18 + } + }, + "xaxis": { + "title": { + "text": "mass-to-charge", + "font": { + "family": "Helvetica", + "size": 16 + } + }, + "tickfont": { + "size": 14, + "family": "Helvetica" + }, + "gridcolor": "#CCCCCC", + "showgrid": true, + "showline": true, + "linewidth": 1, + "linecolor": "black", + "ticks": "outside", + "tickwidth": 1, + "tickcolor": "black", + "range": [ + 80.08, + 126.11999999999999 + ] + }, + "yaxis": { + "title": { + "text": "Intensity", + "font": { + "family": "Helvetica", + "size": 16 + } + }, + "tickfont": { + "size": 14, + "family": "Helvetica" + }, + "gridcolor": "#CCCCCC", + "showgrid": true, + "showline": true, + "linewidth": 1, + "linecolor": "black", + "tickwidth": 1, + "tickcolor": "black", + "range": [ + 0, + 115.0 + ] + }, + "width": 500, + "height": 500, + "dragmode": "select", + "legend": { + "font": { + "size": 10, + "family": "Helvetica" + }, + "title": { + "text": "Trace" + } + }, + "showlegend": true, + "font": { + "family": "Helvetica" + }, + "plot_bgcolor": "#FFFFFF", + "annotations": [ + { + "font": { + "color": "black", + "family": "Open Sans Mono, monospace", + "size": 12 + }, + "showarrow": false, + "text": "105.0", + "x": 105.0, + "xanchor": "left", + "y": 100.0 + }, + { + "font": { + "color": "black", + "family": "Open Sans Mono, monospace", + "size": 12 + }, + "showarrow": false, + "text": "105.1", + "x": 105.1, + "xanchor": "left", + "y": 90.0 + }, + { + "font": { + "color": "black", + "family": "Open Sans Mono, monospace", + "size": 12 + }, + "showarrow": false, + "text": "100.2", + "x": 100.2, + "xanchor": "left", + "y": 50.0 + }, + { + "font": { + "color": "black", + "family": "Open Sans Mono, monospace", + "size": 12 + }, + "showarrow": false, + "text": "100.1", + "x": 100.1, + "xanchor": "left", + "y": 10.0 + } + ], + "shapes": [ + { + "line": { + "color": "#EEEEEE", + "width": 2 + }, + "opacity": 1, + "type": "line", + "x0": 0, + "x1": 1, + "xref": "x domain", + "y0": 0, + "y1": 0, + "yref": "y" + } + ] + } +} \ No newline at end of file diff --git a/test/test_polars_accessor.py b/test/test_polars_accessor.py new file mode 100644 index 000000000..d8d290fc0 --- /dev/null +++ b/test/test_polars_accessor.py @@ -0,0 +1,65 @@ +import pytest +import pandas as pd + +# Skip these tests if the user doesn't have Polars or PyArrow installed +pl = pytest.importorskip("polars") +pytest.importorskip("pyarrow") +@pytest.fixture(autouse=True) +def load_backend(): + """ + Safely override the global backend so the syrupy snapshot fixture + in conftest.py knows which extension to load, then reset it + to avoid leaking state to other tests. + """ + original_backend = pd.options.plotting.backend + pd.options.plotting.backend = "ms_plotly" + + yield + + # Teardown: restore the original state + pd.options.plotting.backend = original_backend + +def test_polars_ms_plot_accessor(): + """ + Test that the ms_plot namespace is properly registered for Polars DataFrames + and successfully delegates to the underlying pandas plotting backend. + """ + df_pl = pl.DataFrame({ + "m/z": [100.1, 100.2, 105.0, 105.1], + "intensity": [10.0, 50.0, 100.0, 90.0] + }) + + fig = df_pl.ms_plot( + kind="spectrum", + x="m/z", + y="intensity", + backend="ms_plotly", + show_plot=False + ) + + assert fig is not None + assert type(fig).__name__ == "Figure" + +def test_polars_ms_plot_snapshot(snapshot): + """ + Test that Polars ms_plot() generates a stable Plotly figure + using the syrupy snapshot testing framework. + """ + data = { + "m/z": [100.1, 100.2, 105.0, 105.1], + "intensity": [10.0, 50.0, 100.0, 90.0] + } + + df_pl = pl.DataFrame(data) + + # Generate the figure using the Polars accessor + out = df_pl.ms_plot( + kind="spectrum", + x="m/z", + y="intensity", + backend="ms_plotly", # Explicitly set backend to avoid global state leaks + show_plot=False + ) + + # Let syrupy handle the serialization and comparison + assert snapshot == out \ No newline at end of file