Skip to content

Commit 3ba7a86

Browse files
committed
feat: new holoviz plotting
1 parent e9fa482 commit 3ba7a86

259 files changed

Lines changed: 1732 additions & 420 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{{ fullname | escape | underline}}
2+
3+
.. currentmodule:: {{ module }}
4+
5+
.. note::
6+
Both backends are accessible as ``{{ fullname }}``.
7+
The active backend is chosen by :attr:`scanpy.settings.preset`:
8+
the default is the legacy matplotlib backend;
9+
set it to :attr:`scanpy.Preset.ScanpyV2Preview` to use the HoloViews backend.
10+
11+
.. tab-set::
12+
13+
.. tab-item:: New (HoloViews)
14+
:sync: scanpy-preset-v2
15+
16+
.. autofunction:: scanpy.plotting._v2.{{ objname }}
17+
:no-index:
18+
19+
.. tab-item:: Legacy (matplotlib)
20+
:sync: scanpy-preset-legacy
21+
22+
.. autofunction:: {{ objname }}

docs/api/plotting.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,13 @@ See the {ref}`settings` section for all important plotting configurations.
2121

2222
## Generic
2323

24+
Functions with both backends:
25+
2426
```{eval-rst}
2527
.. autosummary::
2628
:nosignatures:
2729
:toctree: ../generated/
30+
:template: function-dual
2831
2932
pl.scatter
3033
pl.heatmap
@@ -33,8 +36,18 @@ See the {ref}`settings` section for all important plotting configurations.
3336
pl.violin
3437
pl.stacked_violin
3538
pl.matrixplot
36-
pl.clustermap
3739
pl.ranking
40+
41+
```
42+
43+
Legacy (matplotlib) only:
44+
45+
```{eval-rst}
46+
.. autosummary::
47+
:nosignatures:
48+
:toctree: ../generated/
49+
50+
pl.clustermap
3851
pl.dendrogram
3952
4053
```
@@ -62,6 +75,7 @@ Methods for visualizing quality control and results of preprocessing functions.
6275
.. autosummary::
6376
:nosignatures:
6477
:toctree: generated/
78+
:template: function-dual
6579
6680
pl.highest_expr_genes
6781
pl.highly_variable_genes
@@ -92,17 +106,18 @@ a method with the same name in `pl`.
92106

93107
### Embeddings
94108

109+
Functions with both backends:
110+
95111
```{eval-rst}
96112
.. autosummary::
97113
:nosignatures:
98114
:toctree: generated/
115+
:template: function-dual
99116
100-
pl.tsne
101117
pl.umap
118+
pl.tsne
102119
pl.diffmap
103120
pl.draw_graph
104-
pl.spatial
105-
pl.embedding
106121
```
107122

108123
Compute densities on embeddings.
@@ -111,10 +126,22 @@ Compute densities on embeddings.
111126
.. autosummary::
112127
:nosignatures:
113128
:toctree: generated/
129+
:template: function-dual
114130
115131
pl.embedding_density
116132
```
117133

134+
Legacy (matplotlib) only:
135+
136+
```{eval-rst}
137+
.. autosummary::
138+
:nosignatures:
139+
:toctree: generated/
140+
141+
pl.spatial
142+
pl.embedding
143+
```
144+
118145
### Branching trajectories and pseudotime, clustering
119146

120147
Visualize clusters using one of the embedding methods passing e.g. `color='leiden'`.

docs/conf.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,8 @@
152152
decoupler=("https://decoupler.readthedocs.io/en/stable/", None),
153153
fast_array_utils=("https://fast-array-utils.scverse.org/en/stable/", None),
154154
h5py=("https://docs.h5py.org/en/stable/", None),
155+
holoviews=("https://holoviews.org/", None),
156+
hv_anndata=("https://hv-anndata.readthedocs.io/en/latest/", None),
155157
zarr=("https://zarr.readthedocs.io/en/stable/", None),
156158
ipython=("https://ipython.readthedocs.io/en/stable/", None),
157159
igraph=("https://python.igraph.org/en/stable/api/", None),
@@ -265,9 +267,6 @@ def setup(app: Sphinx) -> None:
265267
qualname_overrides = {
266268
"pathlib._local.Path": "pathlib.Path",
267269
"sklearn.neighbors._dist_metrics.DistanceMetric": "sklearn.metrics.DistanceMetric",
268-
"scanpy.plotting._matrixplot.MatrixPlot": "scanpy.pl.MatrixPlot",
269-
"scanpy.plotting._dotplot.DotPlot": "scanpy.pl.DotPlot",
270-
"scanpy.plotting._stacked_violin.StackedViolin": "scanpy.pl.StackedViolin",
271270
"pandas.core.series.Series": "pandas.Series",
272271
# https://github.com/pandas-dev/pandas/issues/63810
273272
"pandas.api.typing.aliases.AnyArrayLike": ("doc", "pandas:reference/aliases"),
@@ -283,15 +282,14 @@ def setup(app: Sphinx) -> None:
283282
("py:class", "numpy._typing._dtype_like._SupportsDType"),
284283
("py:class", "numpy._typing._dtype_like._DTypeDict"),
285284
# Will probably be documented
286-
("py:class", "scanpy._settings.Verbosity"),
287285
("py:class", "scanpy.neighbors.OnFlySymMatrix"),
288-
("py:class", "scanpy.plotting._baseplot_class.BasePlot"),
286+
("py:class", "scanpy.pl.BasePlot"),
289287
# Currently undocumented
290288
# https://github.com/mwaskom/seaborn/issues/1810
291289
("py:class", "seaborn.matrix.ClusterGrid"),
292290
("py:class", "samalg.SAM"),
293291
# Won’t be documented
294-
("py:class", "scanpy.plotting._utils._AxesSubplot"),
292+
("py:class", "scanpy.pl._AxesSubplot"),
295293
("py:class", "scanpy._utils.Empty"),
296294
("py:class", "numpy.random.mtrand.RandomState"),
297295
("py:class", "scanpy.neighbors._types.KnnTransformerLike"),

docs/release-notes/4188.feat.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add a new [Holoviews](https://holoviews.org)-based plotting backend for {mod}`scanpy.pl`, used when {attr}`scanpy.settings.preset` is set to {attr}`~scanpy.Preset.ScanpyV2Preview` {smaller}`P Angerer`

hatch.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ dependency-groups = [ "dev" ]
44

55
[envs.docs]
66
dependency-groups = [ "docs" ]
7-
extra-dependencies = [ "pandas>=3" ]
7+
# TODO: remove git again after anndata 0.13.0rc4
8+
extra-dependencies = [ "pandas>=3", "anndata @ git+https://github.com/scverse/anndata.git" ]
89
scripts.build = "sphinx-build -M html docs docs/_build -W {args}"
910
scripts.open = "python3 -m webbrowser -t docs/_build/html/index.html"
1011
scripts.clean = "git clean -fdX -- {args:docs}"

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ scanorama = [ "scanorama" ]
9898
scrublet = [ "scikit-image>=0.25" ]
9999
# highly_variable_genes method 'seurat_v3'
100100
skmisc = [ "scikit-misc>=0.5.1" ]
101-
scanpy2 = [ "anndata>=0.13rc3", "igraph>=0.10.8", "scikit-misc>=0.5.1" ]
101+
scanpy2 = [ "anndata>=0.13rc3", "hv-anndata>=0.0.3a4", "igraph>=0.10.8", "scikit-misc>=0.5.1" ]
102102

103103
[dependency-groups]
104104
dev = [
@@ -131,7 +131,7 @@ docs = [
131131
"sphinx-book-theme>=1.1",
132132
"sphinx-copybutton",
133133
"sphinx-design",
134-
"sphinx-exec-jupyter>=0.2.1",
134+
"sphinx-exec-jupyter[holoviews]>=0.2.1",
135135
"sphinx-issues>=5.0.1",
136136
"sphinxcontrib-bibtex",
137137
"sphinxcontrib-katex",
@@ -291,7 +291,7 @@ markers = [
291291
"gpu: tests that use a GPU (unused, required by anndata.tests.helpers)",
292292
"array_api: tests that use array_api (unused, required by anndata.tests.helpers)",
293293
]
294-
norecursedirs = [ "tests/_images" ]
294+
norecursedirs = [ "tests/_images", "src/scanpy/plotting/_v2" ]
295295
strict = true
296296
testpaths = [ "./tests", "./ci", "scanpy" ]
297297

src/scanpy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@
8181
]
8282

8383

84-
set_figure_params = settings._set_figure_params
84+
from .plotting.legacy.mpl_settings import set_figure_params
8585

8686
annotate_doc_types(sys.modules[__name__], "scanpy")
8787

src/scanpy/_settings/__init__.py

Lines changed: 3 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,12 @@ def write(self, data: T, /) -> int: ...
3333

3434

3535
if TYPE_CHECKING:
36-
from collections.abc import Iterable
3736
from typing import Self, TextIO
3837

3938
from pydantic import ValidationInfo
4039

4140
from .verbosity import _VerbosityName
4241

43-
# Collected from the print_* functions in matplotlib.backends
44-
type _Format = (
45-
Literal["png", "jpg", "tif", "tiff"] # noqa: PYI030
46-
| Literal["pdf", "ps", "eps", "svg", "svgz", "pgf"]
47-
| Literal["raw", "rgba"]
48-
)
49-
5042

5143
__all__ = ["AnnDataFileFormat", "Default", "Preset", "Verbosity"]
5244

@@ -156,12 +148,6 @@ def model_post_init(self, context: object) -> None:
156148
})
157149
"""Categories that are omitted in plotting etc."""
158150

159-
_frameon: Annotated[bool, PrivateAttr(default=True)]
160-
"""See set_figure_params."""
161-
_vector_friendly: Annotated[bool, PrivateAttr(default=False)]
162-
"""Set to true if you want to include pngs in svgs and pdfs."""
163-
_low_resolution_warning: Annotated[bool, PrivateAttr(default=True)]
164-
"""Print warning when saving a figure with low resolution."""
165151
_start: Annotated[float, PrivateAttr(default_factory=time)]
166152
"""Time when the settings module is first imported."""
167153
_previous_time: Annotated[float, PrivateAttr(default_factory=time)]
@@ -218,89 +204,9 @@ def _logging_side_effects(self, info: ValidationInfo) -> Self:
218204

219205
@deprecated(Deprecation("1.11.3", "Use :func:`scanpy.set_figure_params` instead"))
220206
def set_figure_params(self, *args, **kwargs) -> None:
221-
self._set_figure_params(*args, **kwargs)
222-
223-
def _set_figure_params( # noqa: PLR0913
224-
self,
225-
*,
226-
scanpy: bool = True,
227-
dpi: int = 80,
228-
dpi_save: int = 150,
229-
frameon: bool = True,
230-
vector_friendly: bool = True,
231-
fontsize: int = 14,
232-
figsize: int | None = None,
233-
color_map: str | None = None,
234-
format: _Format = "pdf",
235-
facecolor: str | None = None,
236-
transparent: bool = False,
237-
ipython_format: str | Iterable[str] = "retina",
238-
) -> None:
239-
"""Set resolution/size, styling and format of figures.
240-
241-
Parameters
242-
----------
243-
scanpy
244-
Init default values for :obj:`matplotlib.rcParams` suited for Scanpy.
245-
dpi
246-
Resolution of rendered figures – this influences the size of figures in notebooks.
247-
dpi_save
248-
Resolution of saved figures.
249-
This should typically be higher to achieve publication quality.
250-
frameon
251-
Add frames and axes labels to scatter plots.
252-
vector_friendly
253-
Plot scatter plots using `png` backend even when exporting as `pdf` or `svg`.
254-
fontsize
255-
Set the fontsize for several `rcParams` entries. Ignored if `scanpy=False`.
256-
figsize
257-
Set `rcParams['figure.figsize']`.
258-
color_map
259-
Convenience method for setting the default color map. Ignored if `scanpy=False`.
260-
format
261-
This sets the default format for saving figures: `file_format_figs`.
262-
facecolor
263-
Sets backgrounds via `rcParams['figure.facecolor'] = facecolor` and
264-
`rcParams['axes.facecolor'] = facecolor`.
265-
transparent
266-
Save figures with transparent background.
267-
Sets `rcParams['savefig.transparent']`.
268-
ipython_format
269-
Only concerns the notebook/IPython environment; see
270-
`matplotlib_inline.backend_inline.set_matplotlib_formats
271-
<https://github.com/ipython/matplotlib-inline/blob/b93777db35267acefe6e37d14214360362d2e8b2/matplotlib_inline/backend_inline.py#L280-L281>`_
272-
for details.
273-
274-
"""
275-
if _is_run_from_ipython():
276-
# No docs yet: https://github.com/ipython/matplotlib-inline/issues/12
277-
from matplotlib_inline.backend_inline import set_matplotlib_formats
278-
279-
if isinstance(ipython_format, str):
280-
ipython_format = [ipython_format]
281-
282-
set_matplotlib_formats(*ipython_format)
283-
284-
from matplotlib import rcParams
285-
286-
self._vector_friendly = vector_friendly
287-
self.file_format_figs = format
288-
if dpi is not None:
289-
rcParams["figure.dpi"] = dpi
290-
if dpi_save is not None:
291-
rcParams["savefig.dpi"] = dpi_save
292-
if transparent is not None:
293-
rcParams["savefig.transparent"] = transparent
294-
if facecolor is not None:
295-
rcParams["figure.facecolor"] = facecolor
296-
rcParams["axes.facecolor"] = facecolor
297-
if scanpy:
298-
from ..plotting._rcmod import set_rcParams_scanpy
299-
300-
set_rcParams_scanpy(fontsize=fontsize, color_map=color_map)
301-
if figsize is not None:
302-
rcParams["figure.figsize"] = figsize
303-
self._frameon = frameon
207+
from ..plotting.legacy.mpl_settings import set_figure_params
208+
209+
set_figure_params(*args, **kwargs)
304210

305211
def __str__(self) -> str:
306212
return "\n".join(

src/scanpy/datasets/_datasets.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,14 @@ def pbmc68k_reduced() -> AnnData:
377377
layers: None
378378
379379
"""
380-
return read_h5ad(HERE / "10x_pbmc68k_reduced.h5ad")
380+
from scanpy._settings import Preset, settings
381+
382+
adata = read_h5ad(HERE / "10x_pbmc68k_reduced.h5ad")
383+
if settings.preset is Preset.ScanpyV2Preview:
384+
adata.layers["counts"] = adata.raw.X
385+
del adata.raw
386+
adata.obsm = {k.removeprefix("X_"): v for k, v in adata.obsm.items()}
387+
return adata
381388

382389

383390
@_doctest_skipif_old_anndata

src/scanpy/external/pl.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,14 @@
1111

1212
from .._utils import _doc_params
1313
from .._utils._doctests import doctest_needs
14-
from ..plotting import _utils, embedding
15-
from ..plotting._docs import (
14+
from ..plotting.legacy import _utils, embedding
15+
from ..plotting.legacy._docs import (
1616
doc_adata_color_etc,
1717
doc_edges_arrows,
1818
doc_scatter_embedding,
1919
doc_show_save_ax,
2020
)
21-
from ..plotting._tools.scatterplots import _wraps_plot_scatter
21+
from ..plotting.legacy._tools.scatterplots import _wraps_plot_scatter
2222
from .tl._wishbone import _anndata_to_wishbone
2323

2424
if TYPE_CHECKING:
@@ -347,6 +347,6 @@ def wishbone_marker_trajectory( # noqa: PLR0913
347347

348348
@deprecated(Deprecation("1.10.0", "Import from sc.pl instead."))
349349
def scrublet_score_distribution(*args, **kwargs): # pragma: no cover
350-
from ..plotting._scrublet import scrublet_score_distribution
350+
from ..plotting.legacy._scrublet import scrublet_score_distribution
351351

352352
return scrublet_score_distribution(*args, **kwargs)

0 commit comments

Comments
 (0)