fluxplot 0.1.0__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.
- fluxplot/__init__.py +115 -0
- fluxplot/_fieldmap.py +97 -0
- fluxplot/_mesh_reduce.py +54 -0
- fluxplot/_scene3d_size.py +95 -0
- fluxplot/_viewer/THIRD-PARTY.txt +23 -0
- fluxplot/_viewer/flux-model3d-viewer.min.js +4221 -0
- fluxplot/_viewer/stamp.json +4 -0
- fluxplot/api.py +1196 -0
- fluxplot/autotag.py +164 -0
- fluxplot/base.mplstyle +0 -0
- fluxplot/brackets.py +242 -0
- fluxplot/canonical_json.py +23 -0
- fluxplot/capture.py +150 -0
- fluxplot/colorcheck.py +285 -0
- fluxplot/colors.py +727 -0
- fluxplot/colorscale.py +477 -0
- fluxplot/data.py +178 -0
- fluxplot/definitions/colormaps.json +1639 -0
- fluxplot/definitions/flexoki.tokens.json +2571 -0
- fluxplot/definitions/palettes.json +2547 -0
- fluxplot/descriptors.py +87 -0
- fluxplot/fields.py +611 -0
- fluxplot/fits.py +240 -0
- fluxplot/glb.py +84 -0
- fluxplot/ids.py +173 -0
- fluxplot/images.py +362 -0
- fluxplot/integrity.py +27 -0
- fluxplot/manifest.py +788 -0
- fluxplot/mesh3d.py +376 -0
- fluxplot/panels.py +284 -0
- fluxplot/postprocess.py +638 -0
- fluxplot/presets.py +66 -0
- fluxplot/provenance.py +177 -0
- fluxplot/raster.py +295 -0
- fluxplot/recipe.py +178 -0
- fluxplot/render.py +66 -0
- fluxplot/roles.py +147 -0
- fluxplot/scene3d.py +386 -0
- fluxplot/scene3d_manifest.py +112 -0
- fluxplot/scene3d_viewer.py +633 -0
- fluxplot/schemas/.gitkeep +0 -0
- fluxplot/schemas/manifest.schema.json +2479 -0
- fluxplot/schemas/recipe.schema.json +179 -0
- fluxplot/schemas/scene3d.schema.json +461 -0
- fluxplot/seaborn_adapters.py +323 -0
- fluxplot/signature_fluxplots/__init__.py +18 -0
- fluxplot/signature_fluxplots/_colour.py +412 -0
- fluxplot/signature_fluxplots/fluxbox.py +433 -0
- fluxplot/signature_fluxplots/glowbar.py +769 -0
- fluxplot/signature_fluxplots/hexmatrix.py +927 -0
- fluxplot/stats/__init__.py +63 -0
- fluxplot/stats/_common.py +196 -0
- fluxplot/stats/multi_group.py +443 -0
- fluxplot/stats/paired.py +209 -0
- fluxplot/stats/two_group.py +149 -0
- fluxplot/style.py +469 -0
- fluxplot/surface.py +487 -0
- fluxplot/surface3d.py +197 -0
- fluxplot/tagger.py +561 -0
- fluxplot/version.py +19 -0
- fluxplot-0.1.0.dist-info/METADATA +1199 -0
- fluxplot-0.1.0.dist-info/RECORD +65 -0
- fluxplot-0.1.0.dist-info/WHEEL +4 -0
- fluxplot-0.1.0.dist-info/licenses/LICENSE +21 -0
- fluxplot-0.1.0.dist-info/licenses/THIRD_PARTY_NOTICES.md +472 -0
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
"""Exact identity for seaborn's categorical plots (:func:`fluxplot.tag_seaborn`).
|
|
2
|
+
|
|
3
|
+
seaborn draws a categorical plot as anonymous artists in a fixed order: one ``BoxPlotContainer``
|
|
4
|
+
per hue level, one violin body per (category, hue) pair, one strip / swarm collection per
|
|
5
|
+
category with the hue levels mixed inside it, one mean line plus its error lines per hue level.
|
|
6
|
+
Given the plot kind — and, for the row-wise splits, the frame it was drawn from — those artists
|
|
7
|
+
can be named exactly: the hue levels in seaborn's own ``categorical_order``, the categories in
|
|
8
|
+
tick order, and a mixed collection's points by the frame rows they came from
|
|
9
|
+
(``Mark.data["point_subset"]``). Nothing is inferred from colour or geometry.
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import warnings
|
|
14
|
+
from typing import Any, Dict, List, Optional
|
|
15
|
+
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
from .descriptors import Mark
|
|
19
|
+
|
|
20
|
+
CATEGORICAL_KINDS = ("boxplot", "violinplot", "stripplot", "swarmplot", "pointplot")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def categorical_order(values) -> list:
|
|
24
|
+
"""seaborn's category order: a Categorical's categories, else sorted numbers, else the order
|
|
25
|
+
of appearance (``seaborn._core.rules.categorical_order`` when seaborn is importable)."""
|
|
26
|
+
try:
|
|
27
|
+
from seaborn._core.rules import categorical_order as _order
|
|
28
|
+
import pandas as pd
|
|
29
|
+
return list(_order(pd.Series(values)))
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
seen = list(dict.fromkeys(v for v in list(values) if not _missing(v)))
|
|
33
|
+
try:
|
|
34
|
+
if all(isinstance(v, (int, float, np.number)) for v in seen):
|
|
35
|
+
return sorted(seen)
|
|
36
|
+
except TypeError:
|
|
37
|
+
pass
|
|
38
|
+
return seen
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _missing(v) -> bool:
|
|
42
|
+
if v is None:
|
|
43
|
+
return True
|
|
44
|
+
try:
|
|
45
|
+
return bool(np.isnan(v))
|
|
46
|
+
except (TypeError, ValueError):
|
|
47
|
+
return False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _column(data, key) -> list:
|
|
51
|
+
if key is None:
|
|
52
|
+
return []
|
|
53
|
+
try:
|
|
54
|
+
col = data[key]
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
raise KeyError(f"tag_seaborn: {key!r} is not a column of data") from exc
|
|
57
|
+
for attr in ("to_list", "tolist"):
|
|
58
|
+
if hasattr(col, attr):
|
|
59
|
+
return list(getattr(col, attr)())
|
|
60
|
+
return list(col)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_numeric(values) -> bool:
|
|
64
|
+
try:
|
|
65
|
+
return all(isinstance(v, (int, float, np.number)) and not isinstance(v, bool) for v in values if not _missing(v))
|
|
66
|
+
except TypeError:
|
|
67
|
+
return False
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class Frame:
|
|
71
|
+
"""What the frame says about a categorical plot: the categorical variable and its order, the
|
|
72
|
+
hue levels and, per row, the (category, hue) it belongs to."""
|
|
73
|
+
|
|
74
|
+
def __init__(self, data, x, y, hue, order=None, hue_order=None, orient=None):
|
|
75
|
+
xs, ys = _column(data, x), _column(data, y)
|
|
76
|
+
if orient is None:
|
|
77
|
+
if x is not None and y is not None:
|
|
78
|
+
orient = "h" if _is_numeric(xs) and not _is_numeric(ys) else "v"
|
|
79
|
+
else:
|
|
80
|
+
orient = "v" if x is not None else "h"
|
|
81
|
+
self.orient = orient
|
|
82
|
+
self.cat_var, self.val_var = (x, y) if orient == "v" else (y, x)
|
|
83
|
+
cats = _column(data, self.cat_var)
|
|
84
|
+
vals = _column(data, self.val_var)
|
|
85
|
+
self.hue_var = hue
|
|
86
|
+
hues = _column(data, hue) if hue is not None else None
|
|
87
|
+
self.categories = list(order) if order is not None else categorical_order(cats)
|
|
88
|
+
self.hues = (list(hue_order) if hue_order is not None else categorical_order(hues)) if hue is not None else None
|
|
89
|
+
n = len(cats) if cats else len(vals)
|
|
90
|
+
# rows seaborn keeps, in frame order: the value, the category and the hue all present
|
|
91
|
+
self.rows: List[int] = []
|
|
92
|
+
self.cat_of: Dict[int, Any] = {}
|
|
93
|
+
self.hue_of: Dict[int, Any] = {}
|
|
94
|
+
for i in range(n):
|
|
95
|
+
c = cats[i] if cats else None
|
|
96
|
+
v = vals[i] if vals else None
|
|
97
|
+
h = hues[i] if hues is not None else None
|
|
98
|
+
if (cats and _missing(c)) or (vals and _missing(v)) or (hues is not None and _missing(h)):
|
|
99
|
+
continue
|
|
100
|
+
if cats and c not in self.categories:
|
|
101
|
+
continue
|
|
102
|
+
if hues is not None and h not in self.hues:
|
|
103
|
+
continue
|
|
104
|
+
self.rows.append(i)
|
|
105
|
+
self.cat_of[i] = c
|
|
106
|
+
self.hue_of[i] = h
|
|
107
|
+
|
|
108
|
+
def rows_in(self, category) -> List[int]:
|
|
109
|
+
return [i for i in self.rows if self.cat_of.get(i) == category]
|
|
110
|
+
|
|
111
|
+
@property
|
|
112
|
+
def n_hues(self) -> int:
|
|
113
|
+
return len(self.hues) if self.hues else 1
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _names(values) -> List[str]:
|
|
117
|
+
return [str(v) for v in values]
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
def _untagged(artists, already):
|
|
121
|
+
return [a for a in artists if id(a) not in already and a.get_visible()]
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def tag_categorical(ax, plot: str, *, frame: Optional[Frame], categories, hues, reg, already, tagged, pin) -> None:
|
|
125
|
+
"""Tag one of seaborn's categorical plot kinds. ``categories`` / ``hues`` are the levels in
|
|
126
|
+
seaborn's order (from the frame, or from the tick labels / legend when there is no frame)."""
|
|
127
|
+
cats = _names(categories)
|
|
128
|
+
hue_names = _names(hues) if hues else None
|
|
129
|
+
if plot == "boxplot":
|
|
130
|
+
_tag_boxes(ax, cats, hue_names, reg, already, tagged, pin)
|
|
131
|
+
elif plot == "violinplot":
|
|
132
|
+
_tag_violins(ax, cats, hue_names, reg, already, tagged, pin)
|
|
133
|
+
elif plot in ("stripplot", "swarmplot"):
|
|
134
|
+
_tag_strips(ax, cats, hue_names, frame, categories, reg, already, tagged, pin)
|
|
135
|
+
elif plot == "pointplot":
|
|
136
|
+
_tag_points(ax, cats, hue_names, reg, already, tagged, pin)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _record(tagged, name, role):
|
|
140
|
+
tagged.setdefault(str(name), []).append(role)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
# ---------------------------------------------------------------------------------------------
|
|
144
|
+
# box plots: one BoxPlotContainer per hue level, k boxes each
|
|
145
|
+
# ---------------------------------------------------------------------------------------------
|
|
146
|
+
def _tag_boxes(ax, cats, hues, reg, already, tagged, pin):
|
|
147
|
+
containers = [c for c in getattr(ax, "containers", []) if type(c).__name__ == "BoxPlotContainer"]
|
|
148
|
+
containers = [c for c in containers if any(id(b) not in already for b in c.boxes)]
|
|
149
|
+
if not containers:
|
|
150
|
+
return
|
|
151
|
+
if hues is not None and len(containers) != len(hues):
|
|
152
|
+
warnings.warn(f"tag_seaborn: {len(containers)} box containers for {len(hues)} hue levels; boxes left as extras", stacklevel=3)
|
|
153
|
+
return
|
|
154
|
+
if hues is None and len(containers) != 1:
|
|
155
|
+
warnings.warn("tag_seaborn: several box containers without hue=; boxes left as extras", stacklevel=3)
|
|
156
|
+
return
|
|
157
|
+
for j, cont in enumerate(containers):
|
|
158
|
+
boxes = list(cont.boxes)
|
|
159
|
+
if len(boxes) != len(cats):
|
|
160
|
+
warnings.warn(f"tag_seaborn: {len(boxes)} boxes for {len(cats)} categories; boxes left as extras", stacklevel=3)
|
|
161
|
+
return
|
|
162
|
+
for i, cat in enumerate(cats):
|
|
163
|
+
series = hues[j] if hues is not None else cat
|
|
164
|
+
name = cat if hues is not None else None
|
|
165
|
+
pin(series, boxes[i], lambda a, c: a.set_facecolor(c))
|
|
166
|
+
reg.add(Mark(role="box", series=series, name=name, kind="box", label=series, artists=[boxes[i]]))
|
|
167
|
+
_record(tagged, series, "box")
|
|
168
|
+
parts = (("whisker", list(cont.whiskers)[2 * i:2 * i + 2]), ("cap", list(cont.caps)[2 * i:2 * i + 2]),
|
|
169
|
+
("median", list(cont.medians)[i:i + 1]), ("flier", list(cont.fliers)[i:i + 1]),
|
|
170
|
+
("mean", list(getattr(cont, "means", []))[i:i + 1]))
|
|
171
|
+
for role, arts in parts:
|
|
172
|
+
arts = [a for a in arts if a is not None]
|
|
173
|
+
if not arts:
|
|
174
|
+
continue
|
|
175
|
+
part_name = f"{cat}-{role}" if hues is not None else None
|
|
176
|
+
reg.add(Mark(role=role, series=series, name=part_name, kind="box", artists=arts))
|
|
177
|
+
_record(tagged, series, role)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
# ---------------------------------------------------------------------------------------------
|
|
181
|
+
# violins: one body (PolyCollection) per (category, hue), category-major; inner lines follow
|
|
182
|
+
# ---------------------------------------------------------------------------------------------
|
|
183
|
+
def _tag_violins(ax, cats, hues, reg, already, tagged, pin):
|
|
184
|
+
from matplotlib.collections import PolyCollection
|
|
185
|
+
bodies = _untagged([c for c in ax.collections if isinstance(c, PolyCollection)], already)
|
|
186
|
+
n_h = len(hues) if hues else 1
|
|
187
|
+
expected = len(cats) * n_h
|
|
188
|
+
if len(bodies) != expected:
|
|
189
|
+
warnings.warn(f"tag_seaborn: {len(bodies)} violin bodies for {len(cats)} categories × {n_h} hue levels; left as extras", stacklevel=3)
|
|
190
|
+
return
|
|
191
|
+
lines = _untagged([ln for ln in ax.lines if len(ln.get_xdata())], already)
|
|
192
|
+
per = len(lines) // expected if expected and len(lines) % expected == 0 else 0
|
|
193
|
+
# seaborn iterates the categorical variable outermost, then hue: (a, p), (a, q), (b, p), …
|
|
194
|
+
for k, body in enumerate(bodies):
|
|
195
|
+
cat, hue = cats[k // n_h], (hues[k % n_h] if hues else None)
|
|
196
|
+
series = hue if hues is not None else cat
|
|
197
|
+
name = cat if hues is not None else None
|
|
198
|
+
pin(series, body, lambda a, c: a.set_facecolor(c))
|
|
199
|
+
reg.add(Mark(role="violin", series=series, name=name, kind="violin", label=series, artists=[body]))
|
|
200
|
+
_record(tagged, series, "violin")
|
|
201
|
+
if per:
|
|
202
|
+
inner = lines[k * per:(k + 1) * per]
|
|
203
|
+
reg.add(Mark(role="segment", series=series, name=f"{cat}-inner" if hues is not None else "inner",
|
|
204
|
+
kind="violin", artists=inner))
|
|
205
|
+
_record(tagged, series, "segment")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ---------------------------------------------------------------------------------------------
|
|
209
|
+
# strips / swarms: one PathCollection per category (hue levels mixed inside, split by rows) or
|
|
210
|
+
# per (category, hue) when dodged
|
|
211
|
+
# ---------------------------------------------------------------------------------------------
|
|
212
|
+
def _tag_strips(ax, cats, hues, frame, categories, reg, already, tagged, pin):
|
|
213
|
+
from matplotlib.collections import PathCollection, PolyCollection
|
|
214
|
+
colls = _untagged([c for c in ax.collections if isinstance(c, PathCollection) and not isinstance(c, PolyCollection)], already)
|
|
215
|
+
n_h = len(hues) if hues else 1
|
|
216
|
+
if len(colls) == len(cats) * n_h and n_h > 1:
|
|
217
|
+
# dodged: one collection per (category, hue), category-major
|
|
218
|
+
for k, coll in enumerate(colls):
|
|
219
|
+
cat, hue = cats[k // n_h], hues[k % n_h]
|
|
220
|
+
_point_mark(reg, coll, series=hue, name=cat, tagged=tagged, pin=pin)
|
|
221
|
+
return
|
|
222
|
+
if len(colls) != len(cats):
|
|
223
|
+
warnings.warn(f"tag_seaborn: {len(colls)} point collections for {len(cats)} categories; left as extras", stacklevel=3)
|
|
224
|
+
return
|
|
225
|
+
for i, (cat, coll) in enumerate(zip(cats, colls)):
|
|
226
|
+
if hues is None:
|
|
227
|
+
_point_mark(reg, coll, series=cat, name=None, tagged=tagged, pin=pin)
|
|
228
|
+
continue
|
|
229
|
+
if frame is None:
|
|
230
|
+
warnings.warn("tag_seaborn: hue levels inside one collection need data= to split by row; tagging per category", stacklevel=3)
|
|
231
|
+
_point_mark(reg, coll, series=cat, name=None, tagged=tagged, pin=pin)
|
|
232
|
+
continue
|
|
233
|
+
rows = frame.rows_in(categories[i])
|
|
234
|
+
off = coll.get_offsets()
|
|
235
|
+
if len(rows) != len(off):
|
|
236
|
+
warnings.warn(f"tag_seaborn: category {cat!r} draws {len(off)} points but the frame has {len(rows)} rows; tagging per category", stacklevel=3)
|
|
237
|
+
_point_mark(reg, coll, series=cat, name=None, tagged=tagged, pin=pin)
|
|
238
|
+
continue
|
|
239
|
+
x, y = [float(v) for v in off[:, 0]], [float(v) for v in off[:, 1]]
|
|
240
|
+
for hue, level in zip(hues, frame.hues):
|
|
241
|
+
subset = [k for k, r in enumerate(rows) if frame.hue_of[r] == level]
|
|
242
|
+
if not subset:
|
|
243
|
+
continue
|
|
244
|
+
reg.add(Mark(role="point", series=hue, name=cat, kind="scatter", x=x, y=y, artists=[coll], indexed=True,
|
|
245
|
+
data={"point_subset": subset}))
|
|
246
|
+
_record(tagged, hue, "point")
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def _point_mark(reg, coll, *, series, name, tagged, pin):
|
|
250
|
+
pin(series, coll, lambda a, c: a.set_facecolor(c))
|
|
251
|
+
off = coll.get_offsets()
|
|
252
|
+
x, y = [float(v) for v in off[:, 0]], [float(v) for v in off[:, 1]]
|
|
253
|
+
reg.add(Mark(role="point", series=series, name=name, kind="scatter", x=x, y=y, artists=[coll], indexed=True, live_data=True))
|
|
254
|
+
_record(tagged, series, "point")
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def split_scatter(reg, coll, frame: Frame, tagged) -> bool:
|
|
258
|
+
"""``scatterplot(hue=)``: one collection for every hue level. Register one point mark per
|
|
259
|
+
level owning the rows of that level (``point_subset``); the collection stays one artist."""
|
|
260
|
+
off = coll.get_offsets()
|
|
261
|
+
if len(frame.rows) != len(off):
|
|
262
|
+
warnings.warn(f"tag_seaborn: the scatter draws {len(off)} points but the frame keeps {len(frame.rows)} rows; not split by hue", stacklevel=3)
|
|
263
|
+
return False
|
|
264
|
+
x, y = [float(v) for v in off[:, 0]], [float(v) for v in off[:, 1]]
|
|
265
|
+
for level in frame.hues:
|
|
266
|
+
subset = [k for k, r in enumerate(frame.rows) if frame.hue_of[r] == level]
|
|
267
|
+
if not subset:
|
|
268
|
+
continue
|
|
269
|
+
reg.add(Mark(role="point", series=str(level), kind="scatter", x=x, y=y, artists=[coll], indexed=True,
|
|
270
|
+
data={"point_subset": subset}))
|
|
271
|
+
_record(tagged, level, "point")
|
|
272
|
+
return True
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
# ---------------------------------------------------------------------------------------------
|
|
276
|
+
# point plots: per hue level a mean line (with markers) followed by its error lines
|
|
277
|
+
# ---------------------------------------------------------------------------------------------
|
|
278
|
+
def _tag_points(ax, cats, hues, reg, already, tagged, pin):
|
|
279
|
+
from matplotlib.lines import Line2D
|
|
280
|
+
lines = _untagged([ln for ln in ax.lines if len(ln.get_xdata())], already)
|
|
281
|
+
n_h = len(hues) if hues else 1
|
|
282
|
+
if not lines or len(lines) % n_h:
|
|
283
|
+
warnings.warn(f"tag_seaborn: {len(lines)} lines for {n_h} hue levels in a pointplot; left as extras", stacklevel=3)
|
|
284
|
+
return
|
|
285
|
+
per = len(lines) // n_h
|
|
286
|
+
for j in range(n_h):
|
|
287
|
+
chunk = lines[j * per:(j + 1) * per]
|
|
288
|
+
mean = chunk[0]
|
|
289
|
+
series = hues[j] if hues else (ax.get_ylabel() or "means")
|
|
290
|
+
pin(series, mean, lambda a, c: (a.set_color(c), a.set_markerfacecolor(c), a.set_markeredgecolor(c)))
|
|
291
|
+
has_line = str(mean.get_linestyle()).lower() not in ("none", "", " ")
|
|
292
|
+
has_marker = str(mean.get_marker()).lower() not in ("none", "", " ")
|
|
293
|
+
if has_marker:
|
|
294
|
+
pts = Line2D([], [])
|
|
295
|
+
pts.update_from(mean)
|
|
296
|
+
pts.set_zorder(mean.get_zorder())
|
|
297
|
+
pts.set_data(mean.get_xdata(orig=False), mean.get_ydata(orig=False))
|
|
298
|
+
pts.set_linestyle("none")
|
|
299
|
+
pts.set_label("_nolegend_")
|
|
300
|
+
ax.add_line(pts)
|
|
301
|
+
reg.add(Mark(role="point", series=series, kind="line", live_data=True, x=None, y=None, artists=[pts], indexed=True))
|
|
302
|
+
_record(tagged, series, "point")
|
|
303
|
+
if has_line:
|
|
304
|
+
mean.set_marker("none")
|
|
305
|
+
else:
|
|
306
|
+
mean.set_visible(False)
|
|
307
|
+
if has_line:
|
|
308
|
+
gx, gy = mean.get_data()
|
|
309
|
+
reg.add(Mark(role="line", series=series, kind="line", x=[float(v) for v in gx], y=[float(v) for v in gy],
|
|
310
|
+
live_data=True, label=series, artists=[mean]))
|
|
311
|
+
_record(tagged, series, "line")
|
|
312
|
+
errs = chunk[1:]
|
|
313
|
+
if errs:
|
|
314
|
+
for ln in errs:
|
|
315
|
+
pin(series, ln, lambda a, c: a.set_color(c))
|
|
316
|
+
reg.add(Mark(role="errorbar", series=series, kind="errorbar", artists=errs))
|
|
317
|
+
_record(tagged, series, "errorbar")
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
def tick_categories(ax, orient: str = "v") -> List[str]:
|
|
321
|
+
"""The categories seaborn wrote as tick labels (when no frame was given)."""
|
|
322
|
+
axis = ax.xaxis if orient == "v" else ax.yaxis
|
|
323
|
+
return [t.get_text() for t in axis.get_ticklabels() if t.get_text()]
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"""Signature fluxplots — preset plot types that are unique to Flux.
|
|
2
|
+
|
|
3
|
+
Each one is a complete, opinionated plot built from ordinary matplotlib artists and tagged part by
|
|
4
|
+
part, so it saves, validates and round-trips through Flux like any hand-built FluxPlot. They take a
|
|
5
|
+
DataFrame plus column names, seaborn-style, and are re-exported at the top level (``fp.glowbar``).
|
|
6
|
+
|
|
7
|
+
* :func:`glowbar` — individual points beside a glowing interval bar with a mean line and a median
|
|
8
|
+
notch; fixed per-unit lanes and colours, optional paired connectors.
|
|
9
|
+
* :func:`fluxbox` — the glowbar with a box plot for its summary: a slim translucent box (Q1–Q3)
|
|
10
|
+
with a solid median line, a mean notch and capless whiskers.
|
|
11
|
+
* :func:`hexmatrix` — hexagonal binning (counts, densities, reductions of a third variable) or a 2D
|
|
12
|
+
array on a hex lattice; every hexagon a named part, with marginals, colour key and log axes.
|
|
13
|
+
"""
|
|
14
|
+
from .fluxbox import FluxboxResult, fluxbox
|
|
15
|
+
from .hexmatrix import HexMatrixResult, hexmatrix
|
|
16
|
+
from .glowbar import GlowbarResult, even_shades, glowbar, interleaved_order
|
|
17
|
+
|
|
18
|
+
__all__ = ["glowbar", "GlowbarResult", "fluxbox", "FluxboxResult", "hexmatrix", "HexMatrixResult", "even_shades", "interleaved_order"]
|