plot-io 0.12.0__py3-none-win_amd64.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.
Files changed (130) hide show
  1. plot_io/__init__.py +73 -0
  2. plot_io/_methods.py +1691 -0
  3. plot_io/_native/__init__.py +199 -0
  4. plot_io/_native/__main__.py +197 -0
  5. plot_io/_native/_api.py +457 -0
  6. plot_io/_native/bin/windows-x86_64.dll +0 -0
  7. plot_io/_native/kernels.py +335 -0
  8. plot_io/_native/manifest.json +49 -0
  9. plot_io/axis.py +1347 -0
  10. plot_io/boxes.py +1156 -0
  11. plot_io/canvas.py +389 -0
  12. plot_io/color/__init__.py +3 -0
  13. plot_io/color/data.py +1646 -0
  14. plot_io/color/maps.py +922 -0
  15. plot_io/color/values.py +295 -0
  16. plot_io/color/variants.py +1246 -0
  17. plot_io/colorbar.py +1426 -0
  18. plot_io/columns.py +614 -0
  19. plot_io/decorations.py +920 -0
  20. plot_io/defaults.py +809 -0
  21. plot_io/density.py +113 -0
  22. plot_io/ext.py +68 -0
  23. plot_io/facets.py +240 -0
  24. plot_io/figure.py +788 -0
  25. plot_io/formatters.py +97 -0
  26. plot_io/genome/__init__.py +17 -0
  27. plot_io/genome/adapters.py +356 -0
  28. plot_io/genome/combine.py +123 -0
  29. plot_io/genome/contacts.py +508 -0
  30. plot_io/genome/intervals.py +750 -0
  31. plot_io/genome/lanes.py +191 -0
  32. plot_io/genome/layout.py +184 -0
  33. plot_io/genome/links.py +364 -0
  34. plot_io/genome/pack.py +27 -0
  35. plot_io/genome/readers.py +145 -0
  36. plot_io/genome/regions.py +228 -0
  37. plot_io/genome/scale.py +349 -0
  38. plot_io/genome/series.py +344 -0
  39. plot_io/genome/signal.py +621 -0
  40. plot_io/genome/sites.py +782 -0
  41. plot_io/genome/sources.py +459 -0
  42. plot_io/genome/testing.py +161 -0
  43. plot_io/geometry.py +289 -0
  44. plot_io/groups.py +308 -0
  45. plot_io/guides.py +503 -0
  46. plot_io/helpers/__init__.py +17 -0
  47. plot_io/helpers/clustermap.py +202 -0
  48. plot_io/helpers/dendrogram.py +172 -0
  49. plot_io/helpers/grids.py +304 -0
  50. plot_io/helpers/qq.py +334 -0
  51. plot_io/helpers/raincloud.py +249 -0
  52. plot_io/helpers/tree.py +214 -0
  53. plot_io/helpers/volcano.py +505 -0
  54. plot_io/hints.py +125 -0
  55. plot_io/kernels.py +740 -0
  56. plot_io/labels.py +566 -0
  57. plot_io/layers/__init__.py +100 -0
  58. plot_io/layers/base.py +630 -0
  59. plot_io/layers/context.py +87 -0
  60. plot_io/layers/core.py +446 -0
  61. plot_io/layers/geoms.py +1362 -0
  62. plot_io/layers/grammar.py +586 -0
  63. plot_io/layers/paint.py +614 -0
  64. plot_io/layers/pipeline.py +170 -0
  65. plot_io/layers/positions.py +431 -0
  66. plot_io/layers/stats.py +1088 -0
  67. plot_io/layout.py +1381 -0
  68. plot_io/legend.py +605 -0
  69. plot_io/levels.py +509 -0
  70. plot_io/limits.py +124 -0
  71. plot_io/lines.py +42 -0
  72. plot_io/mapped.py +522 -0
  73. plot_io/markers.py +110 -0
  74. plot_io/markup.py +128 -0
  75. plot_io/numbers.py +354 -0
  76. plot_io/occupancy.py +295 -0
  77. plot_io/plot.py +597 -0
  78. plot_io/presets/__init__.py +51 -0
  79. plot_io/presets/_util.py +153 -0
  80. plot_io/presets/area.py +145 -0
  81. plot_io/presets/bar.py +261 -0
  82. plot_io/presets/box.py +400 -0
  83. plot_io/presets/catpoints.py +177 -0
  84. plot_io/presets/contour.py +353 -0
  85. plot_io/presets/curves.py +30 -0
  86. plot_io/presets/dots.py +355 -0
  87. plot_io/presets/ecdf.py +97 -0
  88. plot_io/presets/errorbar.py +106 -0
  89. plot_io/presets/errors.py +296 -0
  90. plot_io/presets/fill.py +74 -0
  91. plot_io/presets/heatmap.py +322 -0
  92. plot_io/presets/hexbin.py +198 -0
  93. plot_io/presets/hist.py +391 -0
  94. plot_io/presets/hist2d.py +87 -0
  95. plot_io/presets/kde.py +162 -0
  96. plot_io/presets/kde2d.py +147 -0
  97. plot_io/presets/line.py +143 -0
  98. plot_io/presets/pie.py +502 -0
  99. plot_io/presets/regression.py +115 -0
  100. plot_io/presets/scatter.py +194 -0
  101. plot_io/presets/stem.py +100 -0
  102. plot_io/presets/violin.py +323 -0
  103. plot_io/py.typed +0 -0
  104. plot_io/registry.py +82 -0
  105. plot_io/render.py +480 -0
  106. plot_io/rows.py +491 -0
  107. plot_io/scales.py +103 -0
  108. plot_io/seams.py +138 -0
  109. plot_io/settings.py +807 -0
  110. plot_io/shapes.py +66 -0
  111. plot_io/sharing.py +232 -0
  112. plot_io/sizemaps.py +97 -0
  113. plot_io/spec.py +240 -0
  114. plot_io/states.py +216 -0
  115. plot_io/styles/__init__.py +307 -0
  116. plot_io/styles/library.py +397 -0
  117. plot_io/tables.py +138 -0
  118. plot_io/text.py +412 -0
  119. plot_io/ticks.py +400 -0
  120. plot_io/times.py +287 -0
  121. plot_io/titles.py +225 -0
  122. plot_io/transform.py +114 -0
  123. plot_io/types.py +13604 -0
  124. plot_io/units.py +768 -0
  125. plot_io/validators.py +1029 -0
  126. plot_io/values.py +190 -0
  127. plot_io-0.12.0.dist-info/METADATA +215 -0
  128. plot_io-0.12.0.dist-info/RECORD +130 -0
  129. plot_io-0.12.0.dist-info/WHEEL +4 -0
  130. plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
@@ -0,0 +1,296 @@
1
+ """Error bars: ``Errors`` values, and their layers (segments and caps) on the series
2
+ that take ``x_error``/``y_error`` (scatter, line, errorbar) or ``error`` (bar); the types
3
+ that summarize replicates (``estimate``, ``interval``: :class:`Summarized`)."""
4
+
5
+ import array
6
+ import math
7
+ from typing import Any
8
+
9
+ from vec_io.arrays import doubles
10
+
11
+ from ..color.values import color
12
+ from ..layers.base import Series
13
+ from ..layers.core import Layer, Table
14
+ from ..layers.geoms import GEOMS
15
+ from ..layers.paint import Paint, color_of, role
16
+ from ..units import SettingError, resolve_raw
17
+ from ..validators import length, typed
18
+
19
+ __all__ = ["Errors", "error_value", "error_layers", "Summarized", "errors_of"]
20
+
21
+
22
+ _CAP = length(lo=0, data=True)
23
+ _WIDTH = length(lo=0)
24
+
25
+
26
+ class Errors:
27
+ """Error bars: ``values`` is a value or sequence (symmetric) or a pair
28
+ ``(low, high)``; ``color`` defaults to the series color, ``line_width`` to the
29
+ series', ``cap_length`` is the cap width (a length, `data` units across the error; 0:
30
+ no caps; default: the series' ``cap_length``, else 4 px)."""
31
+
32
+ def __init__(self, values: Any, color: Any = None, line_width: Any = None,
33
+ cap_length: Any = None) -> None:
34
+ self.values = values
35
+ self.color = None if color is None else _color(color)
36
+ self.line_width = None if line_width is None else _WIDTH(line_width)
37
+ self.cap = None if cap_length is None else _CAP(cap_length)
38
+ self._low: Any = None
39
+ self._high: Any = None
40
+ self._owner: Any = None
41
+ self._name = ""
42
+
43
+ def __repr__(self) -> str:
44
+ return "Errors()"
45
+
46
+ def bind(self, n: int, owner: Any = None, name: str = "") -> None:
47
+ """Convert the values for ``n`` points (of setting ``name`` of ``owner``)."""
48
+ v = self.values
49
+ try:
50
+ if _is_pair(v):
51
+ low, high = _column(v[0], n), _column(v[1], n)
52
+ else:
53
+ low = high = _column(v, n)
54
+ except ValueError as e:
55
+ if owner is None:
56
+ raise
57
+ raise SettingError(f"{owner.kind} setting {name!r}: {e}", owner, name) from None
58
+ self._low, self._high = low, high
59
+ self._owner, self._name = owner, name
60
+
61
+ def ends(self, center: Any) -> tuple[array.array, array.array]:
62
+ """The low and high ends at ``center``; their span must be a finite number."""
63
+ lo = array.array("d", (c - e for c, e in zip(center, self._low)))
64
+ hi = array.array("d", (c + e for c, e in zip(center, self._high)))
65
+ ends = [v for v in (*lo, *hi) if math.isfinite(v)]
66
+ if ends and not math.isfinite(max(ends) - min(ends)):
67
+ owner, name = self._owner, self._name
68
+ msg = "errors reach beyond the number range"
69
+ if owner is None:
70
+ raise ValueError(msg)
71
+ raise SettingError(f"{owner.kind} setting {name!r}: {msg}", owner, name)
72
+ return lo, hi
73
+
74
+ def cap_px(self, ctx: Any, owner: Any, horizontal: bool) -> float:
75
+ """Cap width (px): this object's, else the series' ``cap_length``, else 4 px."""
76
+ state, mapper = (ctx.y, ctx.ymap) if horizontal else (ctx.x, ctx.xmap)
77
+ raw = self.cap
78
+ if raw is None:
79
+ if "cap_length" in owner.SETTINGS:
80
+ return owner.resolve("cap_length", scale=_scale(state, mapper),
81
+ time=_is_time(state))
82
+ raw = 4.0
83
+ return resolve_raw(owner, "cap_length", _CAP, raw, scale=_scale(state, mapper),
84
+ time=_is_time(state))
85
+
86
+
87
+ @role("error_source")
88
+ def _error_source(s: Any, ctx: Any, glyph: bool, color: Any = "base", width: str = "scaling",
89
+ simplify: bool = True, opacity: Any = "series",
90
+ marker: Any = None) -> tuple[Any, float, Any, Any]:
91
+ """``(color, width, simplify, opacity)`` of a series' error bars: ``color`` a source
92
+ (``"marker"``: its legend markers' first color), ``width`` a setting (``"scaling"``: the
93
+ series' line width scaling), ``opacity`` ``"series"`` (1 when unset), ``"marker"`` (its
94
+ markers') or a number."""
95
+ if color == "marker":
96
+ g = marker.resolve(ctx, True)
97
+ c = g.first_color("fill") or g.first_color("stroke")
98
+ else:
99
+ c = color_of(s, ctx, color)
100
+ w = s._scaling() if width == "scaling" else s.resolve(width)
101
+ if opacity == "marker":
102
+ opacity = marker.resolve(ctx, False).opacity
103
+ elif opacity == "series":
104
+ o = s.resolve("opacity")
105
+ opacity = 1.0 if o is None else o
106
+ return c, w, s.resolve("simplify") if simplify else None, opacity
107
+
108
+
109
+ @role("error_bars")
110
+ def _error_bars(owner: Any, ctx: Any, glyph: bool, errors: Any = None, source: Any = None,
111
+ caps: Any = None) -> dict[str, Any]:
112
+ """The segments of error bars (``caps``: the axis their caps run across, else the
113
+ bars); the ``Errors``' own color and width before the series' (``source``)."""
114
+ base, width, simplify, opacity = source.resolve(ctx, False)
115
+ e = errors
116
+ c = ctx.color(e.color) if e.color is not None else base
117
+ w = (resolve_raw(owner, "line_width", _WIDTH, e.line_width)
118
+ if e.line_width is not None else width)
119
+ if c is None or not w:
120
+ return {"color": None}
121
+ p = {"color": c, "width": w, "opacity": opacity, "simplify": simplify}
122
+ if caps is not None:
123
+ cap = e.cap_px(ctx, owner, caps == "y")
124
+ if not cap:
125
+ return {"color": None}
126
+ p["tick"] = cap
127
+ return p
128
+
129
+
130
+ def error_layers(owner: Any, e: Errors, xs: Any, ys: Any, horizontal: bool, source: Any,
131
+ flip: bool = False) -> list[Layer]:
132
+ """The layers of error bars at data points ``(xs, ys)`` of a frame: a segment per point
133
+ along x (``horizontal``) or y between the error ends, and caps at both ends (ticks
134
+ across). ``xs`` may be a function of the drawing context (slot centres). ``source``:
135
+ a :class:`~plot_io.layers.paint.Paint` of the series' ``(color, width, simplify,
136
+ opacity)`` (role ``error_source``). ``flip``: the frame's x is the plot's y."""
137
+ center = xs if horizontal else ys
138
+ across_v = ys if horizontal else xs
139
+ lo, hi = e.ends(center)
140
+ if horizontal:
141
+ t = Table(x0=lo, x1=hi)
142
+ ends = [Table(x0=lo), Table(x0=hi)]
143
+ else:
144
+ t = Table(y0=lo, y1=hi)
145
+ ends = [Table(y0=lo), Table(y0=hi)]
146
+ a0, a1 = ("y0", "y1") if horizontal else ("x0", "x1")
147
+ for table in [t] + ends:
148
+ for name in (a0, a1) if table is t else (a0,):
149
+ if callable(across_v):
150
+ table.lazy[name] = across_v
151
+ else:
152
+ table[name] = across_v
153
+ # caps run across the errors: along the frame's y for horizontal errors
154
+ across = ("y" if not flip else "x") if horizontal else ("x" if not flip else "y")
155
+ geom = GEOMS["segments"]
156
+ out = [Layer(geom, t, paint=Paint(owner, "error_bars", errors=e, source=source),
157
+ flip=flip, key=False, occupy=False)]
158
+ caps = Paint(owner, "error_bars", errors=e, source=source, caps=across)
159
+ for table in ends:
160
+ out.append(Layer(geom, table, paint=caps, flip=flip, key=False, occupy=False,
161
+ tick="v" if horizontal else "u"))
162
+ return out
163
+
164
+
165
+ def _scale(state: Any, mapper: Any) -> float | None:
166
+ """px per data unit of a linear, time or category axis (else ``None``)."""
167
+ if state.scale is not None and state.scale.name not in ("linear", "time"):
168
+ return None
169
+ return abs(mapper.a) or None
170
+
171
+
172
+ def _is_time(state: Any) -> bool:
173
+ return state.scale is not None and state.scale.name == "time"
174
+
175
+
176
+ def _is_pair(v: Any) -> bool:
177
+ return isinstance(v, (tuple, list)) and len(v) == 2 and not _is_number(v[0]) \
178
+ and hasattr(v[0], "__len__")
179
+
180
+
181
+ def _is_number(v: Any) -> bool:
182
+ return isinstance(v, (int, float)) and not isinstance(v, bool)
183
+
184
+
185
+ def _color(v: Any) -> Any:
186
+ return color(v)
187
+
188
+
189
+ def _column(v: Any, n: int) -> array.array:
190
+ if _is_number(v):
191
+ return array.array("d", [float(v)] * n)
192
+ col = doubles(v)
193
+ if len(col) != n:
194
+ raise ValueError(f"{len(col)} error values for {n} points")
195
+ return col
196
+
197
+
198
+ @typed("SupportsFloat | Column | Errors")
199
+ def error_value(v: Any) -> Errors:
200
+ if isinstance(v, Errors):
201
+ return v
202
+ if _is_number(v):
203
+ if not (v >= 0 and math.isfinite(v)):
204
+ raise ValueError(f"expected a non-negative error, got {v!r}")
205
+ return Errors(v)
206
+ try:
207
+ for col in (v if _is_pair(v) else (v,)):
208
+ if isinstance(col, (str, bytes, bool)):
209
+ raise ValueError
210
+ doubles(col)
211
+ except (TypeError, ValueError):
212
+ raise ValueError(f"expected an error: a number, a sequence or a pair of sequences, "
213
+ f"got {v!r}") from None
214
+ return Errors(v)
215
+
216
+
217
+ class Summarized(Series):
218
+ """A type taking ``estimate`` (and ``interval``: bar, line, errorbar, scatter; stem
219
+ takes ``estimate`` only): with an
220
+ ``estimate``, :attr:`cols` holds the estimate per position and set (the ``summary``
221
+ stat of the call's rows), and the interval takes the place of the setting ``ERROR``."""
222
+
223
+ #: the error setting the interval replaces
224
+ ERROR = "y_error"
225
+
226
+ @property
227
+ def cols(self) -> Any:
228
+ st = self.summary()
229
+ return self.__dict__["_rows"] if st is None else st["columns"]
230
+
231
+ @cols.setter
232
+ def cols(self, v: Any) -> None:
233
+ self.__dict__["_rows"] = v
234
+
235
+ @property
236
+ def stats(self) -> dict[str, Any] | None:
237
+ """With an ``estimate``, the ``summary`` stat of the call's rows (``x``, ``sets``:
238
+ ``(set, estimate, below, above)``) and its ``columns``; else ``None``."""
239
+ return self.summary()
240
+
241
+ def summary(self) -> dict[str, Any] | None:
242
+ """:attr:`stats` (scatter's ``stats`` are its density's)."""
243
+ from ..layers.stats import STATS, params
244
+ if self.resolve("estimate") is None:
245
+ return None
246
+ st = STATS["summary"]
247
+
248
+ def run() -> dict[str, Any]:
249
+ rows = self.__dict__["_rows"]
250
+ out: dict[str, Any] = st.fn(rows, params(self, "summary"), ())
251
+ out["columns"] = _summary_columns(rows, out)
252
+ return out
253
+ return self.cached("summary", run, st.reads, kind="summary")
254
+
255
+ def set(self, **settings: Any) -> Any:
256
+ super().set(**settings)
257
+ if "interval" not in self.SETTINGS:
258
+ return self
259
+ est, iv = self.resolve("estimate"), self.resolve("interval")
260
+ if iv is None:
261
+ return self
262
+ if est is None:
263
+ raise ValueError(f"{self.kind}: interval needs an estimate")
264
+ if self.is_set(self.ERROR):
265
+ raise ValueError(f"{self.kind}: {self.ERROR}= and interval= both give error bars; "
266
+ "use one")
267
+ if (iv if isinstance(iv, str) else iv[0]) == "ci" and est != "mean":
268
+ raise ValueError(f"{self.kind}: interval='ci' is a t interval of the mean; with "
269
+ f"estimate={est!r} use 'pi'")
270
+ return self
271
+
272
+
273
+ def _summary_columns(rows: Any, out: dict[str, Any]) -> Any:
274
+ """The columns of a summary: its estimates at its positions."""
275
+ from ..columns import Columns, Keys
276
+ name = next(n for n in rows.kinds if n != "x")
277
+ keys = out["keys"]
278
+ x = Keys(out["x"], keys.levels) if keys is not None else array.array("d", out["x"])
279
+ sets = out["sets"]
280
+ y: Any = ({k: e for k, e, _, _ in sets} if out["named"] else
281
+ sets[0][1] if len(sets) == 1 else [e for _, e, _, _ in sets])
282
+ return Columns(rows.kinds, {**rows.args, "x": x, name: y}, rows.options,
283
+ dict(rows.titles), rows.orders, dict(rows.times))
284
+
285
+
286
+ def errors_of(s: Any, name: str) -> Any:
287
+ """The errors of setting ``name`` of ``s``: the interval of its summary in place of
288
+ its ``ERROR`` setting (``Errors``, or one per set; ``None`` when a line draws it as a
289
+ band), else the setting's."""
290
+ if (name != getattr(s, "ERROR", None) or s.resolve("estimate") is None
291
+ or s.resolve("interval") is None):
292
+ return s.resolve(name)
293
+ if "interval_style" in s.SETTINGS and s.resolve("interval_style") == "band":
294
+ return None
295
+ es = [Errors((lo, hi)) for _, _, lo, hi in s.summary()["sets"]]
296
+ return es[0] if len(es) == 1 else es
@@ -0,0 +1,74 @@
1
+ """Fill: the area between two curves (``Band``)."""
2
+
3
+ from typing import Any
4
+
5
+ from .._methods import FillMixin
6
+ from .. import groups
7
+ from ..color.values import color
8
+ from ..layers.base import Series, common
9
+ from ..layers.core import Table
10
+ from ..layers.grammar import Lay, Spec
11
+ from ..settings import Setting, number, one_of, table
12
+ from ..transform import settings_both as coord_settings
13
+ from .curves import curve_settings
14
+
15
+ __all__ = ["Fill"]
16
+
17
+
18
+ class Fill(FillMixin, Series):
19
+ """The area between ``y1`` and ``y2`` (a sequence or a constant) along ``x``;
20
+ ``orientation="horizontal"`` fills between x curves along y (``fill(y, x1, x2)``).
21
+
22
+ ``y1`` may be two columns ``[upper, lower]`` (or an ``(n, 2)`` buffer).
23
+ ``interpolation`` and ``missing`` work as for :class:`Line`, on both edges (a point is
24
+ missing when x, y1 or y2 is), so a band with the settings of its line matches it,
25
+ horizontal ones included. ``color_below`` colors the parts where ``y1`` is below ``y2``
26
+ (a difference between two curves, each side in its color).
27
+
28
+ Example::
29
+
30
+ import math
31
+ x = [i / 10 for i in range(64)]
32
+ y = [math.sin(v) for v in x]
33
+ p = plt.Plot(width=240, height=160)
34
+ p.fill(x, [v + 0.3 for v in y], [v - 0.3 for v in y])
35
+ p.line(x, y)
36
+ """
37
+
38
+ SETTINGS = table(
39
+ **coord_settings(),
40
+ color=Setting(color, doc="Fill color; default: the plot's next color."),
41
+ color_below=Setting(color, doc="Fill color where `y1` is below `y2` (crossings "
42
+ "found on straight segments); default `color` "
43
+ "everywhere."),
44
+ opacity=Setting(number(0, 1), inherited=True,
45
+ doc="Opacity; default 0.3 × the plot's."),
46
+ **groups.Stroke.embed("edge_",
47
+ only=("color", "line_width"),
48
+ docs={"color": "Outline color; default none.",
49
+ "line_width": "Outline width; default the line width."}),
50
+ **curve_settings(),
51
+ orientation=Setting(one_of("vertical", "horizontal"),
52
+ doc="`vertical`: between y curves; `horizontal`: between x "
53
+ "curves."),
54
+ **common("name", "z", "rasterize", "simplify", "font_size", "font_family",
55
+ "text_color"),
56
+ )
57
+
58
+ DATA = {"x": "position", "y1": "curve", "y2": "curve"}
59
+
60
+ def __init__(self, x: Any, y1: Any, y2: Any = 0.0, plot: Any = None,
61
+ **settings: Any) -> None:
62
+ super().__init__(plot=plot, **settings)
63
+
64
+ @property
65
+ def v2(self) -> Any:
66
+ """``y2``: a column, or a constant."""
67
+ return self.cols["y2"]
68
+
69
+ SPEC = Spec(
70
+ [Lay("band", ("band", {"opacity": "opacity", "edges": True,
71
+ "extra": ("simplify", "interpolation", "missing")}))],
72
+ tables={"data": lambda s, b: Table(x=s.cols["x"], y1=s.cols["y1"], y2=s.cols["y2"])},
73
+ key={"fill": "faded", "stroke": "edge_color", "when": "edge_width"},
74
+ zero=("y",))