marsilea 0.4.2__py3-none-any.whl → 0.4.4__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.
@@ -0,0 +1,293 @@
1
+ # The implementation of Image in matplotlib may suffer from compatibility
2
+ # issues across different rendering backend at different DPI. Currently
3
+ # not a public API.
4
+ from functools import partial
5
+ from hashlib import sha256
6
+ from numbers import Number
7
+ from pathlib import Path
8
+
9
+ import numpy as np
10
+ from PIL import Image as PILImage
11
+ from matplotlib.image import imread, BboxImage
12
+ from matplotlib.transforms import Bbox
13
+ from platformdirs import user_cache_dir
14
+
15
+ from .base import RenderPlan
16
+
17
+
18
+ def _cache_remote(url, cache=True):
19
+ try:
20
+ import requests
21
+ except ImportError:
22
+ raise ImportError("Required requests, try `pip install requests`.")
23
+ data_dir = Path(user_cache_dir(appname="Marsilea"))
24
+ data_dir.mkdir(exist_ok=True, parents=True)
25
+
26
+ hasher = sha256()
27
+ hasher.update(url.encode("utf-8"))
28
+ fname = hasher.hexdigest()
29
+
30
+ dest = data_dir / fname
31
+ if not (cache and dest.exists()):
32
+ r = requests.get(url, headers={"User-Agent": "Mozilla/5.0"})
33
+ r.raise_for_status()
34
+ with open(dest, "wb") as f:
35
+ f.write(r.content)
36
+
37
+ return dest
38
+
39
+
40
+ class Image(RenderPlan):
41
+ """Plot static images
42
+
43
+ Parameters
44
+ ----------
45
+ images : array of image
46
+ You can input either path to the image, URL, or numpy array.
47
+ align : {"center", "top", "bottom", "left", "right"}, default: "center"
48
+ The alignment of the images.
49
+ scale : float, default: 1
50
+ The scale of the images.
51
+ spacing : float, default: 0.1
52
+ The spacing between images, a value between 0 and 1,
53
+ relative to the image container size.
54
+ resize : int or tuple, default: None
55
+ The size to resize the images.
56
+
57
+ Examples
58
+ --------
59
+
60
+ .. plot::
61
+ :context: close-figs
62
+
63
+ >>> import numpy as np
64
+ >>> import marsilea as ma
65
+ >>> c = ma.ZeroWidth(height=2)
66
+ >>> c.add_right(
67
+ ... ma.plotter.Image(
68
+ ... [
69
+ ... "https://www.iconfinder.com/icons/4375050/download/png/512",
70
+ ... "https://www.iconfinder.com/icons/8666426/download/png/512",
71
+ ... "https://www.iconfinder.com/icons/652581/download/png/512",
72
+ ... ],
73
+ ... align="right",
74
+ ... ),
75
+ ... pad=0.1,
76
+ ... )
77
+ >>> c.add_right(
78
+ ... ma.plotter.Labels(["Python", "Rust", "JavaScript"], fontsize=20), pad=0.1
79
+ ... )
80
+ >>> c.render()
81
+
82
+ """
83
+
84
+ def __init__(
85
+ self,
86
+ images,
87
+ align="center",
88
+ scale=1,
89
+ spacing=0.1,
90
+ resize=None,
91
+ ):
92
+ self.images_mapper = {}
93
+
94
+ for i, img in enumerate(images):
95
+ if isinstance(img, str):
96
+ # Read from URL
97
+ if img.startswith("http") or img.startswith("https"):
98
+ img = imread(_cache_remote(img))
99
+ else:
100
+ # Read from string path
101
+ img = imread(img)
102
+ # Read from Path
103
+ elif isinstance(img, Path):
104
+ img = imread(img)
105
+ else:
106
+ # Read from array interface
107
+ img = np.asarray(img)
108
+
109
+ self.images_mapper[i] = img
110
+
111
+ self.images_codes = np.asarray(list(self.images_mapper.keys()))
112
+
113
+ self.images = images
114
+ self.align = align
115
+ self.scale = scale
116
+ if not 0 <= spacing <= 1:
117
+ raise ValueError("spacing should be between 0 and 1")
118
+ self.spacing = spacing
119
+ if resize is not None:
120
+ if isinstance(resize, Number):
121
+ resize = (int(resize), int(resize))
122
+ for i, img in self.images_mapper.items():
123
+ self.images_mapper[i] = img.resize(resize, PILImage.Resampling.LANCZOS)
124
+ for i, img in self.images_mapper.items():
125
+ self.images_mapper[i] = np.asarray(img)
126
+ self.set_data(self.images_codes)
127
+
128
+ def _get_bbox_imges(
129
+ self, ax, imgs, scale=1, align="center", ax_height=None, ax_width=None
130
+ ):
131
+ locs = np.linspace(0, 1, len(imgs) + 1)
132
+ slot_size = locs[1] - locs[0]
133
+ locs = locs[:-1] + slot_size * self.spacing / 2
134
+
135
+ xmin, ymin = ax.transAxes.transform((0, 0))
136
+ xmax, ymax = ax.transAxes.transform((1, 1))
137
+
138
+ if ax_width is None or ax_width == 0:
139
+ ax_width = xmax - xmin
140
+ if ax_height is None or ax_height == 0:
141
+ ax_height = ymax - ymin
142
+
143
+ base_dpi = ax.get_figure().get_dpi()
144
+
145
+ bbox_images = []
146
+ imgaes_sizes = []
147
+
148
+ if self.is_body:
149
+ fit_width = ax_width / len(imgs) * (1 - self.spacing)
150
+ for loc, img in zip(locs, imgs):
151
+ height, width = img.shape[:2]
152
+ fit_height = height / width * fit_width
153
+
154
+ fit_scale_width = fit_width * scale
155
+ fit_scale_height = fit_height * scale
156
+
157
+ offset = (fit_width - fit_scale_width) / 2 / ax_width
158
+ loc += offset
159
+
160
+ if align == "top":
161
+ loc_y = 1 - fit_scale_height / ax_height
162
+ elif align == "bottom":
163
+ loc_y = 0
164
+ else:
165
+ loc_y = 0.5 - fit_scale_height / 2 / ax_height
166
+
167
+ def img_bbox(renderer, loc, loc_y, width, height, base_dpi):
168
+ factor = renderer.dpi / base_dpi
169
+ x0, y0 = ax.transData.transform((loc, loc_y))
170
+ return Bbox.from_bounds(x0, y0, width * factor, height * factor)
171
+
172
+ memorized_img_bbox = partial(
173
+ img_bbox,
174
+ loc=loc,
175
+ loc_y=loc_y,
176
+ width=fit_scale_width,
177
+ height=fit_scale_height,
178
+ base_dpi=base_dpi,
179
+ )
180
+
181
+ i1 = BboxImage(memorized_img_bbox, data=img)
182
+ bbox_images.append(i1)
183
+ imgaes_sizes.append(fit_scale_height)
184
+ else:
185
+ fit_height = ax_height / len(imgs) * (1 - self.spacing)
186
+ for loc, img in zip(locs, imgs[::-1]):
187
+ height, width = img.shape[:2]
188
+ fit_width = width / height * fit_height
189
+
190
+ fit_scale_width = fit_width * scale
191
+ fit_scale_height = fit_height * scale
192
+
193
+ offset = (fit_height - fit_scale_height) / 2 / ax_height
194
+ loc += offset
195
+
196
+ if align == "right":
197
+ loc_x = 1 - fit_scale_width / ax_width
198
+ elif align == "left":
199
+ loc_x = 0
200
+ else:
201
+ loc_x = 0.5 - fit_scale_width / 2 / ax_width
202
+
203
+ def img_bbox(renderer, loc_x, loc, width, height, base_dpi):
204
+ factor = renderer.dpi / base_dpi
205
+ x0, y0 = ax.transData.transform((loc_x, loc))
206
+ return Bbox.from_bounds(x0, y0, width * factor, height * factor)
207
+
208
+ memorized_img_bbox = partial(
209
+ img_bbox,
210
+ loc_x=loc_x,
211
+ loc=loc,
212
+ width=fit_scale_width,
213
+ height=fit_scale_height,
214
+ base_dpi=base_dpi,
215
+ )
216
+
217
+ i1 = BboxImage(memorized_img_bbox, data=img)
218
+ bbox_images.append(i1)
219
+ imgaes_sizes.append(fit_scale_width)
220
+
221
+ return bbox_images, max(imgaes_sizes)
222
+
223
+ def render_ax(self, spec):
224
+ ax = spec.ax
225
+ imgs = [self.images_mapper[i] for i in spec.data]
226
+ bbox_images, _ = self._get_bbox_imges(ax, imgs)
227
+ for i in bbox_images:
228
+ ax.add_artist(i)
229
+ ax.set_axis_off()
230
+
231
+ def get_canvas_size(
232
+ self, figure, main_height=None, main_width=None, **kwargs
233
+ ) -> float:
234
+ ax = figure.add_subplot(111)
235
+ imgs = [self.images_mapper[i] for i in self.images_codes]
236
+ _, size = self._get_bbox_imges(
237
+ ax, imgs, ax_width=main_width, ax_height=main_height
238
+ )
239
+ ax.remove()
240
+ return size
241
+
242
+
243
+ # ======== EMOJI Plotter ========
244
+
245
+ TWEMOJI_CDN = "https://cdn.jsdelivr.net/gh/twitter/twemoji/assets/72x72/"
246
+
247
+
248
+ class Emoji(Image):
249
+ """Have fun with emoji images
250
+
251
+ The emoji images are from `twemoji <https://twemoji.twitter.com/>`_.
252
+
253
+ You can will all twemoji from `here <https://twemoji-cheatsheet.vercel.app/>`_
254
+
255
+ Parameters
256
+ ----------
257
+ codes : array of str
258
+ The emoji codes. You can input either unicode or short code.
259
+ lang : str, default: "en"
260
+ The language of the emoji.
261
+ scale : float, default: 1
262
+ The scale of the emoji.
263
+ spacing : float, default: 0.1
264
+ The spacing between emoji, a value between 0 and 1,
265
+ relative to the emoji container size.
266
+
267
+ Examples
268
+ --------
269
+
270
+ .. plot::
271
+
272
+ >>> import marsilea as ma
273
+ >>> c = ma.ZeroHeight(width=2)
274
+ >>> c.add_top(ma.plotter.Emoji("😆😆🤣😂😉😇🐍🦀🦄"))
275
+ >>> c.render()
276
+
277
+ """
278
+
279
+ def __init__(self, codes, lang="en", scale=1, spacing=0.1, **kwargs):
280
+ try:
281
+ import emoji
282
+ except ImportError:
283
+ raise ImportError("Required emoji, try `pip install emoji`.")
284
+
285
+ urls = []
286
+ for i in codes:
287
+ i = emoji.emojize(i, language=lang)
288
+ if not emoji.is_emoji(i):
289
+ raise ValueError(f"{i} is not a valid emoji")
290
+ c = f"{ord(i):X}".lower()
291
+ urls.append(f"{TWEMOJI_CDN}{c}.png")
292
+
293
+ super().__init__(urls, scale=scale, spacing=spacing, **kwargs)
marsilea/plotter/mesh.py CHANGED
@@ -103,7 +103,7 @@ class ColorMesh(MeshBase):
103
103
 
104
104
  >>> import marsilea as ma
105
105
  >>> from marsilea.plotter import ColorMesh
106
- >>> _, ax = plt.subplots(figsize=(5, .5))
106
+ >>> _, ax = plt.subplots(figsize=(5, 0.5))
107
107
  >>> ColorMesh(np.arange(10), cmap="Blues").render(ax)
108
108
 
109
109
  .. plot::
@@ -111,13 +111,13 @@ class ColorMesh(MeshBase):
111
111
 
112
112
  >>> data = np.random.randn(10, 8)
113
113
  >>> h = ma.Heatmap(data)
114
- >>> h.hsplit(cut=[5])
114
+ >>> h.cut_rows(cut=[5])
115
115
  >>> h.add_dendrogram("left")
116
116
  >>> cmap1, cmap2 = "Purples", "Greens"
117
- >>> colors1 = ColorMesh(np.arange(10)+1, cmap=cmap1, label=cmap1, annot=True)
118
- >>> colors2 = ColorMesh(np.arange(10)+1, cmap=cmap2, label=cmap2)
119
- >>> h.add_right(colors1, size=.2, pad=.05)
120
- >>> h.add_right(colors2, size=.2, pad=.05)
117
+ >>> colors1 = ColorMesh(np.arange(10) + 1, cmap=cmap1, label=cmap1, annot=True)
118
+ >>> colors2 = ColorMesh(np.arange(10) + 1, cmap=cmap2, label=cmap2)
119
+ >>> h.add_right(colors1, size=0.2, pad=0.05)
120
+ >>> h.add_right(colors2, size=0.2, pad=0.05)
121
121
  >>> h.render()
122
122
 
123
123
 
@@ -280,7 +280,7 @@ class Colors(MeshBase):
280
280
 
281
281
  >>> import marsilea as ma
282
282
  >>> from marsilea.plotter import Colors
283
- >>> _, ax = plt.subplots(figsize=(5, .5))
283
+ >>> _, ax = plt.subplots(figsize=(5, 0.5))
284
284
  >>> data = np.random.choice(["A", "B", "C"], 10)
285
285
  >>> Colors(data).render(ax)
286
286
 
@@ -288,10 +288,10 @@ class Colors(MeshBase):
288
288
  :context: close-figs
289
289
 
290
290
  >>> h = ma.Heatmap(np.random.randn(10, 8))
291
- >>> h.hsplit(cut=[5])
291
+ >>> h.cut_rows(cut=[5])
292
292
  >>> h.add_dendrogram("left")
293
293
  >>> color = Colors(data, label="Colors")
294
- >>> h.add_right(color, size=.2, pad=.05)
294
+ >>> h.add_right(color, size=0.2, pad=0.05)
295
295
  >>> h.render()
296
296
 
297
297
 
@@ -467,7 +467,7 @@ class SizedMesh(MeshBase):
467
467
 
468
468
  >>> import marsilea as ma
469
469
  >>> from marsilea.plotter import SizedMesh
470
- >>> _, ax = plt.subplots(figsize=(5, .5))
470
+ >>> _, ax = plt.subplots(figsize=(5, 0.5))
471
471
  >>> size, color = np.random.rand(1, 10), np.random.rand(1, 10)
472
472
  >>> SizedMesh(size, color).render(ax)
473
473
 
@@ -475,10 +475,10 @@ class SizedMesh(MeshBase):
475
475
  :context: close-figs
476
476
 
477
477
  >>> h = ma.Heatmap(np.random.randn(10, 8))
478
- >>> h.hsplit(cut=[5])
478
+ >>> h.cut_rows(cut=[5])
479
479
  >>> h.add_dendrogram("left")
480
480
  >>> mesh = SizedMesh(size, color, marker="*", label="SizedMesh")
481
- >>> h.add_right(mesh, size=.2, pad=.05)
481
+ >>> h.add_right(mesh, size=0.2, pad=0.05)
482
482
  >>> h.render()
483
483
 
484
484
 
@@ -668,6 +668,8 @@ class MarkerMesh(MeshBase):
668
668
  See :mod:`matplotlib.markers`
669
669
  size : int
670
670
  The of marker in fontsize unit
671
+ frameon : bool
672
+ Whether to draw the border of the plot
671
673
  label : str
672
674
  The label of the plot, only show when added to the side plot
673
675
  label_loc : {'top', 'bottom', 'left', 'right'}
@@ -698,6 +700,7 @@ class MarkerMesh(MeshBase):
698
700
  color="black",
699
701
  marker="*",
700
702
  size=35,
703
+ frameon=False,
701
704
  label=None,
702
705
  label_loc=None,
703
706
  label_props=None,
@@ -706,9 +709,10 @@ class MarkerMesh(MeshBase):
706
709
  self.set_data(np.asarray(data))
707
710
  self.color = color
708
711
  self.marker = marker
712
+ self.marker_size = size
713
+ self.frameon = frameon
709
714
  self.set_label(label, label_loc, label_props)
710
715
  self.kwargs = kwargs
711
- self.marker_size = size
712
716
 
713
717
  def get_legends(self):
714
718
  return CatLegend(
@@ -737,6 +741,8 @@ class MarkerMesh(MeshBase):
737
741
  ax.set_xlim(0, xticks[-1] + 0.5)
738
742
  ax.set_ylim(0, yticks[-1] + 0.5)
739
743
  ax.invert_yaxis()
744
+ if not self.frameon:
745
+ ax.set_axis_off()
740
746
 
741
747
 
742
748
  class TextMesh(MeshBase):
@@ -748,6 +754,8 @@ class TextMesh(MeshBase):
748
754
  The text to draw
749
755
  color : color
750
756
  The color of the text
757
+ frameon : bool
758
+ Whether to draw the border of the plot
751
759
  label : str
752
760
  The label of the plot, only show when added to the side plot
753
761
  label_loc : {'top', 'bottom', 'left', 'right'}
@@ -765,6 +773,7 @@ class TextMesh(MeshBase):
765
773
  self,
766
774
  texts,
767
775
  color="black",
776
+ frameon=False,
768
777
  label=None,
769
778
  label_loc=None,
770
779
  label_props=None,
@@ -772,6 +781,7 @@ class TextMesh(MeshBase):
772
781
  ):
773
782
  self.set_data(self.data_validator(texts))
774
783
  self.color = color
784
+ self.frameon = frameon
775
785
  self.set_label(label, label_loc, label_props)
776
786
  self.kwargs = kwargs
777
787
 
@@ -797,3 +807,5 @@ class TextMesh(MeshBase):
797
807
  ax.set_xlim(0, xticks[-1] + 0.5)
798
808
  ax.set_ylim(0, yticks[-1] + 0.5)
799
809
  ax.invert_yaxis()
810
+ if not self.frameon:
811
+ ax.set_axis_off()
marsilea/plotter/text.py CHANGED
@@ -10,6 +10,7 @@ import warnings
10
10
  from dataclasses import dataclass
11
11
  from matplotlib.axes import Axes
12
12
  from matplotlib.colors import is_color_like
13
+ from matplotlib.lines import Line2D
13
14
  from matplotlib.patches import Rectangle
14
15
  from matplotlib.text import Text
15
16
  from typing import List, Iterable
@@ -389,7 +390,7 @@ class _LabelBase(RenderPlan):
389
390
  ax.remove()
390
391
  return np.max(sizes) / figure.get_dpi()
391
392
 
392
- def get_canvas_size(self, figure):
393
+ def get_canvas_size(self, figure, **kwargs):
393
394
  self.texts_size = self.silent_render(figure, expand=self.get_expand())
394
395
  return self.texts_size + self.padding / 72
395
396
 
@@ -545,7 +546,7 @@ class AnnoLabels(_LabelBase):
545
546
  p.update_params(self._user_params)
546
547
  return p
547
548
 
548
- def get_canvas_size(self, figure):
549
+ def get_canvas_size(self, figure, **kwargs):
549
550
  expand = self.get_expand()
550
551
  canvas_size = self.silent_render(figure, expand)
551
552
  size = canvas_size + self.pointer_size
@@ -651,7 +652,7 @@ class Labels(_LabelBase):
651
652
  >>> import marsilea as ma
652
653
  >>> from marsilea.plotter import Labels
653
654
  >>> h = ma.Heatmap(np.random.randn(20, 20))
654
- >>> h.add_right(Labels(labels, text_props={'color': colors}))
655
+ >>> h.add_right(Labels(labels, text_props={"color": colors}))
655
656
  >>> h.render()
656
657
 
657
658
  """
@@ -778,10 +779,10 @@ class Title(_LabelBase):
778
779
  >>> matrix = np.random.randn(15, 10)
779
780
  >>> h = ma.Heatmap(matrix)
780
781
  >>> for align in ["left", "right", "center"]:
781
- ... title = Title(f'Title align={align}', align=align)
782
+ ... title = Title(f"Title align={align}", align=align)
782
783
  ... h.add_top(title)
783
784
  >>> for align in ["top", "bottom", "center"]:
784
- ... title = Title(f'Title align={align}', align=align)
785
+ ... title = Title(f"Title align={align}", align=align)
785
786
  ... h.add_left(title)
786
787
  >>> h.render()
787
788
 
@@ -887,6 +888,7 @@ class _ChunkBase(_LabelBase):
887
888
  align=None,
888
889
  props=None,
889
890
  padding=2,
891
+ underline=False,
890
892
  bordercolor=None,
891
893
  borderwidth=None,
892
894
  borderstyle=None,
@@ -910,6 +912,7 @@ class _ChunkBase(_LabelBase):
910
912
  props = [props for _ in range(n)]
911
913
  self.props = props
912
914
 
915
+ self.underline = underline
913
916
  if is_color_like(bordercolor):
914
917
  bordercolor = [bordercolor for _ in range(n)]
915
918
  if bordercolor is not None:
@@ -924,6 +927,12 @@ class _ChunkBase(_LabelBase):
924
927
  borderstyle = [borderstyle for _ in range(n)]
925
928
  self.borderstyle = borderstyle
926
929
 
930
+ if self.underline:
931
+ if self.bordercolor is None:
932
+ self.bordercolor = np.asarray(["black" for _ in range(n)])
933
+ if self.borderwidth is None:
934
+ self.borderwidth = np.asarray([3 for _ in range(n)])
935
+
927
936
  self._draw_bg = (self.fill_colors is not None) or (self.bordercolor is not None)
928
937
  self.text_pad = 0
929
938
 
@@ -941,6 +950,13 @@ class _ChunkBase(_LabelBase):
941
950
  "bottom": 0,
942
951
  }
943
952
 
953
+ default_underline = {
954
+ "right": [(0, 0), (0, 1)],
955
+ "left": [(1, 1), (1, 0)],
956
+ "top": [(0, 1), (0, 0)],
957
+ "bottom": [(0, 1), (1, 1)],
958
+ }
959
+
944
960
  def get_alignment(self, ha, va, rotation):
945
961
  if rotation in {90, -90}:
946
962
  ha, va = va, ha # swap the alignment
@@ -1005,16 +1021,27 @@ class _ChunkBase(_LabelBase):
1005
1021
  if self._draw_bg:
1006
1022
  if bgcolor is None:
1007
1023
  bgcolor = "white"
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
- )
1024
+ if not self.underline:
1025
+ rect = Rectangle(
1026
+ (0, 0),
1027
+ 1,
1028
+ 1,
1029
+ facecolor=bgcolor,
1030
+ edgecolor=bc,
1031
+ linewidth=lw,
1032
+ linestyle=ls,
1033
+ transform=ax.transAxes,
1034
+ )
1035
+ else:
1036
+ xdata, ydata = self.default_underline[self.side]
1037
+ rect = Line2D(
1038
+ xdata,
1039
+ ydata,
1040
+ color=bc,
1041
+ linewidth=lw,
1042
+ linestyle=ls,
1043
+ transform=ax.transAxes,
1044
+ )
1018
1045
  ax.add_artist(rect)
1019
1046
  fontdict.setdefault("color", self.get_text_color(bgcolor))
1020
1047
 
@@ -1069,10 +1096,10 @@ class Chunk(_ChunkBase):
1069
1096
  >>> from marsilea.plotter import Chunk
1070
1097
  >>> matrix = np.random.randn(20, 20)
1071
1098
  >>> h = ma.Heatmap(matrix)
1072
- >>> chunk = ['C1', 'C2', 'C3', 'C4']
1099
+ >>> chunk = ["C1", "C2", "C3", "C4"]
1073
1100
  >>> labels = np.random.choice(chunk, size=20)
1074
- >>> h.hsplit(labels=labels, order=chunk)
1075
- >>> h.add_right(Chunk(chunk, bordercolor="gray"), pad=.1)
1101
+ >>> h.group_rows(labels, order=chunk)
1102
+ >>> h.add_right(Chunk(chunk, bordercolor="gray"), pad=0.1)
1076
1103
  >>> h.add_dendrogram("left")
1077
1104
  >>> h.render()
1078
1105
 
@@ -1086,6 +1113,7 @@ class Chunk(_ChunkBase):
1086
1113
  align=None,
1087
1114
  props=None,
1088
1115
  padding=8,
1116
+ underline=False,
1089
1117
  bordercolor=None,
1090
1118
  borderwidth=None,
1091
1119
  borderstyle=None,
@@ -1100,6 +1128,7 @@ class Chunk(_ChunkBase):
1100
1128
  align=align,
1101
1129
  props=props,
1102
1130
  padding=padding,
1131
+ underline=underline,
1103
1132
  bordercolor=bordercolor,
1104
1133
  borderwidth=borderwidth,
1105
1134
  borderstyle=borderstyle,
@@ -1175,10 +1204,10 @@ class FixedChunk(_ChunkBase):
1175
1204
  >>> from marsilea.plotter import FixedChunk
1176
1205
  >>> matrix = np.random.randn(20, 20)
1177
1206
  >>> h = ma.Heatmap(matrix)
1178
- >>> chunk = ['C1', 'C2', 'C3', 'C4']
1207
+ >>> chunk = ["C1", "C2", "C3", "C4"]
1179
1208
  >>> labels = np.random.choice(chunk, size=20)
1180
- >>> h.hsplit(labels=labels, order=chunk)
1181
- >>> h.add_right(FixedChunk(chunk, bordercolor="gray"), pad=.1)
1209
+ >>> h.group_rows(labels, order=chunk)
1210
+ >>> h.add_right(FixedChunk(chunk, bordercolor="gray"), pad=0.1)
1182
1211
  >>> h.add_dendrogram("left")
1183
1212
  >>> h.render()
1184
1213
 
@@ -1188,12 +1217,18 @@ class FixedChunk(_ChunkBase):
1188
1217
  :context: close-figs
1189
1218
 
1190
1219
  >>> h = ma.Heatmap(matrix)
1191
- >>> chunk = ['C1', 'C2-1', 'C2-2', 'C4']
1220
+ >>> chunk = ["C1", "C2-1", "C2-2", "C4"]
1192
1221
  >>> labels = np.random.choice(chunk, size=20)
1193
- >>> h.hsplit(labels=labels, order=chunk)
1194
- >>> h.add_right(FixedChunk(chunk, bordercolor="gray"), pad=.1)
1195
- >>> h.add_right(FixedChunk(['C1', 'C2', 'C3'], fill_colors="red",
1196
- ... ratio=[1, 2, 1], ), pad=.1)
1222
+ >>> h.group_rows(labels, order=chunk)
1223
+ >>> h.add_right(FixedChunk(chunk, bordercolor="gray"), pad=0.1)
1224
+ >>> h.add_right(
1225
+ ... FixedChunk(
1226
+ ... ["C1", "C2", "C3"],
1227
+ ... fill_colors="red",
1228
+ ... ratio=[1, 2, 1],
1229
+ ... ),
1230
+ ... pad=0.1,
1231
+ ... )
1197
1232
  >>> h.render()
1198
1233
 
1199
1234
 
@@ -1208,6 +1243,7 @@ class FixedChunk(_ChunkBase):
1208
1243
  ratio=None,
1209
1244
  props=None,
1210
1245
  padding=8,
1246
+ underline=False,
1211
1247
  bordercolor=None,
1212
1248
  borderwidth=None,
1213
1249
  borderstyle=None,
@@ -1219,12 +1255,13 @@ class FixedChunk(_ChunkBase):
1219
1255
  super().__init__(
1220
1256
  texts,
1221
1257
  fill_colors,
1222
- align,
1223
- props,
1224
- padding,
1225
- bordercolor,
1226
- borderwidth,
1227
- borderstyle,
1258
+ align=align,
1259
+ props=props,
1260
+ padding=padding,
1261
+ underline=underline,
1262
+ bordercolor=bordercolor,
1263
+ borderwidth=borderwidth,
1264
+ borderstyle=borderstyle,
1228
1265
  **options,
1229
1266
  )
1230
1267
  if ratio is not None:
marsilea/utils.py CHANGED
@@ -2,7 +2,6 @@ import matplotlib as mpl
2
2
  import numpy as np
3
3
  from itertools import tee, islice
4
4
  from matplotlib import colors as mcolors
5
- from matplotlib.colors import Colormap
6
5
  from uuid import uuid4
7
6
 
8
7
  ECHARTS16 = [
@@ -70,7 +69,7 @@ def relative_luminance(color):
70
69
 
71
70
  def get_colormap(cmap):
72
71
  try:
73
- return mpl.cm.ColormapRegistry.get_cmap(cmap)
72
+ return mpl.colormaps.get_cmap(cmap)
74
73
  except AttributeError:
75
74
  try:
76
75
  return mpl.colormaps.get(cmap)