plotnine 0.15.0.dev3__py3-none-any.whl → 0.15.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.
- plotnine/__init__.py +2 -0
- plotnine/_mpl/layout_manager/_engine.py +1 -1
- plotnine/_mpl/layout_manager/_layout_items.py +126 -41
- plotnine/_mpl/layout_manager/_layout_tree.py +712 -314
- plotnine/_mpl/layout_manager/_spaces.py +305 -101
- plotnine/_mpl/patches.py +70 -34
- plotnine/_mpl/text.py +144 -63
- plotnine/_mpl/utils.py +1 -1
- plotnine/_utils/__init__.py +50 -107
- plotnine/_utils/context.py +78 -2
- plotnine/_utils/ipython.py +35 -51
- plotnine/_utils/quarto.py +26 -0
- plotnine/_utils/yippie.py +115 -0
- plotnine/composition/__init__.py +11 -0
- plotnine/composition/_beside.py +55 -0
- plotnine/composition/_compose.py +471 -0
- plotnine/composition/_plot_spacer.py +60 -0
- plotnine/composition/_stack.py +55 -0
- plotnine/coords/coord.py +3 -3
- plotnine/data/__init__.py +31 -0
- plotnine/data/anscombe-quartet.csv +45 -0
- plotnine/doctools.py +4 -4
- plotnine/facets/facet.py +4 -4
- plotnine/facets/strips.py +17 -28
- plotnine/geoms/annotate.py +13 -13
- plotnine/geoms/annotation_logticks.py +7 -8
- plotnine/geoms/annotation_stripes.py +6 -6
- plotnine/geoms/geom.py +60 -27
- plotnine/geoms/geom_abline.py +3 -2
- plotnine/geoms/geom_area.py +2 -2
- plotnine/geoms/geom_bar.py +1 -0
- plotnine/geoms/geom_bin_2d.py +6 -2
- plotnine/geoms/geom_blank.py +0 -3
- plotnine/geoms/geom_boxplot.py +8 -4
- plotnine/geoms/geom_col.py +2 -2
- plotnine/geoms/geom_count.py +6 -2
- plotnine/geoms/geom_crossbar.py +3 -3
- plotnine/geoms/geom_density_2d.py +6 -2
- plotnine/geoms/geom_dotplot.py +2 -2
- plotnine/geoms/geom_errorbar.py +2 -2
- plotnine/geoms/geom_errorbarh.py +2 -2
- plotnine/geoms/geom_histogram.py +1 -1
- plotnine/geoms/geom_hline.py +3 -2
- plotnine/geoms/geom_linerange.py +2 -2
- plotnine/geoms/geom_map.py +5 -5
- plotnine/geoms/geom_path.py +11 -12
- plotnine/geoms/geom_point.py +4 -5
- plotnine/geoms/geom_pointdensity.py +4 -0
- plotnine/geoms/geom_pointrange.py +3 -5
- plotnine/geoms/geom_polygon.py +2 -3
- plotnine/geoms/geom_qq.py +4 -0
- plotnine/geoms/geom_qq_line.py +4 -0
- plotnine/geoms/geom_quantile.py +4 -0
- plotnine/geoms/geom_raster.py +4 -5
- plotnine/geoms/geom_rect.py +3 -4
- plotnine/geoms/geom_ribbon.py +7 -7
- plotnine/geoms/geom_rug.py +1 -1
- plotnine/geoms/geom_segment.py +2 -2
- plotnine/geoms/geom_sina.py +3 -3
- plotnine/geoms/geom_smooth.py +7 -3
- plotnine/geoms/geom_step.py +2 -2
- plotnine/geoms/geom_text.py +2 -3
- plotnine/geoms/geom_violin.py +8 -5
- plotnine/geoms/geom_vline.py +3 -2
- plotnine/ggplot.py +64 -85
- plotnine/guides/guide.py +7 -10
- plotnine/guides/guide_colorbar.py +3 -3
- plotnine/guides/guide_legend.py +3 -3
- plotnine/guides/guides.py +6 -6
- plotnine/helpers.py +49 -0
- plotnine/iapi.py +28 -5
- plotnine/labels.py +3 -3
- plotnine/layer.py +36 -19
- plotnine/mapping/_atomic.py +178 -0
- plotnine/mapping/_env.py +13 -2
- plotnine/mapping/_eval_environment.py +1 -1
- plotnine/mapping/aes.py +85 -49
- plotnine/scales/__init__.py +2 -0
- plotnine/scales/limits.py +7 -7
- plotnine/scales/scale.py +3 -3
- plotnine/scales/scale_color.py +82 -18
- plotnine/scales/scale_continuous.py +6 -4
- plotnine/scales/scale_datetime.py +28 -14
- plotnine/scales/scale_discrete.py +1 -1
- plotnine/scales/scale_identity.py +21 -2
- plotnine/scales/scale_manual.py +8 -2
- plotnine/scales/scale_xy.py +2 -2
- plotnine/stats/binning.py +4 -1
- plotnine/stats/smoothers.py +23 -36
- plotnine/stats/stat.py +20 -32
- plotnine/stats/stat_bin.py +6 -5
- plotnine/stats/stat_bin_2d.py +11 -9
- plotnine/stats/stat_bindot.py +13 -16
- plotnine/stats/stat_boxplot.py +6 -6
- plotnine/stats/stat_count.py +6 -9
- plotnine/stats/stat_density.py +7 -10
- plotnine/stats/stat_density_2d.py +12 -8
- plotnine/stats/stat_ecdf.py +7 -6
- plotnine/stats/stat_ellipse.py +9 -6
- plotnine/stats/stat_function.py +10 -8
- plotnine/stats/stat_hull.py +6 -3
- plotnine/stats/stat_identity.py +5 -2
- plotnine/stats/stat_pointdensity.py +5 -7
- plotnine/stats/stat_qq.py +46 -20
- plotnine/stats/stat_qq_line.py +16 -11
- plotnine/stats/stat_quantile.py +15 -9
- plotnine/stats/stat_sina.py +13 -15
- plotnine/stats/stat_smooth.py +8 -10
- plotnine/stats/stat_sum.py +5 -2
- plotnine/stats/stat_summary.py +7 -10
- plotnine/stats/stat_summary_bin.py +11 -14
- plotnine/stats/stat_unique.py +5 -2
- plotnine/stats/stat_ydensity.py +8 -11
- plotnine/themes/elements/__init__.py +2 -1
- plotnine/themes/elements/element_line.py +17 -9
- plotnine/themes/elements/margin.py +64 -1
- plotnine/themes/theme.py +9 -1
- plotnine/themes/theme_538.py +0 -1
- plotnine/themes/theme_bw.py +0 -1
- plotnine/themes/theme_dark.py +0 -1
- plotnine/themes/theme_gray.py +6 -5
- plotnine/themes/theme_light.py +1 -1
- plotnine/themes/theme_matplotlib.py +5 -5
- plotnine/themes/theme_seaborn.py +7 -4
- plotnine/themes/theme_void.py +9 -8
- plotnine/themes/theme_xkcd.py +0 -1
- plotnine/themes/themeable.py +109 -31
- plotnine/typing.py +17 -6
- plotnine/watermark.py +3 -3
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/METADATA +13 -6
- plotnine-0.15.2.dist-info/RECORD +221 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/WHEEL +1 -1
- plotnine/plot_composition/__init__.py +0 -10
- plotnine/plot_composition/_compose.py +0 -436
- plotnine/plot_composition/_spacer.py +0 -32
- plotnine-0.15.0.dev3.dist-info/RECORD +0 -215
- /plotnine/{plot_composition → composition}/_plotspec.py +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/licenses/LICENSE +0 -0
- {plotnine-0.15.0.dev3.dist-info → plotnine-0.15.2.dist-info}/top_level.txt +0 -0
plotnine/__init__.py
CHANGED
|
@@ -198,6 +198,7 @@ from .scales import (
|
|
|
198
198
|
scale_size_radius,
|
|
199
199
|
scale_stroke,
|
|
200
200
|
scale_stroke_continuous,
|
|
201
|
+
scale_stroke_identity,
|
|
201
202
|
scale_x_continuous,
|
|
202
203
|
scale_x_date,
|
|
203
204
|
scale_x_datetime,
|
|
@@ -441,6 +442,7 @@ __all__ = (
|
|
|
441
442
|
"scale_size_radius",
|
|
442
443
|
"scale_stroke",
|
|
443
444
|
"scale_stroke_continuous",
|
|
445
|
+
"scale_stroke_identity",
|
|
444
446
|
"scale_x_continuous",
|
|
445
447
|
"scale_x_date",
|
|
446
448
|
"scale_x_datetime",
|
|
@@ -6,6 +6,8 @@ from typing import TYPE_CHECKING, cast
|
|
|
6
6
|
|
|
7
7
|
from matplotlib.text import Text
|
|
8
8
|
|
|
9
|
+
from plotnine._mpl.patches import StripTextPatch
|
|
10
|
+
from plotnine._utils import ha_as_float, va_as_float
|
|
9
11
|
from plotnine.exceptions import PlotnineError
|
|
10
12
|
|
|
11
13
|
from ..utils import (
|
|
@@ -35,7 +37,11 @@ if TYPE_CHECKING:
|
|
|
35
37
|
from plotnine._mpl.text import StripText
|
|
36
38
|
from plotnine.iapi import legend_artists
|
|
37
39
|
from plotnine.themes.elements import margin as Margin
|
|
38
|
-
from plotnine.typing import
|
|
40
|
+
from plotnine.typing import (
|
|
41
|
+
HorizontalJustification,
|
|
42
|
+
StripPosition,
|
|
43
|
+
VerticalJustification,
|
|
44
|
+
)
|
|
39
45
|
|
|
40
46
|
from ._spaces import LayoutSpaces
|
|
41
47
|
|
|
@@ -299,9 +305,9 @@ class LayoutItems:
|
|
|
299
305
|
|
|
300
306
|
return chain(major, minor)
|
|
301
307
|
|
|
302
|
-
def
|
|
308
|
+
def strip_text_x_extra_height(self, position: StripPosition) -> float:
|
|
303
309
|
"""
|
|
304
|
-
Height taken up by the top strips
|
|
310
|
+
Height taken up by the top strips that is outside the panels
|
|
305
311
|
"""
|
|
306
312
|
if not self.strip_text_x:
|
|
307
313
|
return 0
|
|
@@ -311,11 +317,23 @@ class LayoutItems:
|
|
|
311
317
|
for st in self.strip_text_x
|
|
312
318
|
if st.patch.position == position
|
|
313
319
|
]
|
|
314
|
-
return self.calc.max_height(artists)
|
|
315
320
|
|
|
316
|
-
|
|
321
|
+
heights = []
|
|
322
|
+
|
|
323
|
+
for a in artists:
|
|
324
|
+
info = (
|
|
325
|
+
a.text.draw_info
|
|
326
|
+
if isinstance(a, StripTextPatch)
|
|
327
|
+
else a.draw_info
|
|
328
|
+
)
|
|
329
|
+
h = self.calc.height(a)
|
|
330
|
+
heights.append(max(h + h * info.strip_align, 0))
|
|
331
|
+
|
|
332
|
+
return max(heights)
|
|
333
|
+
|
|
334
|
+
def strip_text_y_extra_width(self, position: StripPosition) -> float:
|
|
317
335
|
"""
|
|
318
|
-
Width taken up by the
|
|
336
|
+
Width taken up by the top strips that is outside the panels
|
|
319
337
|
"""
|
|
320
338
|
if not self.strip_text_y:
|
|
321
339
|
return 0
|
|
@@ -325,7 +343,19 @@ class LayoutItems:
|
|
|
325
343
|
for st in self.strip_text_y
|
|
326
344
|
if st.patch.position == position
|
|
327
345
|
]
|
|
328
|
-
|
|
346
|
+
|
|
347
|
+
widths = []
|
|
348
|
+
|
|
349
|
+
for a in artists:
|
|
350
|
+
info = (
|
|
351
|
+
a.text.draw_info
|
|
352
|
+
if isinstance(a, StripTextPatch)
|
|
353
|
+
else a.draw_info
|
|
354
|
+
)
|
|
355
|
+
w = self.calc.width(a)
|
|
356
|
+
widths.append(max(w + w * info.strip_align, 0))
|
|
357
|
+
|
|
358
|
+
return max(widths)
|
|
329
359
|
|
|
330
360
|
def axis_ticks_x_max_height_at(self, location: AxesLocation) -> float:
|
|
331
361
|
"""
|
|
@@ -489,6 +519,8 @@ class LayoutItems:
|
|
|
489
519
|
|
|
490
520
|
self._adjust_axis_text_x(justify)
|
|
491
521
|
self._adjust_axis_text_y(justify)
|
|
522
|
+
self._strip_text_x_background_equal_heights()
|
|
523
|
+
self._strip_text_y_background_equal_widths()
|
|
492
524
|
|
|
493
525
|
def _adjust_axis_text_x(self, justify: TextJustifier):
|
|
494
526
|
"""
|
|
@@ -574,6 +606,36 @@ class LayoutItems:
|
|
|
574
606
|
text, ha, -axis_text_col_width, 0, width=width
|
|
575
607
|
)
|
|
576
608
|
|
|
609
|
+
def _strip_text_x_background_equal_heights(self):
|
|
610
|
+
"""
|
|
611
|
+
Make the strip_text_x_backgrounds have equal heights
|
|
612
|
+
|
|
613
|
+
The smaller heights are expanded to match the largest height
|
|
614
|
+
"""
|
|
615
|
+
if not self.strip_text_x:
|
|
616
|
+
return
|
|
617
|
+
|
|
618
|
+
heights = [self.calc.bbox(t.patch).height for t in self.strip_text_x]
|
|
619
|
+
max_height = max(heights)
|
|
620
|
+
relative_heights = [max_height / h for h in heights]
|
|
621
|
+
for text, scale in zip(self.strip_text_x, relative_heights):
|
|
622
|
+
text.patch.expand = scale
|
|
623
|
+
|
|
624
|
+
def _strip_text_y_background_equal_widths(self):
|
|
625
|
+
"""
|
|
626
|
+
Make the strip_text_y_backgrounds have equal widths
|
|
627
|
+
|
|
628
|
+
The smaller widths are expanded to match the largest width
|
|
629
|
+
"""
|
|
630
|
+
if not self.strip_text_y:
|
|
631
|
+
return
|
|
632
|
+
|
|
633
|
+
widths = [self.calc.bbox(t.patch).width for t in self.strip_text_y]
|
|
634
|
+
max_width = max(widths)
|
|
635
|
+
relative_widths = [max_width / w for w in widths]
|
|
636
|
+
for text, scale in zip(self.strip_text_y, relative_widths):
|
|
637
|
+
text.patch.expand = scale
|
|
638
|
+
|
|
577
639
|
|
|
578
640
|
def _text_is_visible(text: Text) -> bool:
|
|
579
641
|
"""
|
|
@@ -596,7 +658,7 @@ class TextJustifier:
|
|
|
596
658
|
def horizontally(
|
|
597
659
|
self,
|
|
598
660
|
text: Text,
|
|
599
|
-
ha:
|
|
661
|
+
ha: HorizontalJustification | float,
|
|
600
662
|
left: float,
|
|
601
663
|
right: float,
|
|
602
664
|
width: float | None = None,
|
|
@@ -604,8 +666,7 @@ class TextJustifier:
|
|
|
604
666
|
"""
|
|
605
667
|
Horizontally Justify text between left and right
|
|
606
668
|
"""
|
|
607
|
-
|
|
608
|
-
rel = lookup.get(ha, ha) # pyright: ignore[reportCallIssue, reportArgumentType]
|
|
669
|
+
rel = ha_as_float(ha)
|
|
609
670
|
if width is None:
|
|
610
671
|
width = self.spaces.items.calc.width(text)
|
|
611
672
|
x = rel_position(rel, width, left, right)
|
|
@@ -615,7 +676,7 @@ class TextJustifier:
|
|
|
615
676
|
def vertically(
|
|
616
677
|
self,
|
|
617
678
|
text: Text,
|
|
618
|
-
va:
|
|
679
|
+
va: VerticalJustification | float,
|
|
619
680
|
bottom: float,
|
|
620
681
|
top: float,
|
|
621
682
|
height: float | None = None,
|
|
@@ -623,14 +684,7 @@ class TextJustifier:
|
|
|
623
684
|
"""
|
|
624
685
|
Vertically Justify text between bottom and top
|
|
625
686
|
"""
|
|
626
|
-
|
|
627
|
-
"top": 1.0,
|
|
628
|
-
"center": 0.5,
|
|
629
|
-
"baseline": 0.5,
|
|
630
|
-
"center_baseline": 0.5,
|
|
631
|
-
"bottom": 0.0,
|
|
632
|
-
}
|
|
633
|
-
rel = lookup.get(va, va) # pyright: ignore[reportCallIssue, reportArgumentType]
|
|
687
|
+
rel = va_as_float(va)
|
|
634
688
|
|
|
635
689
|
if height is None:
|
|
636
690
|
height = self.spaces.items.calc.height(text)
|
|
@@ -638,13 +692,19 @@ class TextJustifier:
|
|
|
638
692
|
text.set_y(y)
|
|
639
693
|
text.set_verticalalignment("bottom")
|
|
640
694
|
|
|
641
|
-
def horizontally_across_panel(
|
|
695
|
+
def horizontally_across_panel(
|
|
696
|
+
self, text: Text, ha: HorizontalJustification | float
|
|
697
|
+
):
|
|
642
698
|
"""
|
|
643
699
|
Horizontally Justify text accross the panel(s) width
|
|
644
700
|
"""
|
|
645
|
-
self.horizontally(
|
|
701
|
+
self.horizontally(
|
|
702
|
+
text, ha, self.spaces.l.panel_left, self.spaces.r.panel_right
|
|
703
|
+
)
|
|
646
704
|
|
|
647
|
-
def horizontally_across_plot(
|
|
705
|
+
def horizontally_across_plot(
|
|
706
|
+
self, text: Text, ha: HorizontalJustification | float
|
|
707
|
+
):
|
|
648
708
|
"""
|
|
649
709
|
Horizontally Justify text across the plot's width
|
|
650
710
|
"""
|
|
@@ -652,13 +712,19 @@ class TextJustifier:
|
|
|
652
712
|
text, ha, self.spaces.l.plot_left, self.spaces.r.plot_right
|
|
653
713
|
)
|
|
654
714
|
|
|
655
|
-
def vertically_along_panel(
|
|
715
|
+
def vertically_along_panel(
|
|
716
|
+
self, text: Text, va: VerticalJustification | float
|
|
717
|
+
):
|
|
656
718
|
"""
|
|
657
719
|
Horizontally Justify text along the panel(s) height
|
|
658
720
|
"""
|
|
659
|
-
self.vertically(
|
|
721
|
+
self.vertically(
|
|
722
|
+
text, va, self.spaces.b.panel_bottom, self.spaces.t.panel_top
|
|
723
|
+
)
|
|
660
724
|
|
|
661
|
-
def vertically_along_plot(
|
|
725
|
+
def vertically_along_plot(
|
|
726
|
+
self, text: Text, va: VerticalJustification | float
|
|
727
|
+
):
|
|
662
728
|
"""
|
|
663
729
|
Vertically Justify text along the plot's height
|
|
664
730
|
"""
|
|
@@ -723,7 +789,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
|
|
|
723
789
|
if legends.right:
|
|
724
790
|
y = rel_position(
|
|
725
791
|
legends.right.justification,
|
|
726
|
-
spaces.r.
|
|
792
|
+
spaces.r.legend_height,
|
|
727
793
|
params.bottom,
|
|
728
794
|
params.top,
|
|
729
795
|
)
|
|
@@ -733,7 +799,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
|
|
|
733
799
|
if legends.left:
|
|
734
800
|
y = rel_position(
|
|
735
801
|
legends.left.justification,
|
|
736
|
-
spaces.l.
|
|
802
|
+
spaces.l.legend_height,
|
|
737
803
|
params.bottom,
|
|
738
804
|
params.top,
|
|
739
805
|
)
|
|
@@ -743,7 +809,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
|
|
|
743
809
|
if legends.top:
|
|
744
810
|
x = rel_position(
|
|
745
811
|
legends.top.justification,
|
|
746
|
-
spaces.t.
|
|
812
|
+
spaces.t.legend_width,
|
|
747
813
|
params.left,
|
|
748
814
|
params.right,
|
|
749
815
|
)
|
|
@@ -753,7 +819,7 @@ def set_legends_position(legends: legend_artists, spaces: LayoutSpaces):
|
|
|
753
819
|
if legends.bottom:
|
|
754
820
|
x = rel_position(
|
|
755
821
|
legends.bottom.justification,
|
|
756
|
-
spaces.b.
|
|
822
|
+
spaces.b.legend_width,
|
|
757
823
|
params.left,
|
|
758
824
|
params.right,
|
|
759
825
|
)
|
|
@@ -830,7 +896,10 @@ def set_plot_tag_position(tag: Text, spaces: LayoutSpaces):
|
|
|
830
896
|
|
|
831
897
|
def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
|
|
832
898
|
"""
|
|
833
|
-
Place the tag in
|
|
899
|
+
Place the tag in an inner margin around the plot
|
|
900
|
+
|
|
901
|
+
The panel_margin remains outside the tag. For compositions, the
|
|
902
|
+
tag is placed and within the tag_alignment space.
|
|
834
903
|
"""
|
|
835
904
|
position: TagPosition = spaces.plot.theme.getp("plot_tag_position")
|
|
836
905
|
if not isinstance(position, str):
|
|
@@ -840,18 +909,34 @@ def set_plot_tag_position_in_margin(tag: Text, spaces: LayoutSpaces):
|
|
|
840
909
|
)
|
|
841
910
|
|
|
842
911
|
tag.set_position(spaces.to_figure_space((0.5, 0.5)))
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
tag.
|
|
849
|
-
if "left" in position:
|
|
850
|
-
tag.set_x(spaces.l.x1("plot_tag"))
|
|
912
|
+
ha = spaces.plot.theme.get_ha("plot_tag")
|
|
913
|
+
va = spaces.plot.theme.get_va("plot_tag")
|
|
914
|
+
if "left" in position: # left, topleft, bottomleft
|
|
915
|
+
space = spaces.l.tag_alignment
|
|
916
|
+
x = spaces.l.x1("plot_tag") - (1 - ha) * space
|
|
917
|
+
tag.set_x(x)
|
|
851
918
|
tag.set_horizontalalignment("left")
|
|
852
|
-
if "right" in position:
|
|
853
|
-
|
|
854
|
-
|
|
919
|
+
if "right" in position: # right, topright, bottomright
|
|
920
|
+
space = spaces.r.tag_alignment
|
|
921
|
+
x = spaces.r.x1("plot_tag") + ha * space
|
|
922
|
+
tag.set_x(x)
|
|
923
|
+
tag.set_horizontalalignment("left")
|
|
924
|
+
if "bottom" in position: # bottom, bottomleft, bottomright
|
|
925
|
+
space = spaces.b.tag_alignment
|
|
926
|
+
y = spaces.b.y1("plot_tag") + (1 - va) * space
|
|
927
|
+
tag.set_y(y)
|
|
928
|
+
tag.set_verticalalignment("bottom")
|
|
929
|
+
if "top" in position: # top, topleft, topright
|
|
930
|
+
space = spaces.t.tag_alignment
|
|
931
|
+
y = spaces.t.y1("plot_tag") + va * space
|
|
932
|
+
tag.set_y(y)
|
|
933
|
+
tag.set_verticalalignment("bottom")
|
|
934
|
+
|
|
935
|
+
justify = TextJustifier(spaces)
|
|
936
|
+
if position in ("left", "right"):
|
|
937
|
+
justify.vertically_along_plot(tag, va)
|
|
938
|
+
elif position in ("top", "bottom"):
|
|
939
|
+
justify.horizontally_across_plot(tag, ha)
|
|
855
940
|
|
|
856
941
|
|
|
857
942
|
def _plot_tag_margin_adjustment(
|