marsilea 0.3.1__py3-none-any.whl → 0.3.3__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 +3 -2
- marsilea/_api.py +3 -2
- marsilea/_deform.py +44 -38
- marsilea/base.py +702 -118
- marsilea/dataset.py +49 -23
- marsilea/dendrogram.py +119 -84
- marsilea/exceptions.py +5 -6
- marsilea/heatmap.py +85 -25
- marsilea/layers.py +77 -69
- marsilea/layout.py +132 -102
- marsilea/plotter/__init__.py +29 -0
- marsilea/plotter/_images.py +37 -7
- marsilea/plotter/_seaborn.py +35 -24
- marsilea/plotter/_utils.py +3 -2
- marsilea/plotter/arc.py +29 -19
- marsilea/plotter/area.py +80 -0
- marsilea/plotter/bar.py +119 -92
- marsilea/plotter/base.py +76 -49
- marsilea/plotter/bio.py +40 -30
- marsilea/plotter/mesh.py +184 -88
- marsilea/plotter/text.py +303 -194
- marsilea/upset.py +229 -151
- marsilea/utils.py +23 -10
- {marsilea-0.3.1.dist-info → marsilea-0.3.3.dist-info}/LICENSE +1 -1
- marsilea-0.3.3.dist-info/METADATA +74 -0
- marsilea-0.3.3.dist-info/RECORD +27 -0
- marsilea-0.3.1.dist-info/METADATA +0 -50
- marsilea-0.3.1.dist-info/RECORD +0 -26
- {marsilea-0.3.1.dist-info → marsilea-0.3.3.dist-info}/WHEEL +0 -0
marsilea/plotter/text.py
CHANGED
|
@@ -22,6 +22,7 @@ class Segment:
|
|
|
22
22
|
"""
|
|
23
23
|
The total length is unchanged
|
|
24
24
|
"""
|
|
25
|
+
|
|
25
26
|
up: float
|
|
26
27
|
low: float
|
|
27
28
|
|
|
@@ -111,8 +112,9 @@ def adjust_segments(lim: Segment, segments: List[Segment]):
|
|
|
111
112
|
segments_length = np.sum(sl)
|
|
112
113
|
space = lim.length
|
|
113
114
|
if segments_length > space:
|
|
114
|
-
warnings.warn(
|
|
115
|
-
|
|
115
|
+
warnings.warn(
|
|
116
|
+
"No enough space to place all labels, " "try reducing the fontsize."
|
|
117
|
+
)
|
|
116
118
|
|
|
117
119
|
segments_length_r = segments_length
|
|
118
120
|
space_r = space
|
|
@@ -201,15 +203,23 @@ def plot_segments(segments, lim=None):
|
|
|
201
203
|
|
|
202
204
|
|
|
203
205
|
class AdjustableText:
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
206
|
+
def __init__(
|
|
207
|
+
self,
|
|
208
|
+
x,
|
|
209
|
+
y,
|
|
210
|
+
text,
|
|
211
|
+
ax=None,
|
|
212
|
+
renderer=None,
|
|
213
|
+
pointer=None,
|
|
214
|
+
expand=(1.05, 1.05),
|
|
215
|
+
va=None,
|
|
216
|
+
ha=None,
|
|
217
|
+
rotation=None,
|
|
218
|
+
connectionstyle=None,
|
|
219
|
+
relpos=None,
|
|
220
|
+
linewidth=None,
|
|
221
|
+
**kwargs,
|
|
222
|
+
):
|
|
213
223
|
if ax is None:
|
|
214
224
|
ax = plt.gca()
|
|
215
225
|
if renderer is None:
|
|
@@ -230,14 +240,19 @@ class AdjustableText:
|
|
|
230
240
|
self.connectionstyle = connectionstyle
|
|
231
241
|
self.relpos = relpos
|
|
232
242
|
self.linewidth = linewidth
|
|
233
|
-
self.text_obj = Text(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
243
|
+
self.text_obj = Text(
|
|
244
|
+
x,
|
|
245
|
+
y,
|
|
246
|
+
text,
|
|
247
|
+
va=va,
|
|
248
|
+
ha=ha,
|
|
249
|
+
rotation=rotation,
|
|
250
|
+
transform=self.ax.transAxes,
|
|
251
|
+
**kwargs,
|
|
252
|
+
)
|
|
237
253
|
self.text_options = kwargs
|
|
238
254
|
ax.add_artist(self.text_obj)
|
|
239
|
-
self._bbox = self.text_obj.get_window_extent(self.renderer)
|
|
240
|
-
.expanded(*expand)
|
|
255
|
+
self._bbox = self.text_obj.get_window_extent(self.renderer).expanded(*expand)
|
|
241
256
|
self.annotation = None
|
|
242
257
|
|
|
243
258
|
def get_display_coordinate(self):
|
|
@@ -282,8 +297,9 @@ class AdjustableText:
|
|
|
282
297
|
arrowstyle="-",
|
|
283
298
|
connectionstyle=self.connectionstyle,
|
|
284
299
|
linewidth=self.linewidth,
|
|
285
|
-
relpos=self.relpos
|
|
286
|
-
|
|
300
|
+
relpos=self.relpos,
|
|
301
|
+
),
|
|
302
|
+
**self.text_options,
|
|
287
303
|
)
|
|
288
304
|
|
|
289
305
|
|
|
@@ -307,11 +323,7 @@ class TextParams:
|
|
|
307
323
|
self._params[k] = v
|
|
308
324
|
|
|
309
325
|
def to_dict(self):
|
|
310
|
-
p = dict(
|
|
311
|
-
va=self._va,
|
|
312
|
-
ha=self._ha,
|
|
313
|
-
rotation=self.rotation
|
|
314
|
-
)
|
|
326
|
+
p = dict(va=self._va, ha=self._ha, rotation=self.rotation)
|
|
315
327
|
p.update(self._params)
|
|
316
328
|
return p
|
|
317
329
|
|
|
@@ -350,16 +362,15 @@ class _LabelBase(RenderPlan):
|
|
|
350
362
|
def get_text_color(bgcolor):
|
|
351
363
|
"""Get text color by background color"""
|
|
352
364
|
lum = relative_luminance(bgcolor)
|
|
353
|
-
return ".15" if lum > .408 else "w"
|
|
365
|
+
return ".15" if lum > 0.408 else "w"
|
|
354
366
|
|
|
355
367
|
def get_expand(self):
|
|
356
368
|
if self.is_flank:
|
|
357
|
-
return 1. + self.text_pad, 1. + self.text_gap
|
|
369
|
+
return 1.0 + self.text_pad, 1.0 + self.text_gap
|
|
358
370
|
else:
|
|
359
|
-
return 1. + self.text_gap, 1. + self.text_pad
|
|
360
|
-
|
|
361
|
-
def silent_render(self, figure, expand=(1., 1.)):
|
|
371
|
+
return 1.0 + self.text_gap, 1.0 + self.text_pad
|
|
362
372
|
|
|
373
|
+
def silent_render(self, figure, expand=(1.0, 1.0)):
|
|
363
374
|
renderer = figure.canvas.get_renderer()
|
|
364
375
|
ax = figure.add_axes([0, 0, 1, 1])
|
|
365
376
|
params = self.get_text_params()
|
|
@@ -398,26 +409,28 @@ class AnnoTextConfig:
|
|
|
398
409
|
def get_connectionstyle(self, armA=None, armB=None):
|
|
399
410
|
armA = self.armA if armA is None else armA
|
|
400
411
|
armB = self.armB if armB is None else armB
|
|
401
|
-
return
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
412
|
+
return (
|
|
413
|
+
f"arc,angleA={self.angleA},"
|
|
414
|
+
f"angleB={self.angleB},"
|
|
415
|
+
f"armA={armA},"
|
|
416
|
+
f"armB={armB},"
|
|
417
|
+
f"rad=0"
|
|
418
|
+
)
|
|
406
419
|
|
|
407
420
|
|
|
408
421
|
anno_default_params = {
|
|
409
|
-
"top": AnnoTextConfig(
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
"bottom": AnnoTextConfig(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
"right": AnnoTextConfig(
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
"left": AnnoTextConfig(
|
|
419
|
-
|
|
420
|
-
|
|
422
|
+
"top": AnnoTextConfig(
|
|
423
|
+
va="bottom", ha="center", rotation=90, angleA=-90, angleB=90, relpos=(0.5, 0)
|
|
424
|
+
),
|
|
425
|
+
"bottom": AnnoTextConfig(
|
|
426
|
+
va="top", ha="center", rotation=-90, angleA=90, angleB=-90, relpos=(0.5, 1)
|
|
427
|
+
),
|
|
428
|
+
"right": AnnoTextConfig(
|
|
429
|
+
va="center", ha="left", rotation=0, angleA=-180, angleB=0, relpos=(0, 0.5)
|
|
430
|
+
),
|
|
431
|
+
"left": AnnoTextConfig(
|
|
432
|
+
va="center", ha="right", rotation=0, angleA=0, angleB=-180, relpos=(1, 0.5)
|
|
433
|
+
),
|
|
421
434
|
}
|
|
422
435
|
|
|
423
436
|
|
|
@@ -447,6 +460,12 @@ class AnnoLabels(_LabelBase):
|
|
|
447
460
|
connectionstyle :
|
|
448
461
|
relpos : 2-tuple
|
|
449
462
|
armA, armB : float
|
|
463
|
+
label : str
|
|
464
|
+
The label of the plot
|
|
465
|
+
label_loc : {'top', 'bottom', 'left', 'right'}
|
|
466
|
+
The location of the label
|
|
467
|
+
label_props : dict
|
|
468
|
+
The label properties
|
|
450
469
|
options :
|
|
451
470
|
Pass to :class:`matplotlib.text.Text`
|
|
452
471
|
|
|
@@ -469,22 +488,32 @@ class AnnoLabels(_LabelBase):
|
|
|
469
488
|
|
|
470
489
|
"""
|
|
471
490
|
|
|
472
|
-
def __init__(
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
491
|
+
def __init__(
|
|
492
|
+
self,
|
|
493
|
+
labels: Iterable | np.ma.MaskedArray,
|
|
494
|
+
mark=None,
|
|
495
|
+
text_pad=0.5,
|
|
496
|
+
text_gap=0.5,
|
|
497
|
+
pointer_size=0.5,
|
|
498
|
+
linewidth=None,
|
|
499
|
+
connectionstyle=None,
|
|
500
|
+
relpos=None,
|
|
501
|
+
armA=None,
|
|
502
|
+
armB=None,
|
|
503
|
+
label=None,
|
|
504
|
+
label_loc=None,
|
|
505
|
+
label_props=None,
|
|
506
|
+
**options,
|
|
507
|
+
):
|
|
481
508
|
if not np.ma.isMaskedArray(labels):
|
|
482
509
|
if mark is not None:
|
|
483
510
|
labels = np.ma.masked_where(~np.in1d(labels, mark), labels)
|
|
484
511
|
else:
|
|
485
|
-
raise TypeError(
|
|
486
|
-
|
|
487
|
-
|
|
512
|
+
raise TypeError(
|
|
513
|
+
"Must be numpy masked array or "
|
|
514
|
+
"use `marks` to mark "
|
|
515
|
+
"the labels you want to draw"
|
|
516
|
+
)
|
|
488
517
|
texts = self.data_validator(labels, target="1d")
|
|
489
518
|
self.set_data(texts)
|
|
490
519
|
self.texts = texts
|
|
@@ -501,15 +530,17 @@ class AnnoLabels(_LabelBase):
|
|
|
501
530
|
|
|
502
531
|
super().__init__()
|
|
503
532
|
self._sort_params(**options)
|
|
533
|
+
self.set_label(label, label_loc, label_props)
|
|
504
534
|
|
|
505
535
|
def get_text_params(self):
|
|
506
536
|
default_params = anno_default_params[self.side]
|
|
507
537
|
self.relpos = default_params.relpos
|
|
508
|
-
self.connectionstyle = (
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
params = dict(
|
|
512
|
-
|
|
538
|
+
self.connectionstyle = default_params.get_connectionstyle(
|
|
539
|
+
armA=self.armA, armB=self.armB
|
|
540
|
+
)
|
|
541
|
+
params = dict(
|
|
542
|
+
va=default_params.va, ha=default_params.ha, rotation=default_params.rotation
|
|
543
|
+
)
|
|
513
544
|
p = TextParams(**params)
|
|
514
545
|
p.update_params(self._user_params)
|
|
515
546
|
return p
|
|
@@ -531,12 +562,15 @@ class AnnoLabels(_LabelBase):
|
|
|
531
562
|
ax_bbox = ax.get_window_extent(renderer)
|
|
532
563
|
params = self.get_text_params()
|
|
533
564
|
|
|
534
|
-
text_options = dict(
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
565
|
+
text_options = dict(
|
|
566
|
+
ax=ax,
|
|
567
|
+
renderer=renderer,
|
|
568
|
+
expand=self.get_expand(),
|
|
569
|
+
linewidth=self.linewidth,
|
|
570
|
+
relpos=self.relpos,
|
|
571
|
+
connectionstyle=self.connectionstyle,
|
|
572
|
+
**params.to_dict(),
|
|
573
|
+
)
|
|
540
574
|
|
|
541
575
|
texts = []
|
|
542
576
|
segments = []
|
|
@@ -544,8 +578,7 @@ class AnnoLabels(_LabelBase):
|
|
|
544
578
|
y = self.text_anchor
|
|
545
579
|
for x, s in zip(locs, labels):
|
|
546
580
|
if not np.ma.is_masked(s):
|
|
547
|
-
t = AdjustableText(x=x, y=y, text=s, pointer=(x, 0),
|
|
548
|
-
**text_options)
|
|
581
|
+
t = AdjustableText(x=x, y=y, text=s, pointer=(x, 0), **text_options)
|
|
549
582
|
texts.append(t)
|
|
550
583
|
segments.append(t.get_segment_x())
|
|
551
584
|
|
|
@@ -558,8 +591,7 @@ class AnnoLabels(_LabelBase):
|
|
|
558
591
|
x = self.text_anchor
|
|
559
592
|
for y, s in zip(locs, labels):
|
|
560
593
|
if not np.ma.is_masked(s):
|
|
561
|
-
t = AdjustableText(x=x, y=y, text=s, pointer=(0, y),
|
|
562
|
-
**text_options)
|
|
594
|
+
t = AdjustableText(x=x, y=y, text=s, pointer=(0, y), **text_options)
|
|
563
595
|
texts.append(t)
|
|
564
596
|
segments.append(t.get_segment_y())
|
|
565
597
|
lim = Segment(ax_bbox.ymin, ax_bbox.ymax)
|
|
@@ -576,10 +608,10 @@ class AnnoLabels(_LabelBase):
|
|
|
576
608
|
|
|
577
609
|
|
|
578
610
|
label_default_params = {
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
611
|
+
"right": dict(align="left", rotation=0),
|
|
612
|
+
"left": dict(align="right", rotation=0),
|
|
613
|
+
"top": dict(align="bottom", rotation=90),
|
|
614
|
+
"bottom": dict(align="top", rotation=90),
|
|
583
615
|
}
|
|
584
616
|
|
|
585
617
|
|
|
@@ -595,6 +627,12 @@ class Labels(_LabelBase):
|
|
|
595
627
|
The buffer space between text and the adjcent plots, in points unit
|
|
596
628
|
text_props : dict
|
|
597
629
|
A dict of array that control the text properties for each text.
|
|
630
|
+
label : str
|
|
631
|
+
The label of the plot
|
|
632
|
+
label_loc : {'top', 'bottom', 'left', 'right'}
|
|
633
|
+
The location of the label
|
|
634
|
+
label_props : dict
|
|
635
|
+
The label properties
|
|
598
636
|
options : dict
|
|
599
637
|
Pass to :class:`matplotlib.text.Text`
|
|
600
638
|
|
|
@@ -618,10 +656,17 @@ class Labels(_LabelBase):
|
|
|
618
656
|
|
|
619
657
|
"""
|
|
620
658
|
|
|
621
|
-
def __init__(
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
659
|
+
def __init__(
|
|
660
|
+
self,
|
|
661
|
+
labels,
|
|
662
|
+
align=None,
|
|
663
|
+
padding=2,
|
|
664
|
+
text_props=None,
|
|
665
|
+
label=None,
|
|
666
|
+
label_loc=None,
|
|
667
|
+
label_props=None,
|
|
668
|
+
**options,
|
|
669
|
+
):
|
|
625
670
|
labels = self.data_validator(labels, target="1d")
|
|
626
671
|
self.set_data(labels)
|
|
627
672
|
self.texts = labels
|
|
@@ -634,6 +679,7 @@ class Labels(_LabelBase):
|
|
|
634
679
|
self._sort_params(**options)
|
|
635
680
|
if text_props is not None:
|
|
636
681
|
self.set_params(text_props)
|
|
682
|
+
self.set_label(label, label_loc, label_props)
|
|
637
683
|
|
|
638
684
|
def _align_compact(self, align):
|
|
639
685
|
"""Make align keyword compatible to any side"""
|
|
@@ -646,14 +692,14 @@ class Labels(_LabelBase):
|
|
|
646
692
|
def get_text_params(self) -> TextParams:
|
|
647
693
|
default_params = label_default_params[self.side]
|
|
648
694
|
if self.align is None:
|
|
649
|
-
self.align = default_params[
|
|
695
|
+
self.align = default_params["align"]
|
|
650
696
|
|
|
651
697
|
self.align = self._align_compact(self.align)
|
|
652
|
-
va, ha = self.align,
|
|
698
|
+
va, ha = self.align, "center"
|
|
653
699
|
if self.is_flank:
|
|
654
700
|
va, ha = ha, va
|
|
655
701
|
|
|
656
|
-
p = TextParams(va=va, ha=ha, rotation=default_params[
|
|
702
|
+
p = TextParams(va=va, ha=ha, rotation=default_params["rotation"])
|
|
657
703
|
p.update_params(self._user_params)
|
|
658
704
|
|
|
659
705
|
return p
|
|
@@ -676,7 +722,7 @@ class Labels(_LabelBase):
|
|
|
676
722
|
if self.is_flank:
|
|
677
723
|
coords = coords[::-1]
|
|
678
724
|
if self.align == "center":
|
|
679
|
-
const = .5
|
|
725
|
+
const = 0.5
|
|
680
726
|
elif self.align in ["right", "top"]:
|
|
681
727
|
const = 1 - offset_ratio / 2
|
|
682
728
|
else:
|
|
@@ -685,8 +731,7 @@ class Labels(_LabelBase):
|
|
|
685
731
|
for s, c, p in zip(data, coords, text_props):
|
|
686
732
|
x, y = (const, c) if self.is_flank else (c, const)
|
|
687
733
|
options = {**params.to_dict(), **p}
|
|
688
|
-
ax.text(x, y, s=s, transform=ax.transAxes,
|
|
689
|
-
**options)
|
|
734
|
+
ax.text(x, y, s=s, transform=ax.transAxes, **options)
|
|
690
735
|
ax.set_axis_off()
|
|
691
736
|
# from matplotlib.patches import Rectangle
|
|
692
737
|
# ax.add_artist(Rectangle((0, 0), 1, 1, edgecolor="r",
|
|
@@ -742,16 +787,21 @@ class Title(_LabelBase):
|
|
|
742
787
|
|
|
743
788
|
|
|
744
789
|
"""
|
|
790
|
+
|
|
745
791
|
allow_split = False
|
|
746
792
|
|
|
747
|
-
def __init__(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
793
|
+
def __init__(
|
|
794
|
+
self,
|
|
795
|
+
title,
|
|
796
|
+
align="center",
|
|
797
|
+
padding=10,
|
|
798
|
+
fontsize=None,
|
|
799
|
+
fill_color=None,
|
|
800
|
+
bordercolor=None,
|
|
801
|
+
borderwidth=None,
|
|
802
|
+
borderstyle=None,
|
|
803
|
+
**options,
|
|
804
|
+
):
|
|
755
805
|
self.title = title
|
|
756
806
|
self.texts = [title]
|
|
757
807
|
self.align = align
|
|
@@ -766,25 +816,18 @@ class Title(_LabelBase):
|
|
|
766
816
|
self.bordercolor = bordercolor
|
|
767
817
|
self.borderwidth = borderwidth
|
|
768
818
|
self.borderstyle = borderstyle
|
|
769
|
-
self._draw_bg = (self.fill_color is not None)
|
|
770
|
-
or (self.bordercolor is not None)
|
|
819
|
+
self._draw_bg = (self.fill_color is not None) or (self.bordercolor is not None)
|
|
771
820
|
|
|
772
821
|
super().__init__()
|
|
773
822
|
self._sort_params(**options)
|
|
774
823
|
|
|
775
|
-
align_pos = {
|
|
776
|
-
'right': 1,
|
|
777
|
-
'left': 0,
|
|
778
|
-
'top': 1,
|
|
779
|
-
'bottom': 0,
|
|
780
|
-
'center': 0.5
|
|
781
|
-
}
|
|
824
|
+
align_pos = {"right": 1, "left": 0, "top": 1, "bottom": 0, "center": 0.5}
|
|
782
825
|
|
|
783
826
|
default_rotation = {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
827
|
+
"right": -90,
|
|
828
|
+
"left": 90,
|
|
829
|
+
"top": 0,
|
|
830
|
+
"bottom": 0,
|
|
788
831
|
}
|
|
789
832
|
|
|
790
833
|
def _align_compact(self, align):
|
|
@@ -797,12 +840,11 @@ class Title(_LabelBase):
|
|
|
797
840
|
|
|
798
841
|
def get_text_params(self) -> TextParams:
|
|
799
842
|
self.align = self._align_compact(self.align)
|
|
800
|
-
va, ha =
|
|
843
|
+
va, ha = "center", self.align
|
|
801
844
|
if self.is_flank:
|
|
802
845
|
va, ha = ha, va
|
|
803
846
|
|
|
804
|
-
p = TextParams(rotation=self.default_rotation[self.side],
|
|
805
|
-
va=va, ha=ha)
|
|
847
|
+
p = TextParams(rotation=self.default_rotation[self.side], va=va, ha=ha)
|
|
806
848
|
p.update_params(self._user_params)
|
|
807
849
|
return p
|
|
808
850
|
|
|
@@ -812,34 +854,44 @@ class Title(_LabelBase):
|
|
|
812
854
|
|
|
813
855
|
if self._draw_bg:
|
|
814
856
|
bgcolor = "white" if self.fill_color is None else self.fill_color
|
|
815
|
-
ax.add_artist(
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
857
|
+
ax.add_artist(
|
|
858
|
+
Rectangle(
|
|
859
|
+
(0, 0),
|
|
860
|
+
1,
|
|
861
|
+
1,
|
|
862
|
+
facecolor=self.fill_color,
|
|
863
|
+
edgecolor=self.bordercolor,
|
|
864
|
+
linewidth=self.borderwidth,
|
|
865
|
+
linestyle=self.borderstyle,
|
|
866
|
+
transform=ax.transAxes,
|
|
867
|
+
)
|
|
868
|
+
)
|
|
869
|
+
|
|
870
|
+
fontdict.setdefault("color", self.get_text_color(bgcolor))
|
|
824
871
|
|
|
825
872
|
const = self.align_pos[self.align]
|
|
826
873
|
|
|
827
|
-
pos = .5
|
|
874
|
+
pos = 0.5
|
|
828
875
|
x, y = (const, pos) if self.is_body else (pos, const)
|
|
829
|
-
ax.text(
|
|
830
|
-
|
|
876
|
+
ax.text(
|
|
877
|
+
x, y, self.title, fontsize=self.fontsize, transform=ax.transAxes, **fontdict
|
|
878
|
+
)
|
|
831
879
|
ax.set_axis_off()
|
|
832
880
|
|
|
833
881
|
|
|
834
882
|
class _ChunkBase(_LabelBase):
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
883
|
+
def __init__(
|
|
884
|
+
self,
|
|
885
|
+
texts,
|
|
886
|
+
fill_colors=None,
|
|
887
|
+
align=None,
|
|
888
|
+
props=None,
|
|
889
|
+
padding=2,
|
|
890
|
+
bordercolor=None,
|
|
891
|
+
borderwidth=None,
|
|
892
|
+
borderstyle=None,
|
|
893
|
+
**options,
|
|
894
|
+
):
|
|
843
895
|
n = len(texts)
|
|
844
896
|
self.n = n
|
|
845
897
|
self.texts = texts
|
|
@@ -872,27 +924,15 @@ class _ChunkBase(_LabelBase):
|
|
|
872
924
|
borderstyle = [borderstyle for _ in range(n)]
|
|
873
925
|
self.borderstyle = borderstyle
|
|
874
926
|
|
|
875
|
-
self._draw_bg = (self.fill_colors is not None)
|
|
876
|
-
or (self.bordercolor is not None)
|
|
927
|
+
self._draw_bg = (self.fill_colors is not None) or (self.bordercolor is not None)
|
|
877
928
|
self.text_pad = 0
|
|
878
929
|
|
|
879
930
|
super().__init__()
|
|
880
931
|
self._sort_params(**options)
|
|
881
932
|
|
|
882
|
-
align_pos = {
|
|
883
|
-
'right': 1,
|
|
884
|
-
'left': 0,
|
|
885
|
-
'top': 1,
|
|
886
|
-
'bottom': 0,
|
|
887
|
-
'center': 0.5
|
|
888
|
-
}
|
|
933
|
+
align_pos = {"right": 1, "left": 0, "top": 1, "bottom": 0, "center": 0.5}
|
|
889
934
|
|
|
890
|
-
default_align = {
|
|
891
|
-
"right": "left",
|
|
892
|
-
"left": "right",
|
|
893
|
-
"top": "bottom",
|
|
894
|
-
"bottom": "top"
|
|
895
|
-
}
|
|
935
|
+
default_align = {"right": "left", "left": "right", "top": "bottom", "bottom": "top"}
|
|
896
936
|
|
|
897
937
|
default_rotation = {
|
|
898
938
|
"right": -90,
|
|
@@ -920,7 +960,7 @@ class _ChunkBase(_LabelBase):
|
|
|
920
960
|
self.align = self.default_align[self.side]
|
|
921
961
|
|
|
922
962
|
self.align = self._align_compact(self.align)
|
|
923
|
-
va, ha = self.align,
|
|
963
|
+
va, ha = self.align, "center"
|
|
924
964
|
if self.is_flank:
|
|
925
965
|
va, ha = ha, va
|
|
926
966
|
|
|
@@ -929,9 +969,9 @@ class _ChunkBase(_LabelBase):
|
|
|
929
969
|
p.update_params(self._user_params)
|
|
930
970
|
return p
|
|
931
971
|
|
|
932
|
-
def _render(
|
|
933
|
-
|
|
934
|
-
|
|
972
|
+
def _render(
|
|
973
|
+
self, axes, texts, fill_colors, border_colors, borderwidth, borderstyle, props
|
|
974
|
+
):
|
|
935
975
|
params = self.get_text_params()
|
|
936
976
|
if self.texts_size is not None:
|
|
937
977
|
padding_px = self.padding / 72
|
|
@@ -940,14 +980,14 @@ class _ChunkBase(_LabelBase):
|
|
|
940
980
|
offset_ratio = 0
|
|
941
981
|
|
|
942
982
|
if self.align == "center":
|
|
943
|
-
const = .5
|
|
983
|
+
const = 0.5
|
|
944
984
|
elif self.align in ["right", "top"]:
|
|
945
985
|
const = 1 - offset_ratio / 2
|
|
946
986
|
else:
|
|
947
987
|
const = offset_ratio / 2 # self.text_pad / (1 + self.text_pad) / 2
|
|
948
988
|
|
|
949
989
|
# adjust the text alignment based on the alignment position and rotation
|
|
950
|
-
c = .5
|
|
990
|
+
c = 0.5
|
|
951
991
|
x, y = (const, c) if self.is_flank else (c, const)
|
|
952
992
|
|
|
953
993
|
fill_colors = [] if fill_colors is None else fill_colors
|
|
@@ -956,19 +996,27 @@ class _ChunkBase(_LabelBase):
|
|
|
956
996
|
borderstyle = [] if borderstyle is None else borderstyle
|
|
957
997
|
props = [] if props is None else props
|
|
958
998
|
|
|
959
|
-
specs = zip_longest(
|
|
960
|
-
|
|
999
|
+
specs = zip_longest(
|
|
1000
|
+
axes, texts, fill_colors, border_colors, borderwidth, borderstyle, props
|
|
1001
|
+
)
|
|
961
1002
|
for ax, t, bgcolor, bc, lw, ls, prop in specs:
|
|
962
1003
|
ax.set_axis_off()
|
|
963
1004
|
fontdict = params.to_dict()
|
|
964
1005
|
if self._draw_bg:
|
|
965
1006
|
if bgcolor is None:
|
|
966
1007
|
bgcolor = "white"
|
|
967
|
-
rect = Rectangle(
|
|
968
|
-
|
|
969
|
-
|
|
1008
|
+
rect = Rectangle(
|
|
1009
|
+
(0, 0),
|
|
1010
|
+
1,
|
|
1011
|
+
1,
|
|
1012
|
+
facecolor=bgcolor,
|
|
1013
|
+
edgecolor=bc,
|
|
1014
|
+
linewidth=lw,
|
|
1015
|
+
linestyle=ls,
|
|
1016
|
+
transform=ax.transAxes,
|
|
1017
|
+
)
|
|
970
1018
|
ax.add_artist(rect)
|
|
971
|
-
fontdict.setdefault(
|
|
1019
|
+
fontdict.setdefault("color", self.get_text_color(bgcolor))
|
|
972
1020
|
|
|
973
1021
|
if prop is not None:
|
|
974
1022
|
fontdict.update(prop)
|
|
@@ -989,7 +1037,7 @@ class Chunk(_ChunkBase):
|
|
|
989
1037
|
The label for each chunk
|
|
990
1038
|
fill_colors : color, array of color
|
|
991
1039
|
The color used as background color for each chunk
|
|
992
|
-
borderwidth, bordercolor, borderstyle :
|
|
1040
|
+
borderwidth, bordercolor, borderstyle :
|
|
993
1041
|
Control the style of border, you can pass an array to style each group.
|
|
994
1042
|
For borderstyle, see :meth:`linestyles <matplotlib.lines.Line2D.set_linestyle>`
|
|
995
1043
|
props : dict or array of dict
|
|
@@ -998,6 +1046,12 @@ class Chunk(_ChunkBase):
|
|
|
998
1046
|
How many angle to rotate the text coutner-clockwise, in degree unit
|
|
999
1047
|
padding : float
|
|
1000
1048
|
The buffer space between text and the adjcent plots, in points unit
|
|
1049
|
+
label : str
|
|
1050
|
+
The label of the plot
|
|
1051
|
+
label_loc : {'right', 'left', 'top', 'bottom'}
|
|
1052
|
+
The location of the label
|
|
1053
|
+
label_props : dict
|
|
1054
|
+
The label properties
|
|
1001
1055
|
|
|
1002
1056
|
See Also
|
|
1003
1057
|
--------
|
|
@@ -1021,33 +1075,46 @@ class Chunk(_ChunkBase):
|
|
|
1021
1075
|
>>> h.add_right(Chunk(chunk, bordercolor="gray"), pad=.1)
|
|
1022
1076
|
>>> h.add_dendrogram("left")
|
|
1023
1077
|
>>> h.render()
|
|
1024
|
-
|
|
1078
|
+
|
|
1025
1079
|
"""
|
|
1026
1080
|
|
|
1027
|
-
def __init__(
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1081
|
+
def __init__(
|
|
1082
|
+
self,
|
|
1083
|
+
texts,
|
|
1084
|
+
fill_colors=None,
|
|
1085
|
+
*,
|
|
1086
|
+
align=None,
|
|
1087
|
+
props=None,
|
|
1088
|
+
padding=8,
|
|
1089
|
+
bordercolor=None,
|
|
1090
|
+
borderwidth=None,
|
|
1091
|
+
borderstyle=None,
|
|
1092
|
+
label=None,
|
|
1093
|
+
label_loc=None,
|
|
1094
|
+
label_props=None,
|
|
1095
|
+
**options,
|
|
1096
|
+
):
|
|
1097
|
+
super().__init__(
|
|
1098
|
+
texts,
|
|
1099
|
+
fill_colors=fill_colors,
|
|
1100
|
+
align=align,
|
|
1101
|
+
props=props,
|
|
1102
|
+
padding=padding,
|
|
1103
|
+
bordercolor=bordercolor,
|
|
1104
|
+
borderwidth=borderwidth,
|
|
1105
|
+
borderstyle=borderstyle,
|
|
1106
|
+
**options,
|
|
1107
|
+
)
|
|
1108
|
+
self.set_label(label, label_loc, label_props)
|
|
1042
1109
|
|
|
1043
1110
|
def render(self, axes):
|
|
1044
|
-
|
|
1045
1111
|
if isinstance(axes, Axes):
|
|
1046
1112
|
axes = [axes]
|
|
1047
1113
|
|
|
1048
1114
|
if len(axes) != self.n:
|
|
1049
|
-
raise ValueError(
|
|
1050
|
-
|
|
1115
|
+
raise ValueError(
|
|
1116
|
+
f"You have {len(axes)} axes " f"but you only provide {self.n} texts."
|
|
1117
|
+
)
|
|
1051
1118
|
|
|
1052
1119
|
texts = self.reindex_by_chunk(self.texts)
|
|
1053
1120
|
fill_colors = self.reindex_by_chunk(self.fill_colors)
|
|
@@ -1056,8 +1123,12 @@ class Chunk(_ChunkBase):
|
|
|
1056
1123
|
borderstyle = self.reindex_by_chunk(self.borderstyle)
|
|
1057
1124
|
props = self.reindex_by_chunk(self.props)
|
|
1058
1125
|
|
|
1059
|
-
self._render(
|
|
1060
|
-
|
|
1126
|
+
self._render(
|
|
1127
|
+
axes, texts, fill_colors, border_colors, borderwidth, borderstyle, props
|
|
1128
|
+
)
|
|
1129
|
+
|
|
1130
|
+
if self._plan_label is not None:
|
|
1131
|
+
self._plan_label.add(axes, self.side)
|
|
1061
1132
|
|
|
1062
1133
|
|
|
1063
1134
|
class FixedChunk(_ChunkBase):
|
|
@@ -1080,6 +1151,12 @@ class FixedChunk(_ChunkBase):
|
|
|
1080
1151
|
How many to rotate the text
|
|
1081
1152
|
padding : float
|
|
1082
1153
|
The buffer space between text and the adjcent plots, in points unit
|
|
1154
|
+
label : str
|
|
1155
|
+
The label of the plot
|
|
1156
|
+
label_loc : {'right', 'left', 'top', 'bottom'}
|
|
1157
|
+
The location of the label
|
|
1158
|
+
label_props : dict
|
|
1159
|
+
The label properties
|
|
1083
1160
|
|
|
1084
1161
|
|
|
1085
1162
|
See Also
|
|
@@ -1115,31 +1192,63 @@ class FixedChunk(_ChunkBase):
|
|
|
1115
1192
|
>>> labels = np.random.choice(chunk, size=20)
|
|
1116
1193
|
>>> h.hsplit(labels=labels, order=chunk)
|
|
1117
1194
|
>>> h.add_right(FixedChunk(chunk, bordercolor="gray"), pad=.1)
|
|
1118
|
-
>>> h.add_right(FixedChunk(['C1', 'C2', 'C3'],
|
|
1119
|
-
... ratio=[1, 2, 1],
|
|
1195
|
+
>>> h.add_right(FixedChunk(['C1', 'C2', 'C3'], fill_colors="red",
|
|
1196
|
+
... ratio=[1, 2, 1], ), pad=.1)
|
|
1120
1197
|
>>> h.render()
|
|
1121
1198
|
|
|
1122
1199
|
|
|
1123
1200
|
"""
|
|
1124
1201
|
|
|
1125
|
-
def __init__(
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1202
|
+
def __init__(
|
|
1203
|
+
self,
|
|
1204
|
+
texts,
|
|
1205
|
+
fill_colors=None,
|
|
1206
|
+
*,
|
|
1207
|
+
align=None,
|
|
1208
|
+
ratio=None,
|
|
1209
|
+
props=None,
|
|
1210
|
+
padding=8,
|
|
1211
|
+
bordercolor=None,
|
|
1212
|
+
borderwidth=None,
|
|
1213
|
+
borderstyle=None,
|
|
1214
|
+
label=None,
|
|
1215
|
+
label_loc=None,
|
|
1216
|
+
label_props=None,
|
|
1217
|
+
**options,
|
|
1218
|
+
):
|
|
1219
|
+
super().__init__(
|
|
1220
|
+
texts,
|
|
1221
|
+
fill_colors,
|
|
1222
|
+
align,
|
|
1223
|
+
props,
|
|
1224
|
+
padding,
|
|
1225
|
+
bordercolor,
|
|
1226
|
+
borderwidth,
|
|
1227
|
+
borderstyle,
|
|
1228
|
+
**options,
|
|
1229
|
+
)
|
|
1132
1230
|
if ratio is not None:
|
|
1133
1231
|
self.set_split_regroup(ratio)
|
|
1232
|
+
self.set_label(label, label_loc, label_props)
|
|
1134
1233
|
|
|
1135
1234
|
def render(self, axes):
|
|
1136
|
-
|
|
1137
1235
|
if isinstance(axes, Axes):
|
|
1138
1236
|
axes = [axes]
|
|
1139
1237
|
|
|
1140
1238
|
if len(axes) != self.n:
|
|
1141
|
-
raise ValueError(
|
|
1142
|
-
|
|
1239
|
+
raise ValueError(
|
|
1240
|
+
f"You have {len(axes)} axes " f"but you only provide {self.n} texts."
|
|
1241
|
+
)
|
|
1242
|
+
|
|
1243
|
+
self._render(
|
|
1244
|
+
axes,
|
|
1245
|
+
self.texts,
|
|
1246
|
+
self.fill_colors,
|
|
1247
|
+
self.bordercolor,
|
|
1248
|
+
self.borderwidth,
|
|
1249
|
+
self.borderstyle,
|
|
1250
|
+
self.props,
|
|
1251
|
+
)
|
|
1143
1252
|
|
|
1144
|
-
|
|
1145
|
-
|
|
1253
|
+
if self._plan_label is not None:
|
|
1254
|
+
self._plan_label.add(axes, self.side)
|