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/layout.py CHANGED
@@ -24,7 +24,8 @@ from .utils import _check_side
24
24
  # It's unlikely to return axes right after being added, the split operation
25
25
  # is unknown, this will create more than one axes.
26
26
 
27
- def _split(chunk_ratios, spacing=.05, group_ratios=None):
27
+
28
+ def _split(chunk_ratios, spacing=0.05, group_ratios=None):
28
29
  """Return the relative anchor point, in ratio"""
29
30
 
30
31
  ratios = np.asarray(chunk_ratios) / np.sum(chunk_ratios)
@@ -53,13 +54,13 @@ def _split(chunk_ratios, spacing=.05, group_ratios=None):
53
54
  if g == 1:
54
55
  gratio = ratios[start_ix]
55
56
  else:
56
- mr = ratios[start_ix:start_ix+g].sum()
57
- ms = spacing[start_ix:start_ix+g-1].sum()
57
+ mr = ratios[start_ix : start_ix + g].sum()
58
+ ms = spacing[start_ix : start_ix + g - 1].sum()
58
59
  gratio = mr + ms
59
60
 
60
61
  if i < last_ix:
61
- gspacing = spacing[start_ix+g-1]
62
- start_anchor += (gratio+gspacing)
62
+ gspacing = spacing[start_ix + g - 1]
63
+ start_anchor += gratio + gspacing
63
64
  anchors.append(start_anchor)
64
65
  else:
65
66
  start_anchor += gratio
@@ -116,7 +117,7 @@ class BaseCell:
116
117
 
117
118
  return cx, cy, cw, ch
118
119
 
119
- def hsplit(self, chunk_ratios, spacing=.05, group_ratios=None):
120
+ def hsplit(self, chunk_ratios, spacing=0.05, group_ratios=None):
120
121
  """
121
122
  Parameters
122
123
  ----------
@@ -128,12 +129,13 @@ class BaseCell:
128
129
  Regroup the split chunks
129
130
 
130
131
  """
131
- ratios, anchors = _split(chunk_ratios[::-1], spacing=spacing,
132
- group_ratios=group_ratios)
132
+ ratios, anchors = _split(
133
+ chunk_ratios[::-1], spacing=spacing, group_ratios=group_ratios
134
+ )
133
135
  self.h_ratios = ratios
134
136
  self.h_anchors = anchors
135
137
 
136
- def vsplit(self, chunk_ratios, spacing=.05, group_ratios=None):
138
+ def vsplit(self, chunk_ratios, spacing=0.05, group_ratios=None):
137
139
  """
138
140
  Parameters
139
141
  ----------
@@ -145,8 +147,9 @@ class BaseCell:
145
147
  Regroup the split chunks
146
148
 
147
149
  """
148
- ratios, anchors = _split(chunk_ratios, spacing=spacing,
149
- group_ratios=group_ratios)
150
+ ratios, anchors = _split(
151
+ chunk_ratios, spacing=spacing, group_ratios=group_ratios
152
+ )
150
153
  self.v_ratios = ratios
151
154
  self.v_anchors = anchors
152
155
 
@@ -204,7 +207,7 @@ class GridCell(BaseCell):
204
207
 
205
208
  def get_cell_size(self):
206
209
  """Get width, height of a cell"""
207
- if self.side in ['top', 'bottom']:
210
+ if self.side in ["top", "bottom"]:
208
211
  return self.attach.width, self.size
209
212
  else:
210
213
  return self.size, self.attach.height
@@ -228,8 +231,10 @@ class _MarginMixin:
228
231
  if len(margin) == 4:
229
232
  self.margin = Margin(*margin)
230
233
  else:
231
- msg = "margin must be one number or a tuple with 4 numbers" \
232
- "(top, right, bottom, left)"
234
+ msg = (
235
+ "margin must be one number or a tuple with 4 numbers"
236
+ "(top, right, bottom, left)"
237
+ )
233
238
  raise ValueError(msg)
234
239
 
235
240
  def get_margin_w(self):
@@ -269,14 +274,14 @@ class CrossLayout(_MarginMixin):
269
274
 
270
275
  """
271
276
 
272
- def __init__(self, name, width, height,
273
- init_main=True, projection=None,
274
- margin=.2):
275
-
277
+ def __init__(
278
+ self, name, width, height, init_main=True, projection=None, margin=0.2
279
+ ):
276
280
  self._legend_ax_name = None
277
- self.main_cell = MainCell(name, width, height, is_canvas=init_main,
278
- projection=projection)
279
- self._side_cells = {'top': [], 'bottom': [], 'left': [], 'right': []}
281
+ self.main_cell = MainCell(
282
+ name, width, height, is_canvas=init_main, projection=projection
283
+ )
284
+ self._side_cells = {"top": [], "bottom": [], "left": [], "right": []}
280
285
  self.cells: Dict[str, BaseCell] = {name: self.main_cell}
281
286
  self._pads = {}
282
287
 
@@ -294,7 +299,7 @@ class CrossLayout(_MarginMixin):
294
299
  raise ValueError(f"Axes with name {name} not exist")
295
300
  return cell
296
301
 
297
- def add_ax(self, side, name, size, pad=0., projection=None):
302
+ def add_ax(self, side, name, size, pad=0.0, projection=None):
298
303
  """Add an axes to the layout
299
304
 
300
305
  Parameters
@@ -313,11 +318,15 @@ class CrossLayout(_MarginMixin):
313
318
  if self.cells.get(name) is not None:
314
319
  raise DuplicateName(name)
315
320
 
316
- new_cell = GridCell(name=name, side=side, size=size,
317
- attach=self.main_cell,
318
- projection=projection)
321
+ new_cell = GridCell(
322
+ name=name,
323
+ side=side,
324
+ size=size,
325
+ attach=self.main_cell,
326
+ projection=projection,
327
+ )
319
328
  # Add pad before add canvas
320
- if pad > 0.:
329
+ if pad > 0.0:
321
330
  self.add_pad(side, pad)
322
331
  self._side_cells[side].append(new_cell)
323
332
  self.cells[name] = new_cell
@@ -335,8 +344,13 @@ class CrossLayout(_MarginMixin):
335
344
 
336
345
  """
337
346
  _check_side(side)
338
- new_pad = GridCell(name=uuid4().hex, side=side, size=size,
339
- is_canvas=False, attach=self.main_cell)
347
+ new_pad = GridCell(
348
+ name=uuid4().hex,
349
+ side=side,
350
+ size=size,
351
+ is_canvas=False,
352
+ attach=self.main_cell,
353
+ )
340
354
  self._side_cells[side].append(new_pad)
341
355
 
342
356
  def remove_ax(self, name):
@@ -356,7 +370,7 @@ class CrossLayout(_MarginMixin):
356
370
  def get_main_ax(self):
357
371
  return self.main_cell.ax
358
372
 
359
- def add_legend_ax(self, side, size, pad=0.):
373
+ def add_legend_ax(self, side, size, pad=0.0):
360
374
  """Add a legend axes
361
375
 
362
376
  Parameters
@@ -370,8 +384,7 @@ class CrossLayout(_MarginMixin):
370
384
 
371
385
  """
372
386
  # TODO: Ensure the legend axes always add at last
373
- self._legend_ax_name = "-".join(
374
- [self.main_cell.name, "legend", uuid4().hex])
387
+ self._legend_ax_name = "-".join([self.main_cell.name, "legend", uuid4().hex])
375
388
  self.add_ax(side, self._legend_ax_name, size, pad=pad)
376
389
 
377
390
  def get_legend_ax(self):
@@ -385,19 +398,15 @@ class CrossLayout(_MarginMixin):
385
398
  legend_cell = self._get_cell(self._legend_ax_name)
386
399
  legend_cell.size = size
387
400
 
388
- def vsplit(self, name, chunk_ratios,
389
- spacing=.05, group_ratios=None):
401
+ def vsplit(self, name, chunk_ratios, spacing=0.05, group_ratios=None):
390
402
  cell = self._get_cell(name)
391
403
  cell.is_split = True
392
- cell.vsplit(chunk_ratios, spacing=spacing,
393
- group_ratios=group_ratios)
404
+ cell.vsplit(chunk_ratios, spacing=spacing, group_ratios=group_ratios)
394
405
 
395
- def hsplit(self, name, chunk_ratios,
396
- spacing=.05, group_ratios=None):
406
+ def hsplit(self, name, chunk_ratios, spacing=0.05, group_ratios=None):
397
407
  cell = self._get_cell(name)
398
408
  cell.is_split = True
399
- cell.hsplit(chunk_ratios, spacing=spacing,
400
- group_ratios=group_ratios)
409
+ cell.hsplit(chunk_ratios, spacing=spacing, group_ratios=group_ratios)
401
410
 
402
411
  def is_split(self, name):
403
412
  """Query if a cell is split"""
@@ -408,14 +417,20 @@ class CrossLayout(_MarginMixin):
408
417
 
409
418
  def get_bbox_width(self):
410
419
  """Get the bbox width in inches"""
411
- box_w = self.main_cell.width + self.get_side_size(
412
- 'left') + self.get_side_size('right')
420
+ box_w = (
421
+ self.main_cell.width
422
+ + self.get_side_size("left")
423
+ + self.get_side_size("right")
424
+ )
413
425
  return box_w
414
426
 
415
427
  def get_bbox_height(self):
416
428
  """Get the bbox height in inches"""
417
- box_h = self.main_cell.height + self.get_side_size(
418
- 'top') + self.get_side_size('bottom')
429
+ box_h = (
430
+ self.main_cell.height
431
+ + self.get_side_size("top")
432
+ + self.get_side_size("bottom")
433
+ )
419
434
  return box_h
420
435
 
421
436
  def get_bbox_size(self):
@@ -428,13 +443,12 @@ class CrossLayout(_MarginMixin):
428
443
  if self.is_composite:
429
444
  return self.get_bbox_size()
430
445
  w, h = self.get_bbox_size()
431
- return (w + self.get_margin_w(),
432
- h + self.get_margin_h())
446
+ return (w + self.get_margin_w(), h + self.get_margin_h())
433
447
 
434
448
  else:
435
449
  ox, oy = self.anchor
436
- fig_w = ox + self.get_main_width() + self.get_side_size('right')
437
- fig_h = ox + self.get_main_height() + self.get_side_size('top')
450
+ fig_w = ox + self.get_main_width() + self.get_side_size("right")
451
+ fig_h = ox + self.get_main_height() + self.get_side_size("top")
438
452
  if self.is_composite:
439
453
  return fig_w, fig_h
440
454
  return fig_w + self.get_margin_w(), fig_h + self.get_margin_h()
@@ -442,8 +456,8 @@ class CrossLayout(_MarginMixin):
442
456
  def get_main_anchor(self):
443
457
  """Get the main anchor point"""
444
458
  if self.anchor is None:
445
- x = self.get_side_size('left')
446
- y = self.get_side_size('bottom')
459
+ x = self.get_side_size("left")
460
+ y = self.get_side_size("bottom")
447
461
  if self.is_composite:
448
462
  return x, y
449
463
  x += self.margin.bottom
@@ -534,10 +548,14 @@ class CrossLayout(_MarginMixin):
534
548
  ax = figure.add_axes(ax_rect, projection=c.projection)
535
549
  c.set_ax(ax)
536
550
  if _debug:
537
- _debug_ax(ax, side=c.side,
538
- text=f"{c.name}{c.get_cell_size()}")
539
-
540
- def freeze(self, figure=None, scale=1, _debug=False, ):
551
+ _debug_ax(ax, side=c.side, text=f"{c.name}{c.get_cell_size()}")
552
+
553
+ def freeze(
554
+ self,
555
+ figure=None,
556
+ scale=1,
557
+ _debug=False,
558
+ ):
541
559
  """Freeze the current layout and draw on figure
542
560
 
543
561
  Freeze is safe to be called multiple times, it will update
@@ -598,12 +616,12 @@ def _debug_ax(ax, side, text=None):
598
616
  close_ticks(ax)
599
617
  options = dict(transform=ax.transAxes, va="center", ha="center")
600
618
  if side == "left":
601
- options['rotation'] = 90
619
+ options["rotation"] = 90
602
620
  elif side == "right":
603
- options['rotation'] = -90
621
+ options["rotation"] = -90
604
622
 
605
623
  if text is not None:
606
- ax.text(.5, .5, text, **options)
624
+ ax.text(0.5, 0.5, text, **options)
607
625
 
608
626
 
609
627
  def _remove_axes(ax: Axes | List[Axes]):
@@ -617,10 +635,16 @@ def _remove_axes(ax: Axes | List[Axes]):
617
635
 
618
636
 
619
637
  def close_ticks(ax):
620
- ax.tick_params(bottom=False, top=False, left=False, right=False,
621
- labelbottom=False, labeltop=False,
622
- labelleft=False, labelright=False,
623
- )
638
+ ax.tick_params(
639
+ bottom=False,
640
+ top=False,
641
+ left=False,
642
+ right=False,
643
+ labelbottom=False,
644
+ labeltop=False,
645
+ labelleft=False,
646
+ labelright=False,
647
+ )
624
648
 
625
649
 
626
650
  @dataclass
@@ -646,14 +670,19 @@ class CompositeCrossLayout(_MarginMixin):
646
670
  The main cross layout
647
671
 
648
672
  """
673
+
649
674
  figure = None
650
675
 
651
676
  def __init__(self, main_layout, margin=0) -> None:
652
677
  self.main_layout = self._reset_layout(main_layout)
653
678
  self.main_cell_height = self.main_layout.get_main_height()
654
679
  self.main_cell_width = self.main_layout.get_main_width()
655
- self._side_layouts: Dict[str, List[CrossLayout]] = \
656
- {"top": [], "bottom": [], "right": [], "left": []}
680
+ self._side_layouts: Dict[str, List[CrossLayout]] = {
681
+ "top": [],
682
+ "bottom": [],
683
+ "right": [],
684
+ "left": [],
685
+ }
657
686
  self._legend_axes = None
658
687
  self.layouts = {self.main_layout.main_cell.name: self.main_layout}
659
688
  self.set_margin(margin)
@@ -674,10 +703,9 @@ class CompositeCrossLayout(_MarginMixin):
674
703
  width, height = other, self.main_cell_height
675
704
  else:
676
705
  width, height = self.main_cell_height, other
677
- other = CrossLayout(name=uuid4().hex,
678
- width=width,
679
- height=height,
680
- init_main=False)
706
+ other = CrossLayout(
707
+ name=uuid4().hex, width=width, height=height, init_main=False
708
+ )
681
709
  other.is_composite = True
682
710
  self._side_layouts[side].append(other)
683
711
  elif isinstance(other, CrossLayout):
@@ -691,12 +719,12 @@ class CompositeCrossLayout(_MarginMixin):
691
719
  raise TypeError(f"Cannot append object type of {type(other)}")
692
720
 
693
721
  def __truediv__(self, other: CrossLayout):
694
- self.append('bottom', other)
722
+ self.append("bottom", other)
695
723
 
696
724
  def __add__(self, other: CrossLayout):
697
- self.append('right', other)
725
+ self.append("right", other)
698
726
 
699
- def add_legend_ax(self, side, size, pad=0.):
727
+ def add_legend_ax(self, side, size, pad=0.0):
700
728
  """Extend the layout
701
729
 
702
730
  This is used to draw legends after concatenation
@@ -722,18 +750,16 @@ class CompositeCrossLayout(_MarginMixin):
722
750
  other_size = []
723
751
  size += self.main_layout.get_side_size(side)
724
752
  if side in ["left", "right"]:
725
-
726
753
  for g in self._side_layouts.get(side):
727
754
  size += g.get_bbox_width()
728
755
 
729
- for g in self._side_layouts['top'] + self._side_layouts['bottom']:
756
+ for g in self._side_layouts["top"] + self._side_layouts["bottom"]:
730
757
  other_size.append(g.get_side_size(side))
731
758
  else:
732
-
733
759
  for g in self._side_layouts.get(side):
734
760
  size += g.get_bbox_height()
735
761
 
736
- for g in self._side_layouts['left'] + self._side_layouts['right']:
762
+ for g in self._side_layouts["left"] + self._side_layouts["right"]:
737
763
  other_size.append(g.get_side_size(side))
738
764
 
739
765
  other_size = np.max(other_size, initial=0)
@@ -744,12 +770,18 @@ class CompositeCrossLayout(_MarginMixin):
744
770
  return max(size, other_size) + legend_size
745
771
 
746
772
  def get_bbox_width(self):
747
- return self.main_cell_width + self.get_side_size('left') + \
748
- self.get_side_size('right')
773
+ return (
774
+ self.main_cell_width
775
+ + self.get_side_size("left")
776
+ + self.get_side_size("right")
777
+ )
749
778
 
750
779
  def get_bbox_height(self):
751
- return self.main_cell_height + self.get_side_size('top') + \
752
- self.get_side_size('bottom')
780
+ return (
781
+ self.main_cell_height
782
+ + self.get_side_size("top")
783
+ + self.get_side_size("bottom")
784
+ )
753
785
 
754
786
  def get_bbox_size(self):
755
787
  """Get the minimum figsize that fits all layouts inside"""
@@ -762,8 +794,8 @@ class CompositeCrossLayout(_MarginMixin):
762
794
  return fig_w, fig_h
763
795
 
764
796
  def get_main_anchor(self):
765
- x = self.get_side_size('left') + self.margin.left
766
- y = self.get_side_size('bottom') + self.margin.bottom
797
+ x = self.get_side_size("left") + self.margin.left
798
+ y = self.get_side_size("bottom") + self.margin.bottom
767
799
  return x, y
768
800
 
769
801
  def set_anchor(self, anchor):
@@ -790,39 +822,37 @@ class CompositeCrossLayout(_MarginMixin):
790
822
 
791
823
  # The left and right share the y
792
824
  offset_x = mx - self.main_layout.get_side_size("left")
793
- for g in self._side_layouts['left']:
794
- offset_x -= (g.get_side_size('right') + g.get_main_width())
825
+ for g in self._side_layouts["left"]:
826
+ offset_x -= g.get_side_size("right") + g.get_main_width()
795
827
  g.set_anchor((offset_x, my))
796
828
  g.set_figsize(figsize)
797
829
  g.freeze(figure, _debug=_debug)
798
- offset_x -= g.get_side_size('left')
830
+ offset_x -= g.get_side_size("left")
799
831
 
800
- offset_x = (mx + self.main_cell_width +
801
- self.main_layout.get_side_size('right'))
802
- for g in self._side_layouts['right']:
803
- offset_x += g.get_side_size('left')
832
+ offset_x = mx + self.main_cell_width + self.main_layout.get_side_size("right")
833
+ for g in self._side_layouts["right"]:
834
+ offset_x += g.get_side_size("left")
804
835
  g.set_anchor((offset_x, my))
805
836
  g.set_figsize(figsize)
806
837
  g.freeze(figure, _debug=_debug)
807
- offset_x += (g.get_main_width() + g.get_side_size('right'))
838
+ offset_x += g.get_main_width() + g.get_side_size("right")
808
839
 
809
840
  # The top and bottom share the x
810
- offset_y = my - self.main_layout.get_side_size('bottom')
811
- for g in self._side_layouts['bottom']:
812
- offset_y -= (g.get_side_size('top') + g.get_main_height())
841
+ offset_y = my - self.main_layout.get_side_size("bottom")
842
+ for g in self._side_layouts["bottom"]:
843
+ offset_y -= g.get_side_size("top") + g.get_main_height()
813
844
  g.set_anchor((mx, offset_y))
814
845
  g.set_figsize(figsize)
815
846
  g.freeze(figure, _debug=_debug)
816
- offset_y -= g.get_side_size('bottom')
847
+ offset_y -= g.get_side_size("bottom")
817
848
 
818
- offset_y = (my + self.main_cell_height +
819
- self.main_layout.get_side_size('top'))
820
- for g in self._side_layouts['top']:
821
- offset_y += g.get_side_size('bottom')
849
+ offset_y = my + self.main_cell_height + self.main_layout.get_side_size("top")
850
+ for g in self._side_layouts["top"]:
851
+ offset_y += g.get_side_size("bottom")
822
852
  g.set_anchor((mx, offset_y))
823
853
  g.set_figsize(figsize)
824
854
  g.freeze(figure, _debug=_debug)
825
- offset_y += (g.get_main_height() + g.get_side_size('top'))
855
+ offset_y += g.get_main_height() + g.get_side_size("top")
826
856
 
827
857
  # Add legend axes
828
858
  # The range is limited to the main_cell in each layout
@@ -834,10 +864,10 @@ class CompositeCrossLayout(_MarginMixin):
834
864
  side = self._legend_axes.side
835
865
  size = self._legend_axes.size
836
866
 
837
- if side == 'right':
867
+ if side == "right":
838
868
  cx, cy = fig_w - size - self.margin.right, ymin
839
869
  cw, ch = size, legend_h
840
- elif side == 'left':
870
+ elif side == "left":
841
871
  cx, cy = self.margin.left, ymin
842
872
  cw, ch = size, legend_h
843
873
  elif side == "top":
@@ -857,26 +887,26 @@ class CompositeCrossLayout(_MarginMixin):
857
887
 
858
888
  def _edge_main_point(self):
859
889
  """This will return the edge point of all the main cell"""
860
- left_side = self._side_layouts['left']
890
+ left_side = self._side_layouts["left"]
861
891
  if len(left_side) > 0:
862
892
  xmin = left_side[-1].main_cell.anchor[0]
863
893
  else:
864
894
  xmin = self.main_layout.main_cell.anchor[0]
865
895
 
866
- right_side = self._side_layouts['right']
896
+ right_side = self._side_layouts["right"]
867
897
  if len(right_side) > 0:
868
898
  cell = right_side[-1].main_cell
869
899
  else:
870
900
  cell = self.main_layout.main_cell
871
901
  xmax = cell.anchor[0] + cell.width
872
902
 
873
- bottom_side = self._side_layouts['bottom']
903
+ bottom_side = self._side_layouts["bottom"]
874
904
  if len(bottom_side) > 0:
875
905
  ymin = bottom_side[-1].main_cell.anchor[1]
876
906
  else:
877
907
  ymin = self.main_layout.main_cell.anchor[1]
878
908
 
879
- top_side = self._side_layouts['top']
909
+ top_side = self._side_layouts["top"]
880
910
  if len(top_side) > 0:
881
911
  cell = top_side[-1].main_cell
882
912
  else:
@@ -1,3 +1,30 @@
1
+ __all__ = [
2
+ "Bar",
3
+ "Box",
4
+ "Boxen",
5
+ "Violin",
6
+ "Point",
7
+ "Strip",
8
+ "Swarm",
9
+ "Arc",
10
+ "Numbers",
11
+ "StackBar",
12
+ "CenterBar",
13
+ "RenderPlan",
14
+ "SeqLogo",
15
+ "Colors",
16
+ "ColorMesh",
17
+ "SizedMesh",
18
+ "MarkerMesh",
19
+ "TextMesh",
20
+ "Labels",
21
+ "AnnoLabels",
22
+ "Title",
23
+ "Chunk",
24
+ "FixedChunk",
25
+ "Area",
26
+ ]
27
+
1
28
  from ._seaborn import Bar, Box, Boxen, Violin, Point, Strip, Swarm
2
29
  from .arc import Arc
3
30
  from .bar import Numbers, StackBar, CenterBar
@@ -5,3 +32,5 @@ from .base import RenderPlan
5
32
  from .bio import SeqLogo
6
33
  from .mesh import Colors, ColorMesh, SizedMesh, MarkerMesh, TextMesh
7
34
  from .text import Labels, AnnoLabels, Title, Chunk, FixedChunk
35
+ # from ._images import Emoji
36
+ from .area import Area
@@ -1,6 +1,7 @@
1
1
  # The implementation of Image in matplotlib may suffer from compatibility
2
2
  # issues across different rendering backend at different DPI. Currently
3
3
  # not a public API.
4
+ from functools import partial
4
5
 
5
6
  import numpy as np
6
7
  from matplotlib.image import imread, BboxImage
@@ -27,7 +28,6 @@ def _cache_remote(url, cache=True):
27
28
 
28
29
 
29
30
  class Emoji(RenderPlan):
30
-
31
31
  def __init__(self, images, lang="en", scale=1, mode="filled"):
32
32
  try:
33
33
  import emoji
@@ -40,7 +40,8 @@ class Emoji(RenderPlan):
40
40
  if not emoji.is_emoji(i):
41
41
  raise ValueError(f"{i} is not a valid emoji")
42
42
  codes.append(f"{ord(i):X}".lower())
43
- self.set_data(codes)
43
+
44
+ self.set_data(np.asarray(codes))
44
45
  self.emoji_caches = {}
45
46
  for c in codes:
46
47
  cache_image = _cache_remote(f"{TWEMOJI_CDN}{c}.png")
@@ -49,19 +50,48 @@ class Emoji(RenderPlan):
49
50
  self.scale = scale
50
51
  self.mode = mode
51
52
 
53
+ def _get_images_bbox(self, figure, imgs):
54
+
55
+ for img in imgs:
56
+ width, height = img.shape[:2]
57
+
58
+ return Bbox.from_bounds(0, 0, width, height)
59
+
60
+
52
61
  def render_ax(self, spec):
53
62
  ax = spec.ax
54
63
  data = spec.data
55
64
 
56
- locs = np.linspace(0, 1, len(data)+2)[1:-1]
65
+ # TODO: Does not work for orient = "v"
66
+ locs = np.linspace(0, 1, len(data) + 1)
57
67
  for loc, d in zip(locs, data):
58
68
  img = self.emoji_caches[d]
59
69
  width, height = img.shape[:2]
60
70
 
61
- def get_emoji_bbox(renderer):
62
- x0, y0 = ax.transData.transform((loc, 0))
71
+ xmin, ymin = ax.transAxes.transform((0, 0))
72
+ xmax, ymax = ax.transAxes.transform((1, 1))
73
+
74
+ ax_width = xmax - xmin
75
+ ax_height = ymax - ymin
76
+
77
+ fit_width = ax_width / len(data)
78
+ fit_height = height / width * fit_width
79
+
80
+ fit_scale_width = fit_width * self.scale
81
+ fit_scale_height = fit_height * self.scale
82
+
83
+ offset = (fit_width - fit_scale_width) / 2 / ax_width
84
+ loc += offset
85
+
86
+ loc_y = 0.5 - fit_scale_height / 2 / ax_height
87
+
88
+ def get_emoji_bbox(renderer, loc, loc_y, width, height):
89
+ x0, y0 = ax.transData.transform((loc, loc_y))
63
90
  return Bbox.from_bounds(x0, y0, width, height)
64
91
 
65
- i1 = BboxImage(get_emoji_bbox,
66
- data=img)
92
+ partial_get_emoji_bbox = partial(get_emoji_bbox, loc=loc, loc_y=loc_y,
93
+ width=fit_scale_width,
94
+ height=fit_scale_height)
95
+
96
+ i1 = BboxImage(partial_get_emoji_bbox, data=img)
67
97
  ax.add_artist(i1)