marsilea 0.3.0rc2__py3-none-any.whl → 0.3.2__py3-none-any.whl
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.
- marsilea/__init__.py +1 -1
- marsilea/_deform.py +59 -9
- marsilea/base.py +49 -28
- marsilea/dataset.py +1 -0
- marsilea/dendrogram.py +37 -7
- marsilea/plotter/arc.py +2 -2
- marsilea/plotter/bar.py +6 -2
- marsilea/plotter/text.py +74 -8
- {marsilea-0.3.0rc2.dist-info → marsilea-0.3.2.dist-info}/METADATA +1 -1
- {marsilea-0.3.0rc2.dist-info → marsilea-0.3.2.dist-info}/RECORD +12 -12
- {marsilea-0.3.0rc2.dist-info → marsilea-0.3.2.dist-info}/WHEEL +1 -1
- {marsilea-0.3.0rc2.dist-info → marsilea-0.3.2.dist-info}/LICENSE +0 -0
marsilea/__init__.py
CHANGED
marsilea/_deform.py
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
from typing import Mapping
|
|
2
|
+
|
|
1
3
|
import numpy as np
|
|
2
4
|
|
|
3
5
|
from .dendrogram import Dendrogram, GroupDendrogram
|
|
@@ -25,9 +27,13 @@ class Deformation:
|
|
|
25
27
|
|
|
26
28
|
row_breakpoints = None
|
|
27
29
|
col_breakpoints = None
|
|
30
|
+
row_split_order = None
|
|
31
|
+
col_split_order = None
|
|
28
32
|
|
|
29
33
|
row_dendrogram = None
|
|
30
34
|
col_dendrogram = None
|
|
35
|
+
row_linkage = None # User supplied linkage
|
|
36
|
+
col_linkage = None
|
|
31
37
|
|
|
32
38
|
row_reorder_index = None
|
|
33
39
|
col_reorder_index = None
|
|
@@ -73,17 +79,20 @@ class Deformation:
|
|
|
73
79
|
self.data_col_reindex = reindex
|
|
74
80
|
self._col_clustered = False
|
|
75
81
|
|
|
76
|
-
def set_cluster(self, col=None, row=None, use_meta=True,
|
|
82
|
+
def set_cluster(self, col=None, row=None, use_meta=True,
|
|
83
|
+
linkage=None, **kwargs):
|
|
77
84
|
if col is not None:
|
|
78
85
|
self.is_col_cluster = col
|
|
79
86
|
self.col_cluster_kws = kwargs
|
|
80
87
|
self._col_clustered = False
|
|
81
88
|
self._use_col_meta = use_meta
|
|
89
|
+
self.col_linkage = linkage
|
|
82
90
|
if row is not None:
|
|
83
91
|
self.is_row_cluster = row
|
|
84
92
|
self.row_cluster_kws = kwargs
|
|
85
93
|
self._row_clustered = False
|
|
86
94
|
self._use_row_meta = use_meta
|
|
95
|
+
self.row_linkage = linkage
|
|
87
96
|
|
|
88
97
|
def get_data(self):
|
|
89
98
|
data = self.data
|
|
@@ -93,17 +102,23 @@ class Deformation:
|
|
|
93
102
|
data = data[:, self.data_col_reindex]
|
|
94
103
|
return data
|
|
95
104
|
|
|
96
|
-
def set_split_row(self, breakpoints=None):
|
|
105
|
+
def set_split_row(self, breakpoints=None, order=None):
|
|
97
106
|
if breakpoints is not None:
|
|
98
107
|
self.is_row_split = True
|
|
99
108
|
self.row_breakpoints = [0, *np.sort(np.asarray(breakpoints)),
|
|
100
109
|
self._nrow]
|
|
110
|
+
if order is None:
|
|
111
|
+
order = np.arange(len(breakpoints) + 1)
|
|
112
|
+
self.row_split_order = order
|
|
101
113
|
|
|
102
|
-
def set_split_col(self, breakpoints=None):
|
|
114
|
+
def set_split_col(self, breakpoints=None, order=None):
|
|
103
115
|
if breakpoints is not None:
|
|
104
116
|
self.is_col_split = True
|
|
105
117
|
self.col_breakpoints = [0, *np.sort(np.asarray(breakpoints)),
|
|
106
118
|
self._ncol]
|
|
119
|
+
if order is None:
|
|
120
|
+
order = np.arange(len(breakpoints) + 1)
|
|
121
|
+
self.col_split_order = order
|
|
107
122
|
|
|
108
123
|
@property
|
|
109
124
|
def row_ratios(self):
|
|
@@ -169,11 +184,24 @@ class Deformation:
|
|
|
169
184
|
return self.split_by_col(data)
|
|
170
185
|
return data
|
|
171
186
|
|
|
187
|
+
_linkage_check_msg = ("If you want to specific linkage when splitting, "
|
|
188
|
+
"it must be a dict-like object, "
|
|
189
|
+
"with keys as group names and values as linkage")
|
|
190
|
+
|
|
172
191
|
def cluster_row(self):
|
|
173
192
|
row_data = self.split_by_row(self.get_data())
|
|
174
193
|
if self.is_row_split:
|
|
175
|
-
|
|
176
|
-
|
|
194
|
+
if not (isinstance(self.row_linkage, Mapping) or (self.row_linkage is None)):
|
|
195
|
+
raise TypeError(self._linkage_check_msg)
|
|
196
|
+
dens = []
|
|
197
|
+
for chunk, k in zip(row_data, self.row_split_order):
|
|
198
|
+
linkage = None
|
|
199
|
+
if self.row_linkage is not None:
|
|
200
|
+
linkage = self.row_linkage.get(k)
|
|
201
|
+
if linkage is None:
|
|
202
|
+
raise KeyError(f"Linkage for group {k} is not specified")
|
|
203
|
+
dens.append(Dendrogram(chunk, linkage=linkage, key=k, **self.row_cluster_kws))
|
|
204
|
+
|
|
177
205
|
dg = GroupDendrogram(dens, **self.row_cluster_kws)
|
|
178
206
|
if self._use_row_meta:
|
|
179
207
|
self.row_chunk_index = dg.reorder_index
|
|
@@ -181,15 +209,23 @@ class Deformation:
|
|
|
181
209
|
self.row_chunk_index = np.arange(len(dens))
|
|
182
210
|
self.row_reorder_index = [d.reorder_index for d in dens]
|
|
183
211
|
else:
|
|
184
|
-
dg = Dendrogram(row_data, **self.row_cluster_kws)
|
|
212
|
+
dg = Dendrogram(row_data, linkage=self.row_linkage, **self.row_cluster_kws)
|
|
185
213
|
self.row_reorder_index = dg.reorder_index
|
|
186
214
|
self.row_dendrogram = dg
|
|
187
215
|
|
|
188
216
|
def cluster_col(self):
|
|
189
217
|
col_data = self.split_by_col(self.get_data())
|
|
190
218
|
if self.is_col_split:
|
|
191
|
-
|
|
192
|
-
|
|
219
|
+
if not (isinstance(self.col_linkage, Mapping) or (self.col_linkage is None)):
|
|
220
|
+
raise TypeError(self._linkage_check_msg)
|
|
221
|
+
dens = []
|
|
222
|
+
for chunk, k in zip(col_data, self.col_split_order):
|
|
223
|
+
linkage = None
|
|
224
|
+
if self.col_linkage is not None:
|
|
225
|
+
linkage = self.col_linkage.get(k)
|
|
226
|
+
if linkage is None:
|
|
227
|
+
raise KeyError(f"Linkage for group {k} is not specified")
|
|
228
|
+
dens.append(Dendrogram(chunk.T, linkage=linkage, key=k, **self.col_cluster_kws))
|
|
193
229
|
dg = GroupDendrogram(dens, **self.col_cluster_kws)
|
|
194
230
|
if self._use_col_meta:
|
|
195
231
|
self.col_chunk_index = dg.reorder_index
|
|
@@ -197,7 +233,7 @@ class Deformation:
|
|
|
197
233
|
self.col_chunk_index = np.arange(len(dens))
|
|
198
234
|
self.col_reorder_index = [d.reorder_index for d in dens]
|
|
199
235
|
else:
|
|
200
|
-
dg = Dendrogram(col_data.T, **self.col_cluster_kws)
|
|
236
|
+
dg = Dendrogram(col_data.T, linkage=self.col_linkage, **self.col_cluster_kws)
|
|
201
237
|
self.col_reorder_index = dg.reorder_index
|
|
202
238
|
self.col_dendrogram = dg
|
|
203
239
|
|
|
@@ -340,6 +376,20 @@ class Deformation:
|
|
|
340
376
|
self._run_cluster()
|
|
341
377
|
return self.col_dendrogram
|
|
342
378
|
|
|
379
|
+
def get_row_linkage(self):
|
|
380
|
+
if self.row_dendrogram is not None:
|
|
381
|
+
if self.is_row_split:
|
|
382
|
+
return {x.key: x.Z for x in self.row_dendrogram.orig_dens}
|
|
383
|
+
else:
|
|
384
|
+
return self.row_dendrogram.Z
|
|
385
|
+
|
|
386
|
+
def get_col_linkage(self):
|
|
387
|
+
if self.col_dendrogram is not None:
|
|
388
|
+
if self.is_col_split:
|
|
389
|
+
return {x.key: x.Z for x in self.col_dendrogram.orig_dens}
|
|
390
|
+
else:
|
|
391
|
+
return self.col_dendrogram.Z
|
|
392
|
+
|
|
343
393
|
@property
|
|
344
394
|
def is_split(self):
|
|
345
395
|
return self.is_row_split | self.is_col_split
|
marsilea/base.py
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import numpy as np
|
|
4
3
|
import warnings
|
|
5
4
|
from copy import deepcopy
|
|
5
|
+
from numbers import Number
|
|
6
|
+
from typing import List, Dict
|
|
7
|
+
from uuid import uuid4
|
|
8
|
+
|
|
9
|
+
import numpy as np
|
|
6
10
|
from legendkit.layout import vstack, hstack
|
|
7
11
|
from matplotlib import pyplot as plt
|
|
8
12
|
from matplotlib.artist import Artist
|
|
9
13
|
from matplotlib.colors import is_color_like
|
|
10
14
|
from matplotlib.figure import Figure
|
|
11
|
-
from numbers import Number
|
|
12
|
-
from typing import List, Dict
|
|
13
|
-
from uuid import uuid4
|
|
14
15
|
|
|
15
16
|
from ._deform import Deformation
|
|
16
17
|
from .dendrogram import Dendrogram
|
|
@@ -26,14 +27,13 @@ def reorder_index(arr, order=None):
|
|
|
26
27
|
for ix, a in enumerate(arr):
|
|
27
28
|
indices[a].append(ix)
|
|
28
29
|
|
|
30
|
+
if order is None:
|
|
31
|
+
order = sorted(uniq)
|
|
32
|
+
|
|
29
33
|
final_index = []
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
else:
|
|
34
|
-
for it in indices.values():
|
|
35
|
-
final_index += it
|
|
36
|
-
return final_index
|
|
34
|
+
for it in order:
|
|
35
|
+
final_index += indices[it]
|
|
36
|
+
return final_index, order
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def get_breakpoints(arr):
|
|
@@ -521,11 +521,11 @@ class ClusterBoard(WhiteBoard):
|
|
|
521
521
|
self._cluster_data = cluster_data
|
|
522
522
|
self._deform = Deformation(cluster_data)
|
|
523
523
|
|
|
524
|
-
def add_dendrogram(self, side, method=None, metric=None,
|
|
524
|
+
def add_dendrogram(self, side, method=None, metric=None, linkage=None,
|
|
525
525
|
add_meta=True, add_base=True, add_divider=True,
|
|
526
526
|
meta_color=None, linewidth=None, colors=None,
|
|
527
527
|
divider_style="--", meta_ratio=.2,
|
|
528
|
-
show=True, name=None, size=0.5, pad=0
|
|
528
|
+
show=True, name=None, size=0.5, pad=0., get_meta_center=None):
|
|
529
529
|
"""Run cluster and add dendrogram
|
|
530
530
|
|
|
531
531
|
.. note::
|
|
@@ -542,11 +542,17 @@ class ClusterBoard(WhiteBoard):
|
|
|
542
542
|
See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>`
|
|
543
543
|
metric : str
|
|
544
544
|
See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>`
|
|
545
|
-
|
|
545
|
+
linkage : ndarray
|
|
546
|
+
Precomputed linkage matrix.
|
|
547
|
+
See scipy's :meth:`linkage <scipy.cluster.hierarchy.linkage>` for
|
|
548
|
+
specific format.
|
|
549
|
+
add_meta : None | bool
|
|
550
|
+
By default, add_meta is set to False if the linkage is provided, otherwise True.
|
|
546
551
|
If the data is split, a meta dendrogram can be drawn for data
|
|
547
552
|
chunks. The mean value of the data chunk is used to calculate
|
|
548
553
|
linkage matrix for meta dendrogram.
|
|
549
|
-
add_base : bool
|
|
554
|
+
add_base : None | bool
|
|
555
|
+
By default, add_meta is set to False if the linkage is provided, otherwise True.
|
|
550
556
|
Draw the base dendrogram for each data chunk. You can turn this
|
|
551
557
|
off if the base dendrogram is too crowded.
|
|
552
558
|
add_divider : bool
|
|
@@ -571,6 +577,11 @@ class ClusterBoard(WhiteBoard):
|
|
|
571
577
|
The name of the dendrogram axes
|
|
572
578
|
size : float
|
|
573
579
|
pad : float
|
|
580
|
+
get_meta_center: callable
|
|
581
|
+
A function to calculate the centroid of data. It should accept a 2D numpy
|
|
582
|
+
array as input and return a 1D numpy array of the same length as the number
|
|
583
|
+
of columns in the input, representing the centroid. The default will use the
|
|
584
|
+
mean values.
|
|
574
585
|
|
|
575
586
|
Examples
|
|
576
587
|
--------
|
|
@@ -638,46 +649,50 @@ class ClusterBoard(WhiteBoard):
|
|
|
638
649
|
den_options['pos'] = "row"
|
|
639
650
|
self._row_den.append(den_options)
|
|
640
651
|
deform.set_cluster(row=True, method=method, metric=metric,
|
|
641
|
-
use_meta=add_meta
|
|
652
|
+
linkage=linkage, use_meta=add_meta,
|
|
653
|
+
get_meta_center=get_meta_center)
|
|
642
654
|
else:
|
|
643
655
|
den_options['pos'] = "col"
|
|
644
656
|
self._col_den.append(den_options)
|
|
645
657
|
deform.set_cluster(col=True, method=method, metric=metric,
|
|
646
|
-
use_meta=add_meta
|
|
647
|
-
|
|
658
|
+
linkage=linkage, use_meta=add_meta,
|
|
659
|
+
get_meta_center=get_meta_center)
|
|
660
|
+
|
|
648
661
|
def hsplit(self, cut=None, labels=None, order=None, spacing=0.01):
|
|
649
662
|
if self._split_row:
|
|
650
663
|
raise SplitTwice(axis="horizontally")
|
|
651
664
|
self._split_row = True
|
|
652
665
|
|
|
653
|
-
|
|
666
|
+
deform = self.get_deform()
|
|
667
|
+
deform.hspace = spacing
|
|
654
668
|
if cut is not None:
|
|
655
|
-
|
|
669
|
+
deform.set_split_row(breakpoints=cut)
|
|
656
670
|
else:
|
|
657
671
|
labels = np.asarray(labels)
|
|
658
672
|
|
|
659
|
-
reindex = reorder_index(labels, order=order)
|
|
660
|
-
|
|
673
|
+
reindex, order = reorder_index(labels, order=order)
|
|
674
|
+
deform.set_data_row_reindex(reindex)
|
|
661
675
|
|
|
662
676
|
breakpoints = get_breakpoints(labels[reindex])
|
|
663
|
-
|
|
677
|
+
deform.set_split_row(breakpoints=breakpoints, order=order)
|
|
664
678
|
|
|
665
679
|
def vsplit(self, cut=None, labels=None, order=None, spacing=0.01):
|
|
666
680
|
if self._split_col:
|
|
667
681
|
raise SplitTwice(axis="vertically")
|
|
668
682
|
self._split_col = True
|
|
669
683
|
|
|
670
|
-
|
|
684
|
+
deform = self.get_deform()
|
|
685
|
+
deform.wspace = spacing
|
|
671
686
|
if cut is not None:
|
|
672
|
-
|
|
687
|
+
deform.set_split_col(breakpoints=cut)
|
|
673
688
|
else:
|
|
674
689
|
labels = np.asarray(labels)
|
|
675
690
|
|
|
676
|
-
reindex = reorder_index(labels, order=order)
|
|
677
|
-
|
|
691
|
+
reindex, order = reorder_index(labels, order=order)
|
|
692
|
+
deform.set_data_col_reindex(reindex)
|
|
678
693
|
|
|
679
694
|
breakpoints = get_breakpoints(labels[reindex])
|
|
680
|
-
|
|
695
|
+
deform.set_split_col(breakpoints=breakpoints, order=order)
|
|
681
696
|
|
|
682
697
|
def _setup_axes(self):
|
|
683
698
|
deform = self.get_deform()
|
|
@@ -768,6 +783,12 @@ class ClusterBoard(WhiteBoard):
|
|
|
768
783
|
def get_deform(self):
|
|
769
784
|
return self._deform
|
|
770
785
|
|
|
786
|
+
def get_row_linkage(self):
|
|
787
|
+
return self._deform.get_row_linkage()
|
|
788
|
+
|
|
789
|
+
def get_col_linkage(self):
|
|
790
|
+
return self._deform.get_col_linkage()
|
|
791
|
+
|
|
771
792
|
@property
|
|
772
793
|
def row_cluster(self):
|
|
773
794
|
return len(self._row_den) > 0
|
marsilea/dataset.py
CHANGED
marsilea/dendrogram.py
CHANGED
|
@@ -3,7 +3,7 @@ from itertools import cycle
|
|
|
3
3
|
from matplotlib.collections import LineCollection
|
|
4
4
|
from matplotlib.colors import is_color_like
|
|
5
5
|
from matplotlib.lines import Line2D
|
|
6
|
-
from scipy.cluster.hierarchy import linkage, dendrogram
|
|
6
|
+
from scipy.cluster.hierarchy import linkage as scipy_linkage, dendrogram
|
|
7
7
|
from typing import List, Sequence
|
|
8
8
|
|
|
9
9
|
|
|
@@ -15,7 +15,12 @@ class _DendrogramBase:
|
|
|
15
15
|
data,
|
|
16
16
|
method=None,
|
|
17
17
|
metric=None,
|
|
18
|
+
linkage=None,
|
|
19
|
+
get_meta_center=None,
|
|
20
|
+
key=None,
|
|
18
21
|
):
|
|
22
|
+
self.key = key
|
|
23
|
+
self.data = data
|
|
19
24
|
if method is None:
|
|
20
25
|
method = "single"
|
|
21
26
|
if metric is None:
|
|
@@ -29,7 +34,10 @@ class _DendrogramBase:
|
|
|
29
34
|
self._reorder_index = np.array([0])
|
|
30
35
|
self.is_singleton = True
|
|
31
36
|
else:
|
|
32
|
-
|
|
37
|
+
if linkage is not None:
|
|
38
|
+
self.Z = linkage
|
|
39
|
+
else:
|
|
40
|
+
self.Z = scipy_linkage(data, method=method, metric=metric)
|
|
33
41
|
self._plot_data = dendrogram(self.Z, no_plot=True)
|
|
34
42
|
|
|
35
43
|
self.x_coords = np.asarray(self._plot_data['icoord']) / 5
|
|
@@ -59,8 +67,22 @@ class _DendrogramBase:
|
|
|
59
67
|
self.ylim = np.array([0, self.max_dependent_coord * 1.05])
|
|
60
68
|
self._render_xlim = self.xlim
|
|
61
69
|
self._render_ylim = self.ylim
|
|
70
|
+
|
|
62
71
|
# Should be lazy eval
|
|
63
|
-
|
|
72
|
+
# TODO: Allow center to be calculated differently
|
|
73
|
+
if get_meta_center is None:
|
|
74
|
+
self._center = np.mean(data, axis=0)
|
|
75
|
+
elif callable(get_meta_center):
|
|
76
|
+
# Ensure the centroid function returns a numpy array of correct shape
|
|
77
|
+
centroid = get_meta_center(data)
|
|
78
|
+
if isinstance(centroid, np.ndarray) and centroid.shape == data.shape[1:]:
|
|
79
|
+
self._center = centroid
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(
|
|
82
|
+
"The get_meta_center must return a numpy array with shape "
|
|
83
|
+
"matching the number of features in the data.")
|
|
84
|
+
else:
|
|
85
|
+
raise TypeError("The get_meta_center must be a callable function or None.")
|
|
64
86
|
|
|
65
87
|
@property
|
|
66
88
|
def xrange(self):
|
|
@@ -143,9 +165,13 @@ class Dendrogram(_DendrogramBase):
|
|
|
143
165
|
def __init__(self,
|
|
144
166
|
data: np.ndarray,
|
|
145
167
|
method=None,
|
|
146
|
-
metric=None
|
|
168
|
+
metric=None,
|
|
169
|
+
linkage=None,
|
|
170
|
+
get_meta_center=None,
|
|
171
|
+
key=None,
|
|
147
172
|
):
|
|
148
|
-
super().__init__(data, method=method, metric=metric
|
|
173
|
+
super().__init__(data, method=method, metric=metric, key=key,
|
|
174
|
+
linkage=linkage, get_meta_center=get_meta_center)
|
|
149
175
|
|
|
150
176
|
# here we left an empty **kwargs to align api with GroupDendrogram
|
|
151
177
|
def draw(self, ax, orient="top",
|
|
@@ -223,10 +249,14 @@ class GroupDendrogram(_DendrogramBase):
|
|
|
223
249
|
def __init__(self,
|
|
224
250
|
dens: List[Dendrogram],
|
|
225
251
|
method=None,
|
|
226
|
-
metric=None
|
|
252
|
+
metric=None,
|
|
253
|
+
get_meta_center=None,
|
|
254
|
+
key=None,
|
|
255
|
+
**kwargs,
|
|
227
256
|
):
|
|
228
257
|
data = np.vstack([d.center for d in dens])
|
|
229
|
-
super().__init__(data, method=method, metric=metric
|
|
258
|
+
super().__init__(data, method=method, metric=metric,
|
|
259
|
+
get_meta_center=get_meta_center, key=key)
|
|
230
260
|
self.orig_dens = np.asarray(dens)
|
|
231
261
|
self.dens = np.asarray(dens)[self.reorder_index]
|
|
232
262
|
self.n = len(self.dens)
|
marsilea/plotter/arc.py
CHANGED
|
@@ -210,8 +210,8 @@ class Arc(StatsBase):
|
|
|
210
210
|
xy = (arc_mid, 0)
|
|
211
211
|
angle = 0
|
|
212
212
|
sizes.append(arc_width)
|
|
213
|
-
arc = mArc(xy, arc_width, arc_width * 2,
|
|
214
|
-
**options)
|
|
213
|
+
arc = mArc(xy, arc_width, arc_width * 2,
|
|
214
|
+
angle=angle, **options)
|
|
215
215
|
ax.add_patch(arc)
|
|
216
216
|
|
|
217
217
|
lim = np.max(sizes)
|
marsilea/plotter/bar.py
CHANGED
|
@@ -387,6 +387,10 @@ class StackBar(_BarBase):
|
|
|
387
387
|
if self.side == "left":
|
|
388
388
|
ax.invert_xaxis()
|
|
389
389
|
|
|
390
|
+
# Hanlde data
|
|
391
|
+
if orient == "h":
|
|
392
|
+
data = data[:, ::-1]
|
|
393
|
+
|
|
390
394
|
locs = np.arange(0, lim) + 0.5
|
|
391
395
|
bottom = np.zeros(lim)
|
|
392
396
|
|
|
@@ -395,8 +399,8 @@ class StackBar(_BarBase):
|
|
|
395
399
|
labels = self.labels[::-1]
|
|
396
400
|
else:
|
|
397
401
|
labels = [None for _ in range(len(data))]
|
|
398
|
-
colors = self.bar_colors[:len(data)]
|
|
399
|
-
for ix, row in enumerate(data
|
|
402
|
+
colors = self.bar_colors[:len(data)]
|
|
403
|
+
for ix, row in enumerate(data):
|
|
400
404
|
bars = bar(locs, row, self.width, bottom,
|
|
401
405
|
fc=colors[ix],
|
|
402
406
|
label=labels[ix], **self.options)
|
marsilea/plotter/text.py
CHANGED
|
@@ -668,7 +668,8 @@ class Labels(_LabelBase):
|
|
|
668
668
|
coords = self.get_axes_coords(data)
|
|
669
669
|
params = self.get_text_params()
|
|
670
670
|
if self.texts_size is not None:
|
|
671
|
-
|
|
671
|
+
padding_px = self.padding / 72
|
|
672
|
+
offset_ratio = padding_px / (self.texts_size + padding_px)
|
|
672
673
|
else:
|
|
673
674
|
offset_ratio = 0
|
|
674
675
|
|
|
@@ -834,6 +835,7 @@ class _ChunkBase(_LabelBase):
|
|
|
834
835
|
|
|
835
836
|
def __init__(self, texts,
|
|
836
837
|
fill_colors=None,
|
|
838
|
+
align=None,
|
|
837
839
|
props=None, padding=2, bordercolor=None,
|
|
838
840
|
borderwidth=None, borderstyle=None,
|
|
839
841
|
**options):
|
|
@@ -841,6 +843,7 @@ class _ChunkBase(_LabelBase):
|
|
|
841
843
|
n = len(texts)
|
|
842
844
|
self.n = n
|
|
843
845
|
self.texts = texts
|
|
846
|
+
self.align = align
|
|
844
847
|
self.padding = padding
|
|
845
848
|
|
|
846
849
|
if is_color_like(fill_colors):
|
|
@@ -876,6 +879,21 @@ class _ChunkBase(_LabelBase):
|
|
|
876
879
|
super().__init__()
|
|
877
880
|
self._sort_params(**options)
|
|
878
881
|
|
|
882
|
+
align_pos = {
|
|
883
|
+
'right': 1,
|
|
884
|
+
'left': 0,
|
|
885
|
+
'top': 1,
|
|
886
|
+
'bottom': 0,
|
|
887
|
+
'center': 0.5
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
default_align = {
|
|
891
|
+
"right": "left",
|
|
892
|
+
"left": "right",
|
|
893
|
+
"top": "bottom",
|
|
894
|
+
"bottom": "top"
|
|
895
|
+
}
|
|
896
|
+
|
|
879
897
|
default_rotation = {
|
|
880
898
|
"right": -90,
|
|
881
899
|
"left": 90,
|
|
@@ -883,9 +901,31 @@ class _ChunkBase(_LabelBase):
|
|
|
883
901
|
"bottom": 0,
|
|
884
902
|
}
|
|
885
903
|
|
|
904
|
+
def get_alignment(self, ha, va, rotation):
|
|
905
|
+
if rotation in {90, -90}:
|
|
906
|
+
ha, va = va, ha # swap the alignment
|
|
907
|
+
align_x, align_y = self.align_pos[ha], self.align_pos[va]
|
|
908
|
+
return align_x, align_y
|
|
909
|
+
|
|
910
|
+
def _align_compact(self, align):
|
|
911
|
+
"""Make align keyword compatible to any side"""
|
|
912
|
+
if self.is_flank:
|
|
913
|
+
checker = {"top": "right", "bottom": "left"}
|
|
914
|
+
else:
|
|
915
|
+
checker = {"right": "top", "left": "bottom"}
|
|
916
|
+
return checker.get(align, align)
|
|
917
|
+
|
|
886
918
|
def get_text_params(self) -> TextParams:
|
|
887
|
-
|
|
888
|
-
|
|
919
|
+
if self.align is None:
|
|
920
|
+
self.align = self.default_align[self.side]
|
|
921
|
+
|
|
922
|
+
self.align = self._align_compact(self.align)
|
|
923
|
+
va, ha = self.align, 'center'
|
|
924
|
+
if self.is_flank:
|
|
925
|
+
va, ha = ha, va
|
|
926
|
+
|
|
927
|
+
rotation = self.default_rotation[self.side]
|
|
928
|
+
p = TextParams(rotation=rotation, va=va, ha=ha)
|
|
889
929
|
p.update_params(self._user_params)
|
|
890
930
|
return p
|
|
891
931
|
|
|
@@ -893,6 +933,23 @@ class _ChunkBase(_LabelBase):
|
|
|
893
933
|
borderwidth, borderstyle, props):
|
|
894
934
|
|
|
895
935
|
params = self.get_text_params()
|
|
936
|
+
if self.texts_size is not None:
|
|
937
|
+
padding_px = self.padding / 72
|
|
938
|
+
offset_ratio = padding_px / (self.texts_size + padding_px)
|
|
939
|
+
else:
|
|
940
|
+
offset_ratio = 0
|
|
941
|
+
|
|
942
|
+
if self.align == "center":
|
|
943
|
+
const = .5
|
|
944
|
+
elif self.align in ["right", "top"]:
|
|
945
|
+
const = 1 - offset_ratio / 2
|
|
946
|
+
else:
|
|
947
|
+
const = offset_ratio / 2 # self.text_pad / (1 + self.text_pad) / 2
|
|
948
|
+
|
|
949
|
+
# adjust the text alignment based on the alignment position and rotation
|
|
950
|
+
c = .5
|
|
951
|
+
x, y = (const, c) if self.is_flank else (c, const)
|
|
952
|
+
|
|
896
953
|
fill_colors = [] if fill_colors is None else fill_colors
|
|
897
954
|
border_colors = [] if border_colors is None else border_colors
|
|
898
955
|
borderwidth = [] if borderwidth is None else borderwidth
|
|
@@ -915,7 +972,8 @@ class _ChunkBase(_LabelBase):
|
|
|
915
972
|
|
|
916
973
|
if prop is not None:
|
|
917
974
|
fontdict.update(prop)
|
|
918
|
-
|
|
975
|
+
|
|
976
|
+
ax.text(x, y, t, fontdict=fontdict, transform=ax.transAxes)
|
|
919
977
|
|
|
920
978
|
|
|
921
979
|
class Chunk(_ChunkBase):
|
|
@@ -968,12 +1026,19 @@ class Chunk(_ChunkBase):
|
|
|
968
1026
|
|
|
969
1027
|
def __init__(self, texts,
|
|
970
1028
|
fill_colors=None,
|
|
1029
|
+
*,
|
|
1030
|
+
align=None,
|
|
971
1031
|
props=None, padding=8, bordercolor=None,
|
|
972
1032
|
borderwidth=None, borderstyle=None,
|
|
973
1033
|
**options):
|
|
974
1034
|
|
|
975
|
-
super().__init__(texts, fill_colors,
|
|
976
|
-
|
|
1035
|
+
super().__init__(texts, fill_colors=fill_colors,
|
|
1036
|
+
align=align,
|
|
1037
|
+
props=props, padding=padding,
|
|
1038
|
+
bordercolor=bordercolor,
|
|
1039
|
+
borderwidth=borderwidth,
|
|
1040
|
+
borderstyle=borderstyle,
|
|
1041
|
+
**options)
|
|
977
1042
|
|
|
978
1043
|
def render(self, axes):
|
|
979
1044
|
|
|
@@ -1057,11 +1122,12 @@ class FixedChunk(_ChunkBase):
|
|
|
1057
1122
|
|
|
1058
1123
|
"""
|
|
1059
1124
|
|
|
1060
|
-
def __init__(self, texts, fill_colors=None,
|
|
1125
|
+
def __init__(self, texts, fill_colors=None, *,
|
|
1126
|
+
align=None, ratio=None,
|
|
1061
1127
|
props=None, padding=8, bordercolor=None,
|
|
1062
1128
|
borderwidth=None, borderstyle=None,
|
|
1063
1129
|
**options):
|
|
1064
|
-
super().__init__(texts, fill_colors, props, padding, bordercolor,
|
|
1130
|
+
super().__init__(texts, align, fill_colors, props, padding, bordercolor,
|
|
1065
1131
|
borderwidth, borderstyle, **options)
|
|
1066
1132
|
if ratio is not None:
|
|
1067
1133
|
self.set_split_regroup(ratio)
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
marsilea/__init__.py,sha256=
|
|
1
|
+
marsilea/__init__.py,sha256=HuFssQKZgyp993Vh7vJupVG9KUVdBylpaxVzQ5LTdnU,398
|
|
2
2
|
marsilea/_api.py,sha256=txHnWiKWrQTm2CXB2t3_3F6xFuwi7-95N5cJ5s6xD5o,282
|
|
3
|
-
marsilea/_deform.py,sha256=
|
|
4
|
-
marsilea/base.py,sha256=
|
|
5
|
-
marsilea/dataset.py,sha256=
|
|
6
|
-
marsilea/dendrogram.py,sha256=
|
|
3
|
+
marsilea/_deform.py,sha256=I8Oh5ug8o0KNJRIxsfL7mBrqjHrkkOoZftT6GQg8gJM,14231
|
|
4
|
+
marsilea/base.py,sha256=iEW2N4QbwHcDApniTNdcmu0tdTkUKfM-09-TuAnYIt8,28408
|
|
5
|
+
marsilea/dataset.py,sha256=8EgpuVfc-R10tjMleBHGn4Ndv7E-4IxKswlz2kDIfUI,3786
|
|
6
|
+
marsilea/dendrogram.py,sha256=bYYC71MNv8DUIbuFkPDC7G7b1ZXEjJ09eX4tgt_hZa4,14620
|
|
7
7
|
marsilea/exceptions.py,sha256=9PQGXvhcrs0p43YnTX0T8OPqrIQL9cGK_C1U0b_L9Y4,650
|
|
8
8
|
marsilea/heatmap.py,sha256=HU2eX0rvCer0l1DcDRKBABig9dc3oLyMK1vgJNKZCag,3565
|
|
9
9
|
marsilea/layers.py,sha256=axS_QFNjni8DzGjb1hDL7oyj_w90j0RBXU5CyZ_RR8w,12273
|
|
@@ -14,13 +14,13 @@ marsilea/plotter/__init__.py,sha256=Og-b5LDBhe7h0VZ-m9zbaGzzdwCnbx8zrzTy8cApOdY,
|
|
|
14
14
|
marsilea/plotter/_images.py,sha256=xjEegaONfeMq0CBXGitHQfXdzVHt31hFV8S6leKcac8,2028
|
|
15
15
|
marsilea/plotter/_seaborn.py,sha256=bZf2eVoCGzWg3q8RguQJ6_Og_qv5FRVVs6Ek3Kaj7NE,7548
|
|
16
16
|
marsilea/plotter/_utils.py,sha256=ehYHVZOUA3R5v8gE2ysaIdyiM6to_0GGrMDhb-g_xlk,683
|
|
17
|
-
marsilea/plotter/arc.py,sha256=
|
|
18
|
-
marsilea/plotter/bar.py,sha256=
|
|
17
|
+
marsilea/plotter/arc.py,sha256=7epdsdxxawE0V2EIQEPxBE0LvnXCAqYx2dS-c0sIUV4,8059
|
|
18
|
+
marsilea/plotter/bar.py,sha256=XnLrF8EtTrmSC7YJBalTcVmh7X3CCw9Q-HcJc9hYblk,12321
|
|
19
19
|
marsilea/plotter/base.py,sha256=xhABfFpUbjAwVuj4FljDdYjFzm10eKyXDHVH6wMLnRY,19071
|
|
20
20
|
marsilea/plotter/bio.py,sha256=Q7EgghHwqtMmXrEEBKZk4TTOjW8kq6hylmUwMf07mt8,5115
|
|
21
21
|
marsilea/plotter/mesh.py,sha256=tw9yAn5b6kZWq4d_BGXUvDeU8LIluYgAsZnWqaRkUzA,22316
|
|
22
|
-
marsilea/plotter/text.py,sha256
|
|
23
|
-
marsilea-0.3.
|
|
24
|
-
marsilea-0.3.
|
|
25
|
-
marsilea-0.3.
|
|
26
|
-
marsilea-0.3.
|
|
22
|
+
marsilea/plotter/text.py,sha256=-H247bkol0fjPAE3GsWgpT5q4XaVo2UcirCF7FtLjEI,35155
|
|
23
|
+
marsilea-0.3.2.dist-info/LICENSE,sha256=UwNW8x-13BDuItvInfX2c0O_9TUp6vC9bjt8lsHqm5w,1074
|
|
24
|
+
marsilea-0.3.2.dist-info/WHEEL,sha256=EZbGkh7Ie4PoZfRQ8I0ZuP9VklN_TvcZ6DSE5Uar4z4,81
|
|
25
|
+
marsilea-0.3.2.dist-info/METADATA,sha256=4gt3Dw7-sDR6T0U7bv6nqpsK8fYfOOcPFsRvun5x1Eo,1701
|
|
26
|
+
marsilea-0.3.2.dist-info/RECORD,,
|
|
File without changes
|