plot-io 0.12.0__py3-none-win_arm64.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.
- plot_io/__init__.py +73 -0
- plot_io/_methods.py +1691 -0
- plot_io/_native/__init__.py +199 -0
- plot_io/_native/__main__.py +197 -0
- plot_io/_native/_api.py +457 -0
- plot_io/_native/bin/windows-arm64.dll +0 -0
- plot_io/_native/kernels.py +335 -0
- plot_io/_native/manifest.json +49 -0
- plot_io/axis.py +1347 -0
- plot_io/boxes.py +1156 -0
- plot_io/canvas.py +389 -0
- plot_io/color/__init__.py +3 -0
- plot_io/color/data.py +1646 -0
- plot_io/color/maps.py +922 -0
- plot_io/color/values.py +295 -0
- plot_io/color/variants.py +1246 -0
- plot_io/colorbar.py +1426 -0
- plot_io/columns.py +614 -0
- plot_io/decorations.py +920 -0
- plot_io/defaults.py +809 -0
- plot_io/density.py +113 -0
- plot_io/ext.py +68 -0
- plot_io/facets.py +240 -0
- plot_io/figure.py +788 -0
- plot_io/formatters.py +97 -0
- plot_io/genome/__init__.py +17 -0
- plot_io/genome/adapters.py +356 -0
- plot_io/genome/combine.py +123 -0
- plot_io/genome/contacts.py +508 -0
- plot_io/genome/intervals.py +750 -0
- plot_io/genome/lanes.py +191 -0
- plot_io/genome/layout.py +184 -0
- plot_io/genome/links.py +364 -0
- plot_io/genome/pack.py +27 -0
- plot_io/genome/readers.py +145 -0
- plot_io/genome/regions.py +228 -0
- plot_io/genome/scale.py +349 -0
- plot_io/genome/series.py +344 -0
- plot_io/genome/signal.py +621 -0
- plot_io/genome/sites.py +782 -0
- plot_io/genome/sources.py +459 -0
- plot_io/genome/testing.py +161 -0
- plot_io/geometry.py +289 -0
- plot_io/groups.py +308 -0
- plot_io/guides.py +503 -0
- plot_io/helpers/__init__.py +17 -0
- plot_io/helpers/clustermap.py +202 -0
- plot_io/helpers/dendrogram.py +172 -0
- plot_io/helpers/grids.py +304 -0
- plot_io/helpers/qq.py +334 -0
- plot_io/helpers/raincloud.py +249 -0
- plot_io/helpers/tree.py +214 -0
- plot_io/helpers/volcano.py +505 -0
- plot_io/hints.py +125 -0
- plot_io/kernels.py +740 -0
- plot_io/labels.py +566 -0
- plot_io/layers/__init__.py +100 -0
- plot_io/layers/base.py +630 -0
- plot_io/layers/context.py +87 -0
- plot_io/layers/core.py +446 -0
- plot_io/layers/geoms.py +1362 -0
- plot_io/layers/grammar.py +586 -0
- plot_io/layers/paint.py +614 -0
- plot_io/layers/pipeline.py +170 -0
- plot_io/layers/positions.py +431 -0
- plot_io/layers/stats.py +1088 -0
- plot_io/layout.py +1381 -0
- plot_io/legend.py +605 -0
- plot_io/levels.py +509 -0
- plot_io/limits.py +124 -0
- plot_io/lines.py +42 -0
- plot_io/mapped.py +522 -0
- plot_io/markers.py +110 -0
- plot_io/markup.py +128 -0
- plot_io/numbers.py +354 -0
- plot_io/occupancy.py +295 -0
- plot_io/plot.py +597 -0
- plot_io/presets/__init__.py +51 -0
- plot_io/presets/_util.py +153 -0
- plot_io/presets/area.py +145 -0
- plot_io/presets/bar.py +261 -0
- plot_io/presets/box.py +400 -0
- plot_io/presets/catpoints.py +177 -0
- plot_io/presets/contour.py +353 -0
- plot_io/presets/curves.py +30 -0
- plot_io/presets/dots.py +355 -0
- plot_io/presets/ecdf.py +97 -0
- plot_io/presets/errorbar.py +106 -0
- plot_io/presets/errors.py +296 -0
- plot_io/presets/fill.py +74 -0
- plot_io/presets/heatmap.py +322 -0
- plot_io/presets/hexbin.py +198 -0
- plot_io/presets/hist.py +391 -0
- plot_io/presets/hist2d.py +87 -0
- plot_io/presets/kde.py +162 -0
- plot_io/presets/kde2d.py +147 -0
- plot_io/presets/line.py +143 -0
- plot_io/presets/pie.py +502 -0
- plot_io/presets/regression.py +115 -0
- plot_io/presets/scatter.py +194 -0
- plot_io/presets/stem.py +100 -0
- plot_io/presets/violin.py +323 -0
- plot_io/py.typed +0 -0
- plot_io/registry.py +82 -0
- plot_io/render.py +480 -0
- plot_io/rows.py +491 -0
- plot_io/scales.py +103 -0
- plot_io/seams.py +138 -0
- plot_io/settings.py +807 -0
- plot_io/shapes.py +66 -0
- plot_io/sharing.py +232 -0
- plot_io/sizemaps.py +97 -0
- plot_io/spec.py +240 -0
- plot_io/states.py +216 -0
- plot_io/styles/__init__.py +307 -0
- plot_io/styles/library.py +397 -0
- plot_io/tables.py +138 -0
- plot_io/text.py +412 -0
- plot_io/ticks.py +400 -0
- plot_io/times.py +287 -0
- plot_io/titles.py +225 -0
- plot_io/transform.py +114 -0
- plot_io/types.py +13604 -0
- plot_io/units.py +768 -0
- plot_io/validators.py +1029 -0
- plot_io/values.py +190 -0
- plot_io-0.12.0.dist-info/METADATA +215 -0
- plot_io-0.12.0.dist-info/RECORD +130 -0
- plot_io-0.12.0.dist-info/WHEEL +4 -0
- plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
plot_io/plot.py
ADDED
|
@@ -0,0 +1,597 @@
|
|
|
1
|
+
"""Plots: one area of a figure, with axes, series, title, legend and colorbars."""
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Self, Unpack
|
|
4
|
+
|
|
5
|
+
from .canvas import Canvas
|
|
6
|
+
from ._methods import FigureLegendMixin, PlotMixin, TitleMixin
|
|
7
|
+
from . import groups
|
|
8
|
+
from .axis import Axis
|
|
9
|
+
from .color.values import VARIANT_DOC, check_variant, color
|
|
10
|
+
from .settings import (DECLINE, custom_vars, Setting, Settings, boolean, canonical_kwargs,
|
|
11
|
+
dpi_or_bool, either, literal, number, one_of, route, simplify_value,
|
|
12
|
+
table, text)
|
|
13
|
+
from .guides import anchor as _anchor, placement, set_place, where_of
|
|
14
|
+
from .titles import ALIGNS, CREATED, SIDES, TitleBase, align_of
|
|
15
|
+
from .validators import length, typed
|
|
16
|
+
from .color.maps import kind_settings
|
|
17
|
+
|
|
18
|
+
if TYPE_CHECKING:
|
|
19
|
+
from .colorbar import Colorbar
|
|
20
|
+
from .decorations import AxLine, Annotate, Bracket, HLine, HSpan, Text, VLine, VSpan
|
|
21
|
+
from .types import (AnnotateSettings, AxLineSettings, BracketSettings, ColorbarSettings,
|
|
22
|
+
HLineSettings, HSpanSettings, LegendSettings, NewPlotSettings,
|
|
23
|
+
PlotSettings, TextSettings, VLineSettings, VSpanSettings, WriteOptions)
|
|
24
|
+
|
|
25
|
+
__all__ = ["Plot", "Title", "Legend", "FigureLegend"]
|
|
26
|
+
|
|
27
|
+
_SPINES = ("left", "bottom", "right", "top")
|
|
28
|
+
@typed("str | Iterable[str]")
|
|
29
|
+
def _spines(v: Any) -> frozenset[str]:
|
|
30
|
+
if isinstance(v, str):
|
|
31
|
+
s = v.strip().lower()
|
|
32
|
+
if s == "all":
|
|
33
|
+
return frozenset(_SPINES)
|
|
34
|
+
if s == "none":
|
|
35
|
+
return frozenset()
|
|
36
|
+
parts = s.replace(",", " ").split()
|
|
37
|
+
else:
|
|
38
|
+
parts = [str(p).lower() for p in v]
|
|
39
|
+
bad = [p for p in parts if p not in _SPINES]
|
|
40
|
+
if bad:
|
|
41
|
+
raise ValueError(f"unknown spine {bad[0]!r} (use left, bottom, right, top, all, none)")
|
|
42
|
+
return frozenset(parts)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@typed("Plot")
|
|
46
|
+
def _plot(v: Any) -> Any:
|
|
47
|
+
if not isinstance(v, Plot):
|
|
48
|
+
raise ValueError(f"expected a Plot, got {v!r}")
|
|
49
|
+
return v
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
_INHERITED = dict(
|
|
53
|
+
**groups.Font.embed(),
|
|
54
|
+
text_color=Setting(color, inherited=True, doc="Text color."),
|
|
55
|
+
line_width=Setting(length(lo=0), inherited=True, doc="Default line width."),
|
|
56
|
+
**kind_settings(),
|
|
57
|
+
color_variant=Setting(check_variant, inherited=True, doc=VARIANT_DOC),
|
|
58
|
+
plain_color=Setting(color, inherited=True,
|
|
59
|
+
doc="Color of a series that needs no hue (no levels, one color, no "
|
|
60
|
+
"`name`); unset: the plot's next color."),
|
|
61
|
+
opacity=Setting(number(0, 1), inherited=True, doc="Default opacity of series."),
|
|
62
|
+
rasterize=Setting(dpi_or_bool, inherited=True,
|
|
63
|
+
doc="`True` or a dpi: series drawn as images inside the PDF."),
|
|
64
|
+
simplify=Setting(simplify_value, inherited=True,
|
|
65
|
+
doc="vec_io's `simplify` for every series: `\"auto\"`, `True`, a dpi, "
|
|
66
|
+
"`False`, or a `Simplify` (or a dict of its fields)."),
|
|
67
|
+
data=Setting(lambda v: v, inherited=True,
|
|
68
|
+
doc="A table (dict, pandas or polars data frame, numpy structured array): "
|
|
69
|
+
"strings in the data arguments of series name its columns."),
|
|
70
|
+
**groups.NumberFormat.embed(),
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _legend_title(legend: "Legend") -> Any:
|
|
75
|
+
"""``Legend.title="auto"``: the name of the `group` column of the plot's series."""
|
|
76
|
+
return getattr(legend._plot, "_legend_title", None)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
class Title(TitleMixin, TitleBase):
|
|
80
|
+
"""A title of a plot: its main title (``plot.title``) or another one
|
|
81
|
+
(``plot.add_title``), in a band of the plot's box on its ``side``.
|
|
82
|
+
|
|
83
|
+
The titles of one side stack outward from the axes, outermost of the side's bands: the
|
|
84
|
+
main title nearest, then the others in creation order; ``order`` places one elsewhere,
|
|
85
|
+
lower nearer (``order=-1``: a subtitle between the main title and the axes). Each takes
|
|
86
|
+
a band of its ``pad``, its text and, with a ``rule``, ``rule_width + rule_gap``. A line
|
|
87
|
+
break starts a line; the last line is nearest the axes on top, left and right. Left
|
|
88
|
+
titles read upward, right titles downward (``rotation="auto"``). ``align``: ``start``,
|
|
89
|
+
``center``, ``end``, or an end by name (``left``, ``right`` on top and bottom; ``top``,
|
|
90
|
+
``bottom`` on the left and right, where ``start`` is where the text begins).
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
def __init__(self, plot: "Plot", **settings: Any) -> None:
|
|
94
|
+
self._plot = plot
|
|
95
|
+
super().__init__(**settings)
|
|
96
|
+
|
|
97
|
+
def _parent(self) -> Settings:
|
|
98
|
+
return self._plot
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class Legend(Settings):
|
|
102
|
+
"""The legend of a plot (``plot.legend``): an entry per named series.
|
|
103
|
+
|
|
104
|
+
Entries are drawn as the series draws itself (marker, line, swatch, box), or as
|
|
105
|
+
``glyph`` (``"box"``, ``"circle"``, ``"line"``) in its color; ``name=""`` gives none. A
|
|
106
|
+
plot and its twins share one legend. ``inside`` (an anchor, ``"auto"`` or ``"best"``)
|
|
107
|
+
places it inside the axes, ``side`` outside; setting one unsets the other, and a
|
|
108
|
+
``side`` from the style yields to an ``inside`` set on the legend. On the top and
|
|
109
|
+
bottom entries are side by side unless ``orientation`` or ``columns`` is set; without
|
|
110
|
+
``columns`` they wrap within the axes' length (inside: less ``pad``). An inside legend
|
|
111
|
+
larger than its axes goes to the right side, with a warning. Outside, a legend wraps
|
|
112
|
+
within the axes' length along its side, or in a figure of given size within the
|
|
113
|
+
plot's box when longer (passing the axes' ends); ``align`` places it along its side
|
|
114
|
+
(``auto``: centred on top and bottom, else at the axes' start).
|
|
115
|
+
|
|
116
|
+
``inside="auto"`` marks what the series of the plot and its twins draw on a 64 × 64 grid
|
|
117
|
+
over the axes (markers with their size, curves along their length, the areas of bars,
|
|
118
|
+
boxes, violins and fills, the whole axes for heatmaps and contours; data sampled beyond
|
|
119
|
+
50,000 points), with the text and lines of decorations and inside colorbars. The legend
|
|
120
|
+
takes the first of ``top_right``, ``top_left``, ``bottom_left``, ``bottom_right``,
|
|
121
|
+
``top``, ``bottom``, ``center_right``, ``center_left`` that covers no marked cell, else
|
|
122
|
+
the free place nearest a corner, else the candidate covering no marker and the fewest
|
|
123
|
+
marked cells (at most 2 %), else the right side. ``inside="best"`` (matplotlib's
|
|
124
|
+
``loc="best"``) chooses among nine places, any covering no marker (the fewest marked
|
|
125
|
+
cells first), else goes outside on the right like ``"auto"``.
|
|
126
|
+
"""
|
|
127
|
+
|
|
128
|
+
SETTINGS = table(
|
|
129
|
+
inside=Setting(_anchor,
|
|
130
|
+
doc="Place inside the axes: `auto` (the least covered place, "
|
|
131
|
+
"never over a marker; else outside right), `best` (the "
|
|
132
|
+
"least covered place, never outside), `top_right`, `top_left`, "
|
|
133
|
+
"`bottom_right`, `bottom_left`, `top`, `bottom`, `center_right` "
|
|
134
|
+
"or `center_left`; setting it unsets `side`."),
|
|
135
|
+
side=Setting(one_of(*SIDES),
|
|
136
|
+
doc="Outside the axes on a side of the plot: `top`, `bottom`, `left` "
|
|
137
|
+
"or `right`; setting it unsets `inside`."),
|
|
138
|
+
align=Setting(one_of("auto", *ALIGNS),
|
|
139
|
+
doc="Alignment along its side, outside the axes: `start` (left, or "
|
|
140
|
+
"top), `center`, `end` (right, or bottom), or an end by name; "
|
|
141
|
+
"`auto`: centred when horizontal on top or bottom, else `start`."),
|
|
142
|
+
visible=Setting(boolean, doc="Draw the legend."),
|
|
143
|
+
columns=Setting(number(lo=1, hi=10_000, integer=True), doc="Number of columns."),
|
|
144
|
+
frame=Setting(boolean, doc="Draw a frame and background."),
|
|
145
|
+
title=Setting(text, auto=_legend_title,
|
|
146
|
+
doc="Legend title; `\"auto\"`: the `group` column name, if any."),
|
|
147
|
+
**groups.TextStyle.embed(only=("font_size", "color")),
|
|
148
|
+
background=Setting(color, doc="Background of a framed legend."),
|
|
149
|
+
**groups.Stroke.embed("frame_", only=("color", "line_width"),
|
|
150
|
+
docs={"color": "Frame color.",
|
|
151
|
+
"line_width": "Frame line width."}),
|
|
152
|
+
pad=Setting(length(lo=0, percent="axes"), doc="Gap to the axes."),
|
|
153
|
+
frame_radius=Setting(length(lo=0), doc="Corner radius of the frame."),
|
|
154
|
+
frame_opacity=Setting(number(0, 1), doc="Opacity of the frame and background."),
|
|
155
|
+
glyph_opacity=Setting(number(0, 1),
|
|
156
|
+
doc="Fill opacity of `box` and `circle` symbols, outlined in the "
|
|
157
|
+
"opaque color; default: the series' own fill."),
|
|
158
|
+
glyph=Setting(one_of("series", "box", "circle", "line"),
|
|
159
|
+
doc="Entry symbol: as the series draws itself, or a filled `box`, a "
|
|
160
|
+
"`circle` or a `line` in the series color."),
|
|
161
|
+
orientation=Setting(one_of("auto", "vertical", "horizontal"),
|
|
162
|
+
doc="`horizontal`: entries side by side; `auto`: horizontal on "
|
|
163
|
+
"the top and bottom sides and at `inside` `top` and "
|
|
164
|
+
"`bottom`."),
|
|
165
|
+
glyph_length=Setting(length(positive=True, percent="em"),
|
|
166
|
+
doc="Width of an entry's symbol."),
|
|
167
|
+
row_height=Setting(length(positive=True, percent="em"), doc="Height of an entry."),
|
|
168
|
+
glyph_gap=Setting(length(lo=0, percent="em"), doc="Gap between a symbol and its text."),
|
|
169
|
+
column_gap=Setting(length(lo=0, percent="em"), doc="Gap between columns."),
|
|
170
|
+
title_gap=Setting(length(lo=0, percent="em"), doc="Gap below the title."),
|
|
171
|
+
inner_pad=Setting(either(literal("auto"), length(lo=0, percent="em")),
|
|
172
|
+
auto=lambda g: (0.5 if g.resolve("frame") else 0.15)
|
|
173
|
+
* g.resolve("font_size"),
|
|
174
|
+
doc="Space inside the box; `\"auto\"`: 0.5em with a frame, 0.15em "
|
|
175
|
+
"without."),
|
|
176
|
+
**groups.Font.embed(only=groups.Font.FACE),
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
def __init__(self, plot: "Plot") -> None:
|
|
180
|
+
self._plot = plot
|
|
181
|
+
super().__init__()
|
|
182
|
+
|
|
183
|
+
def _parent(self) -> Settings:
|
|
184
|
+
return self._plot
|
|
185
|
+
|
|
186
|
+
def pads(self) -> tuple[float, float]:
|
|
187
|
+
"""``pad`` across and down (px), while drawn: `%` of the axes width and height."""
|
|
188
|
+
return (self.resolve("pad", of=self._box("axes_width", "pad")),
|
|
189
|
+
self.resolve("pad", of=self._box("axes_height", "pad")))
|
|
190
|
+
|
|
191
|
+
def set(self, **settings: "Unpack[LegendSettings]") -> Self: # type: ignore[override]
|
|
192
|
+
super().set(**settings)
|
|
193
|
+
set_place(self, dict(settings))
|
|
194
|
+
return self
|
|
195
|
+
|
|
196
|
+
def placement(self) -> tuple[str, Any]:
|
|
197
|
+
"""``("inside", anchor)`` or ``("side", side)``: where the legend goes."""
|
|
198
|
+
return placement(self, side_first=True)
|
|
199
|
+
|
|
200
|
+
def is_placed(self) -> bool:
|
|
201
|
+
"""Whether its place (``inside``, ``side`` or ``visible``) is set on it."""
|
|
202
|
+
return any(self.is_set(k) for k in ("inside", "side", "visible"))
|
|
203
|
+
|
|
204
|
+
def __repr__(self) -> str:
|
|
205
|
+
return f"Legend({self.placement()!r})"
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def _figure_legend_title(legend: "FigureLegend") -> Any:
|
|
209
|
+
"""``FigureLegend.title="auto"``: its plots' legend titles (each the name of the
|
|
210
|
+
`group` column of its series unless set) when they are all the same (otherwise each
|
|
211
|
+
heads its own entries)."""
|
|
212
|
+
plots = legend.plots if legend.plots is not None else legend._figure.plots
|
|
213
|
+
titles = [p.legend.resolve("title") for p in plots]
|
|
214
|
+
return titles[0] if titles and all(t == titles[0] for t in titles) else None
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
class FigureLegend(FigureLegendMixin, Legend):
|
|
218
|
+
"""A legend of the figure (``figure.add_legend``, or made by ``share_legend``): the
|
|
219
|
+
entries of a set of plots, in the lanes beside them as figure titles are.
|
|
220
|
+
|
|
221
|
+
Its plots' cells must fill the grid rectangle they span. Entries come in plot order (row
|
|
222
|
+
by row), then series order; entries with the same label and the same symbol are one,
|
|
223
|
+
the same label with another symbol keeps both. Its plots' legends lose those entries
|
|
224
|
+
(a plot left without entries shows none), unless the plot's legend has ``inside``,
|
|
225
|
+
``side`` or ``visible`` set. A size key whose plots the legend all covers goes into it.
|
|
226
|
+
``side`` and ``align`` place it within the axes of its plots, in the lanes of
|
|
227
|
+
:class:`FigureTitle` (a legend nearer the plots than a title over the same plots; one
|
|
228
|
+
without entries takes no lane). ``share_legend`` (figure setting) makes one per group
|
|
229
|
+
of plots, as ``share_color`` groups them (a group with a plot an ``add_legend`` legend
|
|
230
|
+
covers gets none). ``plot.save()`` leaves figure legends out.
|
|
231
|
+
"""
|
|
232
|
+
|
|
233
|
+
SETTINGS = table(**{
|
|
234
|
+
**{k: s for k, s in Legend.SETTINGS.items()
|
|
235
|
+
if k not in ("inside", "side", "visible", "orientation", "pad", "title")},
|
|
236
|
+
"side": Setting(one_of(*SIDES), doc="`top`, `bottom`, `left` or `right` of its plots."),
|
|
237
|
+
"align": Setting(one_of("auto", *ALIGNS),
|
|
238
|
+
doc="Alignment along the side: `start` (left, or top), `center`, "
|
|
239
|
+
"`end` (right, or bottom), or an end by name: `left`, `right` "
|
|
240
|
+
"on top and bottom, `top`, `bottom` on the left and right; "
|
|
241
|
+
"`auto`: `center`."),
|
|
242
|
+
"order": Setting(number(), doc="Place in its side's lanes, as figure titles' "
|
|
243
|
+
"`order`: lower is nearer the plots."),
|
|
244
|
+
"pad": Setting(length(lo=0), doc="Gap between the legend and the plots."),
|
|
245
|
+
"title": Setting(text, auto=_figure_legend_title,
|
|
246
|
+
doc="Legend title; `\"auto\"`: its plots' legend title when they "
|
|
247
|
+
"share one, else each heads its own entries."),
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
def __init__(self, figure: Any, plots: Any = None, **settings: Any) -> None:
|
|
251
|
+
self._figure = figure
|
|
252
|
+
#: the plots it covers (``None``: every plot)
|
|
253
|
+
self.plots: list[Any] | None = plots
|
|
254
|
+
self._made = next(CREATED)
|
|
255
|
+
Settings.__init__(self, **settings)
|
|
256
|
+
|
|
257
|
+
def _parent(self) -> Settings:
|
|
258
|
+
return self._figure
|
|
259
|
+
|
|
260
|
+
def __repr__(self) -> str:
|
|
261
|
+
return f"FigureLegend({self.get('side')!r})"
|
|
262
|
+
|
|
263
|
+
def along(self) -> str:
|
|
264
|
+
"""``align`` as ``start``, ``center`` or ``end`` (``auto``: ``center``)."""
|
|
265
|
+
a = self.resolve("align")
|
|
266
|
+
return "center" if a == "auto" else align_of(a)
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def _data_ratio(plot: "Plot") -> Any:
|
|
270
|
+
"""1 when a series asks for equal units on both axes (a square Hi-C matrix)."""
|
|
271
|
+
if any(getattr(s, "equal_aspect", lambda: False)() for s in plot.series):
|
|
272
|
+
return 1.0
|
|
273
|
+
return DECLINE
|
|
274
|
+
|
|
275
|
+
|
|
276
|
+
class Plot(PlotMixin, Settings):
|
|
277
|
+
"""One plot: axes, series and decorations.
|
|
278
|
+
|
|
279
|
+
``plt.Plot(**settings)`` creates a one-plot figure (``plot.figure``);
|
|
280
|
+
``figure.plot(…)`` adds a plot to an existing figure.
|
|
281
|
+
|
|
282
|
+
At creation and in ``set``: ``title=`` sets the title text, ``x_axis={…}`` and
|
|
283
|
+
``y_axis={…}`` the axes' settings, ``legend=`` (a place, a dict of legend settings or
|
|
284
|
+
``False``) calls :meth:`add_legend` and ``colorbar=`` (``True``, a place or a dict)
|
|
285
|
+
:meth:`add_colorbar`. A place is a side (``"right"``: ``side=``), an anchor inside the
|
|
286
|
+
axes (``"top_left"``, ``"auto"``: ``inside=``) or ``"none"``. ``plot.title``,
|
|
287
|
+
``plot.x_axis`` and ``plot.y_axis`` are objects: calling one sets its settings and
|
|
288
|
+
returns it. Series are drawn in ``z`` order (default: call order) inside the axes clip.
|
|
289
|
+
|
|
290
|
+
``twin_y()`` (``twin_x()``) returns a plot on the same axes rectangle sharing the x (y)
|
|
291
|
+
axis, limits from the series of both, with its own y axis on the right (x axis on top).
|
|
292
|
+
Its axis adds margin on its side; its series continue the plot's colors and appear in the
|
|
293
|
+
plot's legend (``twin.legend is plot.legend``); background, title, frame and grid are
|
|
294
|
+
the plot's (a twin draws a grid only when its axis asks). ``save`` includes the twins.
|
|
295
|
+
"""
|
|
296
|
+
|
|
297
|
+
SETTINGS = table(
|
|
298
|
+
spines=Setting(_spines,
|
|
299
|
+
doc="Axis lines drawn: any of `left`, `bottom`, `right`, `top`, or "
|
|
300
|
+
"`all`, `none`."),
|
|
301
|
+
data_ratio=Setting(number(positive=True),
|
|
302
|
+
doc="Data aspect ratio: 1 makes one unit as long on both axes; "
|
|
303
|
+
"unset: 1 for a square Hi-C matrix (outside a tracks "
|
|
304
|
+
"figure)."),
|
|
305
|
+
background=Setting(color, doc="Background color of the axes area."),
|
|
306
|
+
**groups.Stroke.embed("border_", only=("color", "line_width"),
|
|
307
|
+
docs={"color": "Color of a frame around the axes area (drawn "
|
|
308
|
+
"under the axis lines); default none.",
|
|
309
|
+
"line_width": "Width of the frame."}),
|
|
310
|
+
clip=Setting(boolean, doc="Clip series to the axes area."),
|
|
311
|
+
axes=Setting(boolean, doc="Draw axes; `False` hides both axes, drops padding "
|
|
312
|
+
"and fixes the aspect to 1 (set by pie charts)."),
|
|
313
|
+
share_x=Setting(_plot, doc="Another plot of the figure whose x axis this plot "
|
|
314
|
+
"shares (groups chain); overrides the figure's `share_x`."),
|
|
315
|
+
share_y=Setting(_plot, doc="Another plot whose y axis this plot shares."),
|
|
316
|
+
vars=Setting(custom_vars, expr=False,
|
|
317
|
+
doc="Custom variables, read by `var(--name)` in any setting here and "
|
|
318
|
+
"below: `{\"accent\": \"#c00\"}`."),
|
|
319
|
+
share=Setting(_plot, doc="Another plot whose x and y axes this plot shares."),
|
|
320
|
+
space_above=Setting(length(lo=0, percent="em"),
|
|
321
|
+
doc="Space above the plot, beyond the figure's `spacing`."),
|
|
322
|
+
**_INHERITED,
|
|
323
|
+
)
|
|
324
|
+
RULES = {"data_ratio": _data_ratio}
|
|
325
|
+
#: Keywords accepted besides settings: ``title="…"`` sets the title text,
|
|
326
|
+
#: ``x_axis={…}`` and ``y_axis={…}`` the axes' settings, ``legend=`` and ``colorbar=``
|
|
327
|
+
#: call :meth:`add_legend` and :meth:`add_colorbar`.
|
|
328
|
+
EXTRA = ("title", "x_axis", "y_axis", "legend", "colorbar")
|
|
329
|
+
#: Of those, the ones a series call making the plot passes on to it (``colorbar=`` is
|
|
330
|
+
#: a series setting there).
|
|
331
|
+
CALL_EXTRA = ("title", "x_axis", "y_axis", "legend")
|
|
332
|
+
|
|
333
|
+
def __init__(self, figure: Any = None, id: Any = None, _twin: Any = None,
|
|
334
|
+
**settings: Unpack["NewPlotSettings"]) -> None:
|
|
335
|
+
from .figure import Figure
|
|
336
|
+
|
|
337
|
+
own, fig_kw = route(dict(settings), [Plot, Figure], extra={0: Plot.EXTRA})
|
|
338
|
+
title = own.pop("title", None)
|
|
339
|
+
axes: dict[str, Any] = {k: own.pop(k) for k in ("x_axis", "y_axis") if k in own}
|
|
340
|
+
guides: dict[str, Any] = {k: own.pop(k) for k in ("legend", "colorbar") if k in own}
|
|
341
|
+
if figure is None:
|
|
342
|
+
figure = Figure(**fig_kw)
|
|
343
|
+
elif fig_kw:
|
|
344
|
+
from .layers.base import upward_message
|
|
345
|
+
raise ValueError(upward_message("Plot", [("figure", "figure", fig_kw)]))
|
|
346
|
+
self.figure = figure
|
|
347
|
+
self.x_axis = Axis(self, "x")
|
|
348
|
+
self.y_axis = Axis(self, "y")
|
|
349
|
+
self.title = Title(self)
|
|
350
|
+
self._titles: list[Title] = []
|
|
351
|
+
#: the legend: a twin's is its main plot's (one legend per axes rectangle)
|
|
352
|
+
self.legend = _twin[0].legend if _twin is not None else Legend(self)
|
|
353
|
+
self.series: list[Any] = []
|
|
354
|
+
self.decorations: list[Any] = []
|
|
355
|
+
self.colorbars: list[Any] = []
|
|
356
|
+
self.twins: list[Plot] = []
|
|
357
|
+
#: ``(main plot, "y" or "x")`` for a twin: the axis it has of its own.
|
|
358
|
+
self.twin_of: tuple[Plot, str] | None = _twin
|
|
359
|
+
super().__init__(**own)
|
|
360
|
+
if title is not None:
|
|
361
|
+
self.title.set(text=title)
|
|
362
|
+
self._set_axes(axes)
|
|
363
|
+
self.id = figure._attach(self, id) if _twin is None else None
|
|
364
|
+
self._set_guides(guides)
|
|
365
|
+
|
|
366
|
+
def _parent(self) -> Settings:
|
|
367
|
+
return self.twin_of[0] if self.twin_of is not None else self.figure
|
|
368
|
+
|
|
369
|
+
def _children(self) -> list[Settings]:
|
|
370
|
+
legend = [] if self.twin_of is not None else [self.legend]
|
|
371
|
+
return [self.title, *self._titles, *legend, self.x_axis, self.y_axis, *self.series,
|
|
372
|
+
*self.decorations, *self.colorbars, *self.twins]
|
|
373
|
+
|
|
374
|
+
@property
|
|
375
|
+
def titles(self) -> list[Title]:
|
|
376
|
+
"""The plot's titles: its main title first, then those of :meth:`add_title`."""
|
|
377
|
+
return [self.title, *self._titles]
|
|
378
|
+
|
|
379
|
+
def add_title(self, text: Any, side: Any = None, **settings: Any) -> Title:
|
|
380
|
+
"""Another title of the plot, on ``side`` (default ``"top"``), stacked outward of
|
|
381
|
+
the titles before it there (``order`` places it elsewhere); returns it."""
|
|
382
|
+
if side is not None:
|
|
383
|
+
settings["side"] = side
|
|
384
|
+
t = Title(self, text=text, **settings)
|
|
385
|
+
self._titles.append(t)
|
|
386
|
+
return t
|
|
387
|
+
|
|
388
|
+
def twin_y(self, **settings: Unpack["PlotSettings"]) -> "Plot":
|
|
389
|
+
"""A plot on the same axes with its own y axis (on the right) and the x axis of
|
|
390
|
+
this plot: add series to it and set its ``y_axis``."""
|
|
391
|
+
return self._twin("y", dict(settings))
|
|
392
|
+
|
|
393
|
+
def twin_x(self, **settings: Unpack["PlotSettings"]) -> "Plot":
|
|
394
|
+
"""A plot on the same axes with its own x axis (on top) and the y axis of this
|
|
395
|
+
plot."""
|
|
396
|
+
return self._twin("x", dict(settings))
|
|
397
|
+
|
|
398
|
+
def _twin(self, role: str, settings: dict[str, Any]) -> "Plot":
|
|
399
|
+
"""A twin with its own ``role`` axis on the far side (its line drawn there with
|
|
400
|
+
the axis) and the other axis hidden."""
|
|
401
|
+
t = Plot(figure=self.figure, _twin=(self, role), **settings)
|
|
402
|
+
own, other = (t.y_axis, t.x_axis) if role == "y" else (t.x_axis, t.y_axis)
|
|
403
|
+
other.set(visible=False)
|
|
404
|
+
if not own.is_set("side"):
|
|
405
|
+
own.set(side="right" if role == "y" else "top")
|
|
406
|
+
if not t.is_set("spines"):
|
|
407
|
+
t.set(spines="left" if role == "y" else "bottom")
|
|
408
|
+
self.twins.append(t)
|
|
409
|
+
return t
|
|
410
|
+
|
|
411
|
+
def get(self, name: str) -> Any:
|
|
412
|
+
"""As :meth:`Settings.get`; ``"title"``: the title's text."""
|
|
413
|
+
if name == "title":
|
|
414
|
+
return self.title.get("text")
|
|
415
|
+
return super().get(name)
|
|
416
|
+
|
|
417
|
+
def set(self, **given: "Unpack[PlotSettings]") -> Self: # type: ignore[override]
|
|
418
|
+
settings = canonical_kwargs(dict(given), self.SETTINGS, "Plot")
|
|
419
|
+
fig_share = {k: settings.pop(k) for k in ("share_x", "share_y", "share")
|
|
420
|
+
if k in settings and settings[k] is not None
|
|
421
|
+
and not isinstance(settings[k], Plot)}
|
|
422
|
+
if fig_share:
|
|
423
|
+
self.figure.set(**fig_share)
|
|
424
|
+
title = settings.pop("title", None)
|
|
425
|
+
if title is not None:
|
|
426
|
+
self.title.set(text=title)
|
|
427
|
+
self._set_axes({k: settings.pop(k) for k in ("x_axis", "y_axis") if k in settings})
|
|
428
|
+
guides: dict[str, Any] = {k: settings.pop(k) for k in ("legend", "colorbar")
|
|
429
|
+
if k in settings}
|
|
430
|
+
super().set(**settings)
|
|
431
|
+
self._set_guides(guides)
|
|
432
|
+
return self
|
|
433
|
+
|
|
434
|
+
def _set_axes(self, axes: dict[str, Any]) -> None:
|
|
435
|
+
"""``x_axis={…}``, ``y_axis={…}``: settings of the plot's axes."""
|
|
436
|
+
for k, v in axes.items():
|
|
437
|
+
if not isinstance(v, dict):
|
|
438
|
+
raise ValueError(f"{k}= takes a dict of axis settings, got {v!r}")
|
|
439
|
+
getattr(self, k).set(**v)
|
|
440
|
+
|
|
441
|
+
def _set_guides(self, guides: dict[str, Any]) -> None:
|
|
442
|
+
"""``legend=``: a place (a side, a place inside the axes, ``"none"``), a dict of
|
|
443
|
+
legend settings or ``False`` (no legend); ``colorbar=``: ``True``, a place or a
|
|
444
|
+
dict of colorbar settings (one more colorbar each time)."""
|
|
445
|
+
legend = guides.get("legend")
|
|
446
|
+
if legend is not None:
|
|
447
|
+
if legend is False:
|
|
448
|
+
self.add_legend(visible=False)
|
|
449
|
+
elif isinstance(legend, dict):
|
|
450
|
+
self.add_legend(**legend)
|
|
451
|
+
elif isinstance(legend, str):
|
|
452
|
+
self.add_legend(legend)
|
|
453
|
+
else:
|
|
454
|
+
raise ValueError(f"legend= takes a place, a dict of legend settings or "
|
|
455
|
+
f"False, got {legend!r}")
|
|
456
|
+
bar = guides.get("colorbar")
|
|
457
|
+
if bar is not None and bar is not False:
|
|
458
|
+
if bar is True:
|
|
459
|
+
self.add_colorbar()
|
|
460
|
+
elif isinstance(bar, dict):
|
|
461
|
+
self.add_colorbar(**bar)
|
|
462
|
+
elif isinstance(bar, str) and "visible" not in where_of(bar):
|
|
463
|
+
self.add_colorbar(None, bar)
|
|
464
|
+
else:
|
|
465
|
+
raise ValueError(f"colorbar= takes True, a place or a dict of colorbar "
|
|
466
|
+
f"settings, got {bar!r}")
|
|
467
|
+
|
|
468
|
+
def __repr__(self) -> str:
|
|
469
|
+
from .layout import arrange
|
|
470
|
+
main = self.twin_of[0] if self.twin_of is not None else self
|
|
471
|
+
cell = next(((a.r0, a.c0) for a in arrange(self.figure)[0] if a.obj is main), None)
|
|
472
|
+
where = f"row {cell[0]}, col {cell[1]}, " if cell is not None else ""
|
|
473
|
+
return f"Plot({where}{len(self.series)} series)"
|
|
474
|
+
|
|
475
|
+
def own_share(self, role: str) -> Any:
|
|
476
|
+
"""The plot this plot's ``role`` axis is paired with, or ``None``."""
|
|
477
|
+
return self.resolve(f"share_{role}") or self.resolve("share")
|
|
478
|
+
|
|
479
|
+
def _add_series(self, s: Any) -> None:
|
|
480
|
+
self.series.append(s)
|
|
481
|
+
from .spec import current
|
|
482
|
+
r = current()
|
|
483
|
+
if r is not None: # added during a render: its calls change
|
|
484
|
+
r.plots.clear()
|
|
485
|
+
r.calls.clear()
|
|
486
|
+
|
|
487
|
+
# Plot types are methods added by ``registry.layers.add``
|
|
488
|
+
# (``p.scatter(…)``, ``p.hist(…)``, ``p.bigwig(…)``…).
|
|
489
|
+
|
|
490
|
+
# -- decorations -----------------------------------------------------------------
|
|
491
|
+
|
|
492
|
+
def hline(self, y: Any, **settings: Unpack["HLineSettings"]) -> "HLine":
|
|
493
|
+
"""A horizontal reference line at ``y``."""
|
|
494
|
+
from .decorations import HLine
|
|
495
|
+
return HLine(self, y, **settings)
|
|
496
|
+
|
|
497
|
+
def vline(self, x: Any, **settings: Unpack["VLineSettings"]) -> "VLine":
|
|
498
|
+
"""A vertical reference line at ``x``."""
|
|
499
|
+
from .decorations import VLine
|
|
500
|
+
return VLine(self, x, **settings)
|
|
501
|
+
|
|
502
|
+
def axline(self, point: Any, slope: Any = None, point2: Any = None,
|
|
503
|
+
**settings: Unpack["AxLineSettings"]) -> "AxLine":
|
|
504
|
+
"""An infinite line through ``point`` with ``slope``, or through ``point2``."""
|
|
505
|
+
from .decorations import AxLine
|
|
506
|
+
return AxLine(self, point, slope, point2, **settings)
|
|
507
|
+
|
|
508
|
+
def hspan(self, y0: Any, y1: Any, **settings: Unpack["HSpanSettings"]) -> "HSpan":
|
|
509
|
+
"""A horizontal band from ``y0`` to ``y1``."""
|
|
510
|
+
from .decorations import HSpan
|
|
511
|
+
return HSpan(self, y0, y1, **settings)
|
|
512
|
+
|
|
513
|
+
def vspan(self, x0: Any, x1: Any, **settings: Unpack["VSpanSettings"]) -> "VSpan":
|
|
514
|
+
"""A vertical band from ``x0`` to ``x1``."""
|
|
515
|
+
from .decorations import VSpan
|
|
516
|
+
return VSpan(self, x0, x1, **settings)
|
|
517
|
+
|
|
518
|
+
def text(self, x: Any, y: Any, content: Any,
|
|
519
|
+
**settings: Unpack["TextSettings"]) -> "Text":
|
|
520
|
+
"""Text at ``(x, y)``."""
|
|
521
|
+
from .decorations import Text
|
|
522
|
+
return Text(self, x, y, content, **settings)
|
|
523
|
+
|
|
524
|
+
def annotate(self, content: Any, xy: Any, xytext: Any = None,
|
|
525
|
+
**settings: Unpack["AnnotateSettings"]) -> "Annotate":
|
|
526
|
+
"""Text at ``xytext`` with an arrow to ``xy``."""
|
|
527
|
+
from .decorations import Annotate
|
|
528
|
+
return Annotate(self, content, xy, xytext, **settings)
|
|
529
|
+
|
|
530
|
+
def bracket(self, a: Any, b: Any, text: Any = None,
|
|
531
|
+
**settings: Unpack["BracketSettings"]) -> "Bracket":
|
|
532
|
+
"""A bracket from ``a`` to ``b`` (categories or positions) with ``text``, or ``p=``
|
|
533
|
+
written as stars or a p-value."""
|
|
534
|
+
from .decorations import Bracket
|
|
535
|
+
return Bracket(self, a, b, text, **settings)
|
|
536
|
+
|
|
537
|
+
def brackets(self, pairs: Any, p: Any = None, text: Any = None,
|
|
538
|
+
**settings: Unpack["BracketSettings"]) -> list["Bracket"]:
|
|
539
|
+
"""A bracket per ``(a, b)`` of ``pairs``, with the p-value (``p``) or text
|
|
540
|
+
(``text``) of the same index; the same settings."""
|
|
541
|
+
from .decorations import Bracket
|
|
542
|
+
pairs = list(pairs)
|
|
543
|
+
for name, v in (("p", p), ("text", text)):
|
|
544
|
+
if v is not None and len(v) != len(pairs):
|
|
545
|
+
raise ValueError(f"brackets: {len(v)} {name} values for {len(pairs)} pairs")
|
|
546
|
+
return [Bracket(self, a, b, None if text is None else text[i],
|
|
547
|
+
p=None if p is None else p[i], **settings)
|
|
548
|
+
for i, (a, b) in enumerate(pairs)]
|
|
549
|
+
|
|
550
|
+
def add_legend(self, where: Any = None, /, **settings: Unpack["LegendSettings"]) -> Legend:
|
|
551
|
+
"""Set the plot's legend (``plot.legend``, shared with its twins) and return it:
|
|
552
|
+
``where`` is a side (``"right"``: ``side=``), a place inside the axes
|
|
553
|
+
(``"top_left"``: ``inside=``) or ``"none"``; then any legend settings."""
|
|
554
|
+
kw: dict[str, Any] = {**where_of(where), **settings} if where is not None \
|
|
555
|
+
else dict(settings)
|
|
556
|
+
return self.legend.set(**kw)
|
|
557
|
+
|
|
558
|
+
def add_colorbar(self, source: Any = None, where: Any = None,
|
|
559
|
+
**settings: Unpack["ColorbarSettings"]) -> "Colorbar":
|
|
560
|
+
"""A colorbar attached to this plot: the guide of the color (or size) axis of
|
|
561
|
+
``source`` (a mapped series or an axis; default: the plot's last mapped series);
|
|
562
|
+
``where``: a side (``side=``, default ``"bottom"``) or a place inside the axes
|
|
563
|
+
(``inside=``); returns it."""
|
|
564
|
+
from .colorbar import Colorbar
|
|
565
|
+
kw: dict[str, Any] = {**where_of(where), **settings} if where is not None \
|
|
566
|
+
else dict(settings)
|
|
567
|
+
cb = Colorbar(source, plot=self, **kw)
|
|
568
|
+
self.colorbars.append(cb)
|
|
569
|
+
return cb
|
|
570
|
+
|
|
571
|
+
# -- output ----------------------------------------------------------------------
|
|
572
|
+
|
|
573
|
+
def draw(self, canvas: Canvas, x: float = 0.0, y: float = 0.0) -> tuple[float, float]:
|
|
574
|
+
"""Draw the plot alone on a canvas (a vec_io page, a
|
|
575
|
+
:class:`~plot_io.canvas.Recording`) with its top-left corner at ``(x, y)``
|
|
576
|
+
(page units); returns its size in px."""
|
|
577
|
+
from . import render
|
|
578
|
+
return render.draw(self.figure, canvas, x, y, only=self)
|
|
579
|
+
|
|
580
|
+
def save(self, file: Any, *, region: Any = None, format: str | None = None,
|
|
581
|
+
**options: Unpack["WriteOptions"]) -> None:
|
|
582
|
+
"""Save the plot alone (see :meth:`Figure.save`)."""
|
|
583
|
+
from . import render
|
|
584
|
+
with self.figure._region(region):
|
|
585
|
+
render.save(self.figure, file, only=self, format=format, **options)
|
|
586
|
+
|
|
587
|
+
def write(self, format: str, *, region: Any = None,
|
|
588
|
+
**options: Unpack["WriteOptions"]) -> bytes:
|
|
589
|
+
"""The plot alone as ``format`` bytes (see :meth:`Figure.write`)."""
|
|
590
|
+
from . import render
|
|
591
|
+
with self.figure._region(region):
|
|
592
|
+
return render.write(self.figure, format, only=self, **options)
|
|
593
|
+
|
|
594
|
+
def _repr_mimebundle_(self, include: Any = None, exclude: Any = None) -> dict[str, str]:
|
|
595
|
+
"""The plot alone as SVG for notebooks (``image/svg+xml`` only)."""
|
|
596
|
+
from . import render
|
|
597
|
+
return render.repr_svg(self.figure, self, include, exclude)
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""The plot types: each a :class:`~plot_io.layers.Series` declared by a ``SPEC`` (its data,
|
|
2
|
+
layers and what the axes need, built by :mod:`plot_io.layers.grammar`) or with a
|
|
3
|
+
``layers()`` of its own, registered as a ``Plot`` method (``registry.layers.add``)."""
|
|
4
|
+
|
|
5
|
+
from ..layers import add_layer
|
|
6
|
+
from ..levels import adopt
|
|
7
|
+
from ..styles import register_class
|
|
8
|
+
from .area import Area
|
|
9
|
+
from .bar import Bar
|
|
10
|
+
from .box import Box
|
|
11
|
+
from .catpoints import Strip, Swarm
|
|
12
|
+
from .contour import Contour
|
|
13
|
+
from .dots import Dots, group_summary
|
|
14
|
+
from .ecdf import Ecdf
|
|
15
|
+
from .errorbar import Errorbar
|
|
16
|
+
from .errors import Errors
|
|
17
|
+
from .fill import Fill
|
|
18
|
+
from .heatmap import Heatmap
|
|
19
|
+
from .hexbin import Hexbin
|
|
20
|
+
from .hist import Hist
|
|
21
|
+
from .hist2d import Hist2d
|
|
22
|
+
from .kde import Kde
|
|
23
|
+
from .kde2d import Kde2d
|
|
24
|
+
from .line import Line
|
|
25
|
+
from .pie import Pie
|
|
26
|
+
from .regression import Regression
|
|
27
|
+
from .scatter import Scatter
|
|
28
|
+
from .stem import Stem
|
|
29
|
+
from .violin import Violin
|
|
30
|
+
|
|
31
|
+
__all__ = ["Area", "Bar", "Box", "Strip", "Swarm", "Contour", "Dots", "group_summary", "Ecdf", "Errorbar",
|
|
32
|
+
"Errors", "Fill", "Heatmap", "Hexbin", "Hist", "Hist2d", "Kde", "Kde2d", "Line",
|
|
33
|
+
"Pie", "Regression", "Scatter", "Stem", "Violin", "TYPES"]
|
|
34
|
+
|
|
35
|
+
#: The plot types by ``Plot`` method name.
|
|
36
|
+
TYPES = {"scatter": Scatter, "line": Line, "fill": Fill, "box": Box, "heatmap": Heatmap,
|
|
37
|
+
"bar": Bar, "hist": Hist, "kde": Kde, "ecdf": Ecdf, "violin": Violin,
|
|
38
|
+
"strip": Strip, "swarm": Swarm, "area": Area, "errorbar": Errorbar, "stem": Stem,
|
|
39
|
+
"regression": Regression, "hist2d": Hist2d, "hexbin": Hexbin, "contour": Contour,
|
|
40
|
+
"pie": Pie, "dots": Dots}
|
|
41
|
+
|
|
42
|
+
for _split in (Line, Fill, Area, Hist, Kde, Ecdf, Regression, Bar, Box, Violin, Strip, Swarm,
|
|
43
|
+
Errorbar, Stem):
|
|
44
|
+
adopt(_split, "split")
|
|
45
|
+
adopt(Scatter, "items")
|
|
46
|
+
Heatmap.LEVELS = Contour.LEVELS = Hexbin.LEVELS = "grid"
|
|
47
|
+
Pie.LEVELS = "refuse"
|
|
48
|
+
|
|
49
|
+
for _name, _cls in TYPES.items():
|
|
50
|
+
add_layer(_name, _cls)
|
|
51
|
+
register_class(Kde2d) # p.kde(x, y): a style class of its own
|