cellestial 0.49.0__tar.gz → 0.49.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {cellestial-0.49.0 → cellestial-0.49.2}/PKG-INFO +17 -7
- {cellestial-0.49.0 → cellestial-0.49.2}/README.md +5 -5
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/__init__.py +11 -1
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/arrow.py +129 -75
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/dimensional.py +2 -2
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/distribution.py +3 -209
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/subdimensional.py +8 -8
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/subdimensionals.py +10 -10
- cellestial-0.49.2/cellestial/single/core/utilities.py +221 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/markers.py +2 -145
- cellestial-0.49.2/cellestial/single/differential/utilities.py +309 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/volcano.py +1 -152
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/dotplot.py +2 -2
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/heatmap.py +25 -148
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/stacked_violin.py +10 -133
- cellestial-0.49.2/cellestial/single/heatmap/utilities.py +872 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/spatial.py +1 -90
- cellestial-0.49.0/cellestial/spatial/_components.py → cellestial-0.49.2/cellestial/spatial/utilities.py +95 -10
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/__init__.py +3 -0
- cellestial-0.49.2/cellestial/util/markers.py +229 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/pyproject.toml +29 -2
- cellestial-0.49.0/cellestial/single/heatmap/_key_groups.py +0 -317
- cellestial-0.49.0/cellestial/single/heatmap/_rank_genes_groups.py +0 -187
- {cellestial-0.49.0 → cellestial-0.49.2}/LICENSE +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/_version.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/datasets/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/datasets/datasets.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/build.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/frames/operations.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/_deferred.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/bracket.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/ondata_legend.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/outline.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/stream.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/layers/utilities.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/base/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/base/base.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/bar.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/basic/scatter.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/xyplot.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/common/xyplots.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/core/distributions.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/differential/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/heatmap/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/elbow.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/highest.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/single/quick/ridge.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/spatial/spatials.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/__init__.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_distributions.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_heatmaps.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/themes/_scatters.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/colors.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/dendrogram.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/errors.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/operations.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/save.py +0 -0
- {cellestial-0.49.0 → cellestial-0.49.2}/cellestial/util/utilities.py +0 -0
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cellestial
|
|
3
|
-
Version: 0.49.
|
|
4
|
-
Summary:
|
|
3
|
+
Version: 0.49.2
|
|
4
|
+
Summary: Interactive single-cell and spatial omics visualization library
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
License-File: LICENSE
|
|
7
|
+
Keywords: anndata,bioinformatics,data-visualization,lets-plot,polars,single-cell,spatial-omics
|
|
7
8
|
Author: Zaf4
|
|
8
9
|
Author-email: zafermolbio@gmail.com
|
|
9
10
|
Requires-Python: >=3.11,<4.0
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
10
14
|
Classifier: Programming Language :: Python :: 3
|
|
11
15
|
Classifier: Programming Language :: Python :: 3.11
|
|
12
16
|
Classifier: Programming Language :: Python :: 3.12
|
|
13
17
|
Classifier: Programming Language :: Python :: 3.13
|
|
14
18
|
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Scientific/Engineering :: Bio-Informatics
|
|
20
|
+
Classifier: Topic :: Scientific/Engineering :: Visualization
|
|
15
21
|
Provides-Extra: extras
|
|
16
22
|
Requires-Dist: anndata (>=0.10.8,<1.0)
|
|
17
23
|
Requires-Dist: lets-plot (>=4.9.0,<5.0.0)
|
|
@@ -23,6 +29,10 @@ Requires-Dist: scikit-image (>=0.23.1,<1.0)
|
|
|
23
29
|
Requires-Dist: scvelo (>=0.3.3,<0.4.0) ; extra == "extras"
|
|
24
30
|
Requires-Dist: spatialdata (>=0.7,<1.0)
|
|
25
31
|
Requires-Dist: tqdm (>=4,<5)
|
|
32
|
+
Project-URL: Documentation, https://cellestial.datavil.org
|
|
33
|
+
Project-URL: Homepage, https://cellestial.datavil.org
|
|
34
|
+
Project-URL: Issues, https://github.com/DataVil/Cellestial/issues
|
|
35
|
+
Project-URL: Repository, https://github.com/DataVil/Cellestial
|
|
26
36
|
Description-Content-Type: text/markdown
|
|
27
37
|
|
|
28
38
|
# Cellestial
|
|
@@ -250,14 +260,14 @@ Cellestial mirrors most of `scanpy.pl` with a few naming shifts:
|
|
|
250
260
|
- `groupby` becomes `group_by`, `var_names` becomes `keys`.
|
|
251
261
|
- Saving is a separate `cl.save(plot, "umap.png")` call.
|
|
252
262
|
|
|
253
|
-
See the [migration guide](
|
|
263
|
+
See the [migration guide](https://cellestial.datavil.org/migrating_from_scanpy.html) for a side-by-side mapping.
|
|
254
264
|
|
|
255
265
|
## Documentation
|
|
256
266
|
|
|
257
|
-
- [Quickstart](
|
|
258
|
-
- [Features](
|
|
259
|
-
- [API reference](
|
|
260
|
-
- [Migrating from Scanpy](
|
|
267
|
+
- [Quickstart](https://cellestial.datavil.org/quickstart.html) — guided tour of the API.
|
|
268
|
+
- [Features](https://cellestial.datavil.org/features.html) — tooltips, layers, grids, colors, faceting.
|
|
269
|
+
- [API reference](https://cellestial.datavil.org/API.html) — every function.
|
|
270
|
+
- [Migrating from Scanpy](https://cellestial.datavil.org/migrating_from_scanpy.html).
|
|
261
271
|
|
|
262
272
|
## License
|
|
263
273
|
|
|
@@ -223,14 +223,14 @@ Cellestial mirrors most of `scanpy.pl` with a few naming shifts:
|
|
|
223
223
|
- `groupby` becomes `group_by`, `var_names` becomes `keys`.
|
|
224
224
|
- Saving is a separate `cl.save(plot, "umap.png")` call.
|
|
225
225
|
|
|
226
|
-
See the [migration guide](
|
|
226
|
+
See the [migration guide](https://cellestial.datavil.org/migrating_from_scanpy.html) for a side-by-side mapping.
|
|
227
227
|
|
|
228
228
|
## Documentation
|
|
229
229
|
|
|
230
|
-
- [Quickstart](
|
|
231
|
-
- [Features](
|
|
232
|
-
- [API reference](
|
|
233
|
-
- [Migrating from Scanpy](
|
|
230
|
+
- [Quickstart](https://cellestial.datavil.org/quickstart.html) — guided tour of the API.
|
|
231
|
+
- [Features](https://cellestial.datavil.org/features.html) — tooltips, layers, grids, colors, faceting.
|
|
232
|
+
- [API reference](https://cellestial.datavil.org/API.html) — every function.
|
|
233
|
+
- [Migrating from Scanpy](https://cellestial.datavil.org/migrating_from_scanpy.html).
|
|
234
234
|
|
|
235
235
|
## License
|
|
236
236
|
|
|
@@ -37,7 +37,15 @@ from cellestial.single import (
|
|
|
37
37
|
xyplots,
|
|
38
38
|
)
|
|
39
39
|
from cellestial.spatial import spatial, spatials
|
|
40
|
-
from cellestial.util import
|
|
40
|
+
from cellestial.util import (
|
|
41
|
+
get_figure,
|
|
42
|
+
get_figures,
|
|
43
|
+
get_mapping,
|
|
44
|
+
marker_genes,
|
|
45
|
+
marker_genes_dict,
|
|
46
|
+
retrieve,
|
|
47
|
+
save,
|
|
48
|
+
)
|
|
41
49
|
from cellestial.util.colors import (
|
|
42
50
|
BLUE,
|
|
43
51
|
CHERRY,
|
|
@@ -84,6 +92,8 @@ __all__ = [
|
|
|
84
92
|
"get_mapping",
|
|
85
93
|
"heatmap",
|
|
86
94
|
"highest_expressed_genes",
|
|
95
|
+
"marker_genes",
|
|
96
|
+
"marker_genes_dict",
|
|
87
97
|
"markers",
|
|
88
98
|
"matrixplot",
|
|
89
99
|
"ondata_legend",
|
|
@@ -2,11 +2,14 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Literal
|
|
4
4
|
|
|
5
|
+
import polars as pl
|
|
5
6
|
from lets_plot import (
|
|
7
|
+
aes,
|
|
6
8
|
arrow,
|
|
7
9
|
element_blank,
|
|
8
10
|
element_text,
|
|
9
11
|
geom_blank,
|
|
12
|
+
geom_path,
|
|
10
13
|
geom_segment,
|
|
11
14
|
theme,
|
|
12
15
|
)
|
|
@@ -19,6 +22,111 @@ if TYPE_CHECKING:
|
|
|
19
22
|
from lets_plot.plot.core import FeatureSpecArray, PlotSpec
|
|
20
23
|
from polars import DataFrame
|
|
21
24
|
|
|
25
|
+
# fraction of each axis arm drawn as the arrowhead-bearing tip segment;
|
|
26
|
+
# lets_plot caps the arrowhead to ~half the segment length, so the tip must be
|
|
27
|
+
# long enough to render a full-size head
|
|
28
|
+
_TIP_FRACTION = 0.3
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _axis_arrow_layers(
|
|
32
|
+
frame: DataFrame,
|
|
33
|
+
*,
|
|
34
|
+
x: str,
|
|
35
|
+
y: str,
|
|
36
|
+
length: float,
|
|
37
|
+
size: float,
|
|
38
|
+
color: str,
|
|
39
|
+
angle: float,
|
|
40
|
+
arrow_kwargs: dict | None = None,
|
|
41
|
+
) -> FeatureSpecArray:
|
|
42
|
+
"""
|
|
43
|
+
Build the arrow-axis as an L-shaped path plus two arrowhead tip segments.
|
|
44
|
+
|
|
45
|
+
The semi-square L is one continuous `geom_path` that stops short of each
|
|
46
|
+
open end so the corner joins cleanly with no overlap. Each tip is completed
|
|
47
|
+
by a `geom_segment` carrying the arrowhead, meeting the path end-to-end.
|
|
48
|
+
|
|
49
|
+
Parameters
|
|
50
|
+
----------
|
|
51
|
+
frame : `polars.DataFrame`
|
|
52
|
+
DataFrame holding the `x` and `y` columns.
|
|
53
|
+
x : str
|
|
54
|
+
Name of the x axis column.
|
|
55
|
+
y : str
|
|
56
|
+
Name of the y axis column.
|
|
57
|
+
length : float
|
|
58
|
+
Length of each axis arm as a fraction of its data range.
|
|
59
|
+
size : float
|
|
60
|
+
Line width of the path and tip segments.
|
|
61
|
+
color : str
|
|
62
|
+
Color of the path and tip segments.
|
|
63
|
+
angle : float
|
|
64
|
+
Angle of the arrowhead in degrees.
|
|
65
|
+
arrow_kwargs : dict | None
|
|
66
|
+
Additional parameters for the `arrow` layer.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
`FeatureSpecArray`
|
|
71
|
+
The path and tip-segment layers.
|
|
72
|
+
"""
|
|
73
|
+
arrow_kwargs = arrow_kwargs or {}
|
|
74
|
+
|
|
75
|
+
x_min = frame[x].min()
|
|
76
|
+
x_max = frame[x].max()
|
|
77
|
+
y_min = frame[y].min()
|
|
78
|
+
y_max = frame[y].max()
|
|
79
|
+
|
|
80
|
+
# find total difference between the max and min for both axis
|
|
81
|
+
x_diff = x_max - x_min
|
|
82
|
+
y_diff = y_max - y_min
|
|
83
|
+
|
|
84
|
+
# find the ends of the arrows
|
|
85
|
+
xend = x_min + length * x_diff
|
|
86
|
+
yend = y_min + length * y_diff
|
|
87
|
+
|
|
88
|
+
# adjust bottom ends of arrows
|
|
89
|
+
adjust_rate = 0.025
|
|
90
|
+
x0 = x_min - x_diff * adjust_rate
|
|
91
|
+
y0 = y_min - y_diff * adjust_rate
|
|
92
|
+
|
|
93
|
+
# tip segments carry the arrowheads; the path stops short of each tip
|
|
94
|
+
tip_x = (xend - x0) * _TIP_FRACTION
|
|
95
|
+
tip_y = (yend - y0) * _TIP_FRACTION
|
|
96
|
+
|
|
97
|
+
# semi-square L (stopping short of both tips), drawn as one connected path
|
|
98
|
+
path_frame = pl.DataFrame(
|
|
99
|
+
{"x": [xend - tip_x, x0, x0], "y": [y0, y0, yend - tip_y]}
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
layers = geom_path(
|
|
103
|
+
data=path_frame,
|
|
104
|
+
mapping=aes(x="x", y="y"),
|
|
105
|
+
color=color,
|
|
106
|
+
size=size,
|
|
107
|
+
)
|
|
108
|
+
# X axis tip
|
|
109
|
+
layers += geom_segment(
|
|
110
|
+
x=xend - tip_x,
|
|
111
|
+
y=y0,
|
|
112
|
+
xend=xend,
|
|
113
|
+
yend=y0,
|
|
114
|
+
color=color,
|
|
115
|
+
size=size,
|
|
116
|
+
arrow=arrow(angle, **arrow_kwargs),
|
|
117
|
+
)
|
|
118
|
+
# Y axis tip
|
|
119
|
+
layers += geom_segment(
|
|
120
|
+
x=x0,
|
|
121
|
+
y=yend - tip_y,
|
|
122
|
+
xend=x0,
|
|
123
|
+
yend=yend,
|
|
124
|
+
color=color,
|
|
125
|
+
size=size,
|
|
126
|
+
arrow=arrow(angle, **arrow_kwargs),
|
|
127
|
+
)
|
|
128
|
+
return layers
|
|
129
|
+
|
|
22
130
|
|
|
23
131
|
def _modify_axis(
|
|
24
132
|
frame: DataFrame,
|
|
@@ -45,13 +153,13 @@ def _modify_axis(
|
|
|
45
153
|
axis_type : {'axis', 'arrow'} | None
|
|
46
154
|
Whether to use regular axis or arrows as the axis.
|
|
47
155
|
arrow_size : float
|
|
48
|
-
|
|
156
|
+
Width of the axis lines.
|
|
49
157
|
arrow_color : str
|
|
50
158
|
Color of the arrow.
|
|
51
159
|
arrow_angle : float
|
|
52
160
|
Angle of the arrow head in degrees.
|
|
53
161
|
arrow_length : float
|
|
54
|
-
|
|
162
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
55
163
|
|
|
56
164
|
Returns
|
|
57
165
|
-------
|
|
@@ -86,44 +194,14 @@ def _modify_axis(
|
|
|
86
194
|
axis_title_x=element_text(hjust=arrow_length / 2.5), # better than 2
|
|
87
195
|
axis_title_y=element_text(hjust=arrow_length / 2.5),
|
|
88
196
|
)
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
# find total difference between the max and min for both axis
|
|
95
|
-
x_diff = x_max - x_min # ty:ignore[unsupported-operator]
|
|
96
|
-
y_diff = y_max - y_min # ty:ignore[unsupported-operator]
|
|
97
|
-
|
|
98
|
-
# find the ends of the arrows
|
|
99
|
-
# ensure the arrow length is the same for both axis
|
|
100
|
-
xend = x_min + arrow_length * x_diff
|
|
101
|
-
yend = y_min + arrow_length * y_diff
|
|
102
|
-
|
|
103
|
-
# adjust bottom ends of arrows
|
|
104
|
-
adjust_rate = 0.025
|
|
105
|
-
x0 = x_min - x_diff * adjust_rate
|
|
106
|
-
y0 = y_min - y_diff * adjust_rate
|
|
107
|
-
|
|
108
|
-
# X axis
|
|
109
|
-
new_layer += geom_segment(
|
|
110
|
-
x=x0,
|
|
111
|
-
y=y0,
|
|
112
|
-
xend=xend,
|
|
113
|
-
yend=y0,
|
|
114
|
-
color=arrow_color,
|
|
197
|
+
new_layer += _axis_arrow_layers(
|
|
198
|
+
frame,
|
|
199
|
+
x=x,
|
|
200
|
+
y=y,
|
|
201
|
+
length=arrow_length,
|
|
115
202
|
size=arrow_size,
|
|
116
|
-
arrow=arrow(arrow_angle),
|
|
117
|
-
)
|
|
118
|
-
# Y axis
|
|
119
|
-
new_layer += geom_segment(
|
|
120
|
-
x=x0,
|
|
121
|
-
y=y0,
|
|
122
|
-
xend=x0,
|
|
123
|
-
yend=yend,
|
|
124
203
|
color=arrow_color,
|
|
125
|
-
|
|
126
|
-
arrow=arrow(arrow_angle),
|
|
204
|
+
angle=arrow_angle,
|
|
127
205
|
)
|
|
128
206
|
else:
|
|
129
207
|
msg = f"expected 'axis' or 'arrow' for 'axis_type' argument, but received {axis_type}"
|
|
@@ -151,13 +229,13 @@ def arrow_axis(
|
|
|
151
229
|
regardless of which plot the resulting layer is added to. When `None`,
|
|
152
230
|
the layer is deferred and introspects the plot it is added to via `+`.
|
|
153
231
|
size : float
|
|
154
|
-
|
|
232
|
+
Width of the axis lines.
|
|
155
233
|
color : str
|
|
156
234
|
Color of the arrow.
|
|
157
235
|
angle : float
|
|
158
236
|
Angle of the arrow head in degrees.
|
|
159
237
|
length : float
|
|
160
|
-
|
|
238
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
161
239
|
**arrow_kwargs
|
|
162
240
|
Additional parameters for the `arrow` layer.
|
|
163
241
|
For more information on arrow parameters, see:
|
|
@@ -172,6 +250,10 @@ def arrow_axis(
|
|
|
172
250
|
MissingAestheticError
|
|
173
251
|
If `x` or `y` cannot be inferred from the receiving plot.
|
|
174
252
|
|
|
253
|
+
Notes
|
|
254
|
+
-----
|
|
255
|
+
Uses `geom_path` to draw the axis lines and `geom_segment` for the arrowheads.
|
|
256
|
+
|
|
175
257
|
Examples
|
|
176
258
|
--------
|
|
177
259
|
Without arrow.
|
|
@@ -242,43 +324,15 @@ def arrow_axis(
|
|
|
242
324
|
axis_title_x=element_text(hjust=length / 2.5), # better than 2
|
|
243
325
|
axis_title_y=element_text(hjust=length / 2.5),
|
|
244
326
|
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
# find total difference between the max and min for both axis
|
|
251
|
-
x_diff = x_max - x_min
|
|
252
|
-
y_diff = y_max - y_min
|
|
253
|
-
|
|
254
|
-
# find the ends of the arrows
|
|
255
|
-
xend = x_min + length * x_diff
|
|
256
|
-
yend = y_min + length * y_diff
|
|
257
|
-
|
|
258
|
-
# adjust bottom ends of arrows
|
|
259
|
-
adjust_rate = 0.025
|
|
260
|
-
x0 = x_min - x_diff * adjust_rate
|
|
261
|
-
y0 = y_min - y_diff * adjust_rate
|
|
262
|
-
|
|
263
|
-
# X axis
|
|
264
|
-
new_layer += geom_segment(
|
|
265
|
-
x=x0,
|
|
266
|
-
y=y0,
|
|
267
|
-
xend=xend,
|
|
268
|
-
yend=y0,
|
|
269
|
-
color=color,
|
|
327
|
+
new_layer += _axis_arrow_layers(
|
|
328
|
+
frame,
|
|
329
|
+
x=x,
|
|
330
|
+
y=y,
|
|
331
|
+
length=length,
|
|
270
332
|
size=size,
|
|
271
|
-
arrow=arrow(angle, **arrow_kwargs),
|
|
272
|
-
)
|
|
273
|
-
# Y axis
|
|
274
|
-
new_layer += geom_segment(
|
|
275
|
-
x=x0,
|
|
276
|
-
y=y0,
|
|
277
|
-
xend=x0,
|
|
278
|
-
yend=yend,
|
|
279
333
|
color=color,
|
|
280
|
-
|
|
281
|
-
|
|
334
|
+
angle=angle,
|
|
335
|
+
arrow_kwargs=arrow_kwargs,
|
|
282
336
|
)
|
|
283
337
|
return new_layer
|
|
284
338
|
|
|
@@ -116,9 +116,9 @@ def dimensional(
|
|
|
116
116
|
axis_type : {'axis', 'arrow'} | None
|
|
117
117
|
Whether to use regular axis or arrows as the axis.
|
|
118
118
|
arrow_length : float, default=0.25
|
|
119
|
-
|
|
119
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
120
120
|
arrow_size : float, default=1
|
|
121
|
-
|
|
121
|
+
Width of the axis lines.
|
|
122
122
|
arrow_color : str, default='#3f3f3f'
|
|
123
123
|
Color of the arrows.
|
|
124
124
|
arrow_angle : float, default=10
|
|
@@ -1,222 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections.abc import Sequence
|
|
4
3
|
from typing import TYPE_CHECKING, Any, Literal
|
|
5
4
|
|
|
6
|
-
import polars as pl
|
|
7
5
|
from anndata import AnnData
|
|
8
|
-
from lets_plot import (
|
|
9
|
-
aes,
|
|
10
|
-
geom_boxplot,
|
|
11
|
-
geom_jitter,
|
|
12
|
-
geom_point,
|
|
13
|
-
geom_sina,
|
|
14
|
-
geom_violin,
|
|
15
|
-
ggplot,
|
|
16
|
-
ggtb,
|
|
17
|
-
position_dodge,
|
|
18
|
-
position_jitterdodge,
|
|
19
|
-
)
|
|
20
6
|
from lets_plot.plot.core import FeatureSpec, PlotSpec
|
|
21
7
|
|
|
22
|
-
from cellestial.
|
|
23
|
-
from cellestial.themes import _THEME_DIST
|
|
24
|
-
from cellestial.util import (
|
|
25
|
-
_determine_axis,
|
|
26
|
-
_resolve_tooltips,
|
|
27
|
-
_select_variable_keys,
|
|
28
|
-
_validate_tooltips,
|
|
29
|
-
_warn,
|
|
30
|
-
)
|
|
31
|
-
from cellestial.util.errors import UnsupportedDataTypeError
|
|
8
|
+
from cellestial.single.core.utilities import _distribution
|
|
32
9
|
|
|
33
10
|
if TYPE_CHECKING:
|
|
34
|
-
from
|
|
35
|
-
|
|
11
|
+
from collections.abc import Sequence
|
|
36
12
|
|
|
37
|
-
|
|
38
|
-
data: AnnData,
|
|
39
|
-
key: str | Sequence[str],
|
|
40
|
-
*,
|
|
41
|
-
group_by: str | None = None,
|
|
42
|
-
mapping: FeatureSpec | None = None,
|
|
43
|
-
geom: Literal["violin", "boxplot"] = "violin",
|
|
44
|
-
axis: Literal[0, 1] | None = None,
|
|
45
|
-
color: str | None = None,
|
|
46
|
-
fill: str | None = None,
|
|
47
|
-
threshold: float | None = None,
|
|
48
|
-
add_keys: Sequence[str] | str | None = None,
|
|
49
|
-
tooltips: Literal["none"] | Sequence[str] | FeatureSpec | None = None,
|
|
50
|
-
geom_fill: str | None = None,
|
|
51
|
-
geom_color: str | None = None,
|
|
52
|
-
point_color: str = "#1f1f1f",
|
|
53
|
-
point_alpha: float = 0.7,
|
|
54
|
-
point_size: float = 0.5,
|
|
55
|
-
point_geom: Literal["jitter", "point", "sina"] = "jitter",
|
|
56
|
-
point_mapping: FeatureSpec | None = None,
|
|
57
|
-
observations_name: str = "Barcode",
|
|
58
|
-
variables_name: str = "Variable",
|
|
59
|
-
show_points: bool = True,
|
|
60
|
-
interactive: bool = False,
|
|
61
|
-
value_column: str = "value",
|
|
62
|
-
variable_column: str = "variable",
|
|
63
|
-
point_kwargs: dict[str, Any] | None = None,
|
|
64
|
-
**geom_kwargs,
|
|
65
|
-
) -> PlotSpec:
|
|
66
|
-
# Handling Data types
|
|
67
|
-
if not isinstance(data, AnnData):
|
|
68
|
-
msg = f"Unsupported data type: `{type(data)}`"
|
|
69
|
-
raise UnsupportedDataTypeError(msg)
|
|
70
|
-
|
|
71
|
-
# HANDLE: mapping
|
|
72
|
-
mapping = mapping or aes()
|
|
73
|
-
point_mapping = point_mapping or aes()
|
|
74
|
-
|
|
75
|
-
# merge standalone color/fill into mapping
|
|
76
|
-
_mapping = mapping.as_dict()
|
|
77
|
-
if color is not None and "color" not in _mapping:
|
|
78
|
-
_mapping["color"] = color
|
|
79
|
-
if fill is not None and "fill" not in _mapping:
|
|
80
|
-
_mapping["fill"] = fill
|
|
81
|
-
mapping = aes(**_mapping)
|
|
82
|
-
|
|
83
|
-
mapping_color = _mapping.get("color")
|
|
84
|
-
mapping_fill = _mapping.get("fill")
|
|
85
|
-
|
|
86
|
-
# convert to list if single string
|
|
87
|
-
if isinstance(key, str):
|
|
88
|
-
keys = [key]
|
|
89
|
-
elif isinstance(key, Sequence):
|
|
90
|
-
keys = list(key)
|
|
91
|
-
|
|
92
|
-
# handle value_column if single key is provided
|
|
93
|
-
if len(keys) == 1:
|
|
94
|
-
value_column = keys[0]
|
|
95
|
-
|
|
96
|
-
# determine separator (group_by)
|
|
97
|
-
group_by = group_by or mapping_fill or mapping_color
|
|
98
|
-
# initialize point kwargs if necessary
|
|
99
|
-
point_kwargs = {} if point_kwargs is None else dict(point_kwargs)
|
|
100
|
-
|
|
101
|
-
# determine index to unpivot
|
|
102
|
-
index = list(
|
|
103
|
-
dict.fromkeys(c for c in (group_by, mapping_fill, mapping_color) if c is not None)
|
|
104
|
-
)
|
|
105
|
-
if add_keys is not None:
|
|
106
|
-
if isinstance(add_keys, str):
|
|
107
|
-
add_keys = [add_keys]
|
|
108
|
-
index.extend(add_keys)
|
|
109
|
-
|
|
110
|
-
# DETERMINE: axis if not provided
|
|
111
|
-
axis = _determine_axis(data=data, keys=keys) if axis is None else axis
|
|
112
|
-
|
|
113
|
-
# BUILD: the DataFrame
|
|
114
|
-
variable_keys = _select_variable_keys(data=data, keys=keys)
|
|
115
|
-
variable_keys.extend(_select_variable_keys(data=data, keys=add_keys))
|
|
116
|
-
frame = build_frame(
|
|
117
|
-
data=data,
|
|
118
|
-
variable_keys=variable_keys,
|
|
119
|
-
axis=axis,
|
|
120
|
-
observations_name=observations_name,
|
|
121
|
-
variables_name=variables_name,
|
|
122
|
-
)
|
|
123
|
-
|
|
124
|
-
if group_by is not None:
|
|
125
|
-
frame = frame.filter(pl.col(group_by).is_not_null())
|
|
126
|
-
|
|
127
|
-
frame = frame.unpivot(
|
|
128
|
-
on=keys, index=index, value_name=value_column, variable_name=variable_column
|
|
129
|
-
)
|
|
130
|
-
frame = frame.drop_nulls(subset=[value_column])
|
|
131
|
-
if threshold is not None:
|
|
132
|
-
frame = frame.filter(pl.col(value_column) >= threshold)
|
|
133
|
-
if group_by is None:
|
|
134
|
-
group_by = variable_column
|
|
135
|
-
elif len(keys) > 1 and group_by != variable_column:
|
|
136
|
-
_warn(
|
|
137
|
-
f"Multiple keys provided; `group_by={group_by!r}` cannot share the "
|
|
138
|
-
f"x-axis with the variable column. Falling back to "
|
|
139
|
-
f"`group_by={variable_column!r}`. For a per-key panel, use the plural variant."
|
|
140
|
-
)
|
|
141
|
-
group_by = variable_column
|
|
142
|
-
|
|
143
|
-
# HANDLE: tooltips
|
|
144
|
-
tooltips = _resolve_tooltips(
|
|
145
|
-
tooltips,
|
|
146
|
-
data=data,
|
|
147
|
-
variable_keys=variable_keys,
|
|
148
|
-
defaults=[variable_column, value_column],
|
|
149
|
-
)
|
|
150
|
-
_validate_tooltips(tooltips, frame)
|
|
151
|
-
|
|
152
|
-
# BUILD: the plot
|
|
153
|
-
dst = ggplot(data=frame) + _THEME_DIST
|
|
154
|
-
|
|
155
|
-
# add the geom layer
|
|
156
|
-
if geom == "violin":
|
|
157
|
-
dst += geom_violin(
|
|
158
|
-
mapping=aes(
|
|
159
|
-
x=group_by,
|
|
160
|
-
y=value_column,
|
|
161
|
-
**mapping.as_dict(),
|
|
162
|
-
),
|
|
163
|
-
fill=geom_fill,
|
|
164
|
-
color=geom_color,
|
|
165
|
-
tooltips=frame.columns,
|
|
166
|
-
**geom_kwargs,
|
|
167
|
-
)
|
|
168
|
-
elif geom == "boxplot":
|
|
169
|
-
dst += geom_boxplot(
|
|
170
|
-
mapping=aes(
|
|
171
|
-
x=group_by,
|
|
172
|
-
y=value_column,
|
|
173
|
-
**mapping.as_dict(),
|
|
174
|
-
),
|
|
175
|
-
fill=geom_fill,
|
|
176
|
-
color=geom_color,
|
|
177
|
-
tooltips=frame.columns,
|
|
178
|
-
**geom_kwargs,
|
|
179
|
-
)
|
|
180
|
-
|
|
181
|
-
# handle the points (jitter,point,sina)
|
|
182
|
-
if show_points:
|
|
183
|
-
if point_geom in ["jitter", "point", "sina"]:
|
|
184
|
-
geom_functions = {
|
|
185
|
-
"jitter": geom_jitter,
|
|
186
|
-
"point": geom_point,
|
|
187
|
-
"sina": geom_sina,
|
|
188
|
-
}
|
|
189
|
-
dodge_width = geom_kwargs.get("width", 0.95)
|
|
190
|
-
positions = {
|
|
191
|
-
"jitter": position_jitterdodge(dodge_width=dodge_width),
|
|
192
|
-
"point": position_dodge(width=dodge_width),
|
|
193
|
-
"sina": None,
|
|
194
|
-
}
|
|
195
|
-
geom_function = geom_functions.get(point_geom, geom_jitter)
|
|
196
|
-
if "position" not in point_kwargs:
|
|
197
|
-
position = positions.get(point_geom, position_jitterdodge())
|
|
198
|
-
else:
|
|
199
|
-
position = point_kwargs.pop("position")
|
|
200
|
-
|
|
201
|
-
point_kwargs.update(color=point_color, alpha=point_alpha, size=point_size)
|
|
202
|
-
for key in point_mapping.as_dict():
|
|
203
|
-
point_kwargs.pop(key, None)
|
|
204
|
-
dst += geom_function(
|
|
205
|
-
data=frame,
|
|
206
|
-
mapping=aes(x=group_by, y=value_column, **point_mapping.as_dict()),
|
|
207
|
-
tooltips=tooltips,
|
|
208
|
-
position=position,
|
|
209
|
-
**point_kwargs,
|
|
210
|
-
)
|
|
211
|
-
else:
|
|
212
|
-
msg = "point_geom must be one of ['jitter','point','sina']."
|
|
213
|
-
raise ValueError(msg)
|
|
214
|
-
|
|
215
|
-
# handle interactive
|
|
216
|
-
if interactive:
|
|
217
|
-
dst += ggtb(size_zoomin=-1)
|
|
218
|
-
|
|
219
|
-
return dst
|
|
13
|
+
from lets_plot.plot.core import PlotSpec
|
|
220
14
|
|
|
221
15
|
|
|
222
16
|
def violin(
|
|
@@ -90,9 +90,9 @@ def umap(
|
|
|
90
90
|
axis_type : {'axis', 'arrow'} | None
|
|
91
91
|
Whether to use regular axis or arrows as the axis.
|
|
92
92
|
arrow_length : float, default=0.25
|
|
93
|
-
|
|
93
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
94
94
|
arrow_size : float, default=1
|
|
95
|
-
|
|
95
|
+
Width of the axis lines.
|
|
96
96
|
arrow_color : str, default='#3f3f3f'
|
|
97
97
|
Color of the arrows.
|
|
98
98
|
arrow_angle : float, default=10
|
|
@@ -261,9 +261,9 @@ def tsne(
|
|
|
261
261
|
axis_type : {'axis', 'arrow'} | None
|
|
262
262
|
Whether to use regular axis or arrows as the axis.
|
|
263
263
|
arrow_length : float, default=0.25
|
|
264
|
-
|
|
264
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
265
265
|
arrow_size : float, default=1
|
|
266
|
-
|
|
266
|
+
Width of the axis lines.
|
|
267
267
|
arrow_color : str, default='#3f3f3f'
|
|
268
268
|
Color of the arrows.
|
|
269
269
|
arrow_angle : float, default=10
|
|
@@ -432,9 +432,9 @@ def pca(
|
|
|
432
432
|
axis_type : {'axis', 'arrow'} | None
|
|
433
433
|
Whether to use regular axis or arrows as the axis.
|
|
434
434
|
arrow_length : float, default=0.25
|
|
435
|
-
|
|
435
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
436
436
|
arrow_size : float, default=1
|
|
437
|
-
|
|
437
|
+
Width of the axis lines.
|
|
438
438
|
arrow_color : str, default='#3f3f3f'
|
|
439
439
|
Color of the arrows.
|
|
440
440
|
arrow_angle : float, default=10
|
|
@@ -606,9 +606,9 @@ def expression(
|
|
|
606
606
|
axis_type : {'axis', 'arrow'} | None
|
|
607
607
|
Whether to use regular axis or arrows as the axis.
|
|
608
608
|
arrow_length : float, default=0.25
|
|
609
|
-
|
|
609
|
+
Span of each axis line as a fraction of its data range (0.25 covers 25%).
|
|
610
610
|
arrow_size : float, default=1
|
|
611
|
-
|
|
611
|
+
Width of the axis lines.
|
|
612
612
|
arrow_color : str, default='#3f3f3f'
|
|
613
613
|
Color of the arrows.
|
|
614
614
|
arrow_angle : float, default=10
|