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.
- 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-x86_64.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
|
@@ -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
|
plot_io/presets/fill.py
ADDED
|
@@ -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",))
|