anyplotlib 0.7.0__py3-none-any.whl → 0.7.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- anyplotlib/figure/_figure.py +27 -3
- anyplotlib/keys.py +5 -8
- anyplotlib/plot2d/_plot2d.py +64 -5
- {anyplotlib-0.7.0.dist-info → anyplotlib-0.7.2.dist-info}/METADATA +1 -1
- {anyplotlib-0.7.0.dist-info → anyplotlib-0.7.2.dist-info}/RECORD +7 -7
- {anyplotlib-0.7.0.dist-info → anyplotlib-0.7.2.dist-info}/WHEEL +0 -0
- {anyplotlib-0.7.0.dist-info → anyplotlib-0.7.2.dist-info}/licenses/LICENSE +0 -0
anyplotlib/figure/_figure.py
CHANGED
|
@@ -300,7 +300,7 @@ class Figure(anywidget.AnyWidget, _EventMixin):
|
|
|
300
300
|
self._push(pid)
|
|
301
301
|
self._push_layout()
|
|
302
302
|
|
|
303
|
-
def _push(self, panel_id: str) -> None:
|
|
303
|
+
def _push(self, panel_id: str, *, resolve_pixels: bool = False) -> None:
|
|
304
304
|
"""Serialise one panel and write to its trait.
|
|
305
305
|
|
|
306
306
|
Inside a :meth:`batch` block, pushes are coalesced: each panel is
|
|
@@ -309,6 +309,14 @@ class Figure(anywidget.AnyWidget, _EventMixin):
|
|
|
309
309
|
many per-frame pushes of a linked-view update (set_data + set_title +
|
|
310
310
|
widget moves on the same panel) into one serialise/transfer per panel
|
|
311
311
|
— the dominant cost over a Pyodide comm boundary.
|
|
312
|
+
|
|
313
|
+
Parameters
|
|
314
|
+
----------
|
|
315
|
+
resolve_pixels : bool, optional
|
|
316
|
+
Force pixel change-tokens to be materialised to inline base64 even
|
|
317
|
+
when the binary transport is live. Set by :meth:`_sync_for_export`
|
|
318
|
+
because a snapshot has no PLOTBIN channel to carry the bytes; see
|
|
319
|
+
the token discussion below.
|
|
312
320
|
"""
|
|
313
321
|
plot = self._plots_map.get(panel_id)
|
|
314
322
|
if plot is None:
|
|
@@ -329,7 +337,15 @@ class Figure(anywidget.AnyWidget, _EventMixin):
|
|
|
329
337
|
# save_html / Jupyter figure with no binary channel — materialise the
|
|
330
338
|
# real base64 inline so the pixels actually travel. ``_binary_wire``
|
|
331
339
|
# matches the producer's gate (``Plot2D._encode_pixels``).
|
|
332
|
-
|
|
340
|
+
#
|
|
341
|
+
# ``resolve_pixels`` overrides that gate for an EXPORT. A snapshot is by
|
|
342
|
+
# definition off-wire: whatever PLOTBIN would have delivered never
|
|
343
|
+
# arrives, so a token left in the state is a dangling reference and the
|
|
344
|
+
# pixels are simply lost. The gate reads a process-global env var, which
|
|
345
|
+
# is on in a host app even while THIS push is serialising a snapshot —
|
|
346
|
+
# so the export has to say so explicitly.
|
|
347
|
+
if (geom_keys and (resolve_pixels or not _binary_wire())
|
|
348
|
+
and hasattr(plot, "resolve_pixel_tokens")):
|
|
333
349
|
plot.resolve_pixel_tokens(state)
|
|
334
350
|
if geom_keys and self.has_trait(gname):
|
|
335
351
|
# Split heavy geometry into its own channel. Detect change by
|
|
@@ -814,9 +830,17 @@ class Figure(anywidget.AnyWidget, _EventMixin):
|
|
|
814
830
|
Called from ``_repr_utils._widget_state``, the one chokepoint every
|
|
815
831
|
export path goes through. The live Jupyter path deliberately keeps
|
|
816
832
|
using targeted pushes and does not pay this cost.
|
|
833
|
+
|
|
834
|
+
The re-push passes ``resolve_pixels=True``: this rewrites the panel
|
|
835
|
+
traits, and under a live binary transport an ordinary push leaves pixel
|
|
836
|
+
change-tokens in place for PLOTBIN to fill in. A snapshot has no
|
|
837
|
+
PLOTBIN, so those tokens would be dangling — and because the re-push
|
|
838
|
+
rewrites ``panel_<id>_json`` unconditionally it would also undo any
|
|
839
|
+
materialisation the caller did beforehand. An export always ships real
|
|
840
|
+
pixels.
|
|
817
841
|
"""
|
|
818
842
|
for panel_id in list(self._plots_map):
|
|
819
|
-
self._push(panel_id)
|
|
843
|
+
self._push(panel_id, resolve_pixels=True)
|
|
820
844
|
|
|
821
845
|
def _push_panel_fields(self, panel_id: str, fields: dict) -> None:
|
|
822
846
|
"""Apply a small set of changed *fields* to a panel, then push once.
|
anyplotlib/keys.py
CHANGED
|
@@ -37,14 +37,6 @@ class KeyOverlay:
|
|
|
37
37
|
key = plot.add_key(ipf_triangle, corner="bottom-right")
|
|
38
38
|
key.set(size=0.3, bgcolor="none")
|
|
39
39
|
key.visible = False # plain attribute assignment also pushes
|
|
40
|
-
|
|
41
|
-
Attributes
|
|
42
|
-
----------
|
|
43
|
-
id : str
|
|
44
|
-
Short unique identifier.
|
|
45
|
-
name : str
|
|
46
|
-
Caller-supplied name, or the id when none was given. Use it with
|
|
47
|
-
:meth:`~anyplotlib.Plot2D.get_key`.
|
|
48
40
|
"""
|
|
49
41
|
|
|
50
42
|
#: Fields that live on the light view channel. The image itself does NOT
|
|
@@ -68,10 +60,15 @@ class KeyOverlay:
|
|
|
68
60
|
|
|
69
61
|
@property
|
|
70
62
|
def id(self) -> str:
|
|
63
|
+
"""Short unique identifier."""
|
|
71
64
|
return self._id
|
|
72
65
|
|
|
73
66
|
@property
|
|
74
67
|
def name(self) -> str:
|
|
68
|
+
"""Caller-supplied name, or the id when none was given.
|
|
69
|
+
|
|
70
|
+
Use it with :meth:`~anyplotlib.Plot2D.get_key`.
|
|
71
|
+
"""
|
|
75
72
|
return self._name
|
|
76
73
|
|
|
77
74
|
# ── attribute access ──────────────────────────────────────────────
|
anyplotlib/plot2d/_plot2d.py
CHANGED
|
@@ -45,6 +45,40 @@ def _binary_transport_active() -> bool:
|
|
|
45
45
|
return os.environ.get("APL_BINARY_TRANSPORT") == "1"
|
|
46
46
|
|
|
47
47
|
|
|
48
|
+
def _reduce_mask_any(mask, out_h: int, out_w: int):
|
|
49
|
+
"""Reduce a boolean *mask* to ``(out_h, out_w)`` with a block ANY.
|
|
50
|
+
|
|
51
|
+
A block of the source is True in the output if ANY of its pixels is. For a
|
|
52
|
+
mask that is the only defensible reduction: it marks *where objects are*,
|
|
53
|
+
and an object smaller than one output block must not vanish because the
|
|
54
|
+
sampled pixel happened to miss it.
|
|
55
|
+
|
|
56
|
+
The tail rows/columns that do not divide evenly are folded into the last
|
|
57
|
+
block rather than cropped, so an object at the right or bottom edge is kept.
|
|
58
|
+
"""
|
|
59
|
+
import numpy as _np
|
|
60
|
+
|
|
61
|
+
src_h, src_w = mask.shape
|
|
62
|
+
if (src_h, src_w) == (out_h, out_w):
|
|
63
|
+
return mask
|
|
64
|
+
ys = max(1, src_h // out_h)
|
|
65
|
+
xs = max(1, src_w // out_w)
|
|
66
|
+
keep_h, keep_w = (src_h // ys) * ys, (src_w // xs) * xs
|
|
67
|
+
small = mask[:keep_h, :keep_w].reshape(
|
|
68
|
+
keep_h // ys, ys, keep_w // xs, xs).any(axis=(1, 3))
|
|
69
|
+
out = _np.zeros((out_h, out_w), bool)
|
|
70
|
+
sh, sw = min(out_h, small.shape[0]), min(out_w, small.shape[1])
|
|
71
|
+
out[:sh, :sw] = small[:sh, :sw]
|
|
72
|
+
# Fold any cropped tail into the final block so edge objects survive.
|
|
73
|
+
if keep_h < src_h and sh > 0:
|
|
74
|
+
out[sh - 1, :sw] |= mask[keep_h:, :keep_w].reshape(
|
|
75
|
+
1, src_h - keep_h, keep_w // xs, xs).any(axis=(1, 3))[0, :sw]
|
|
76
|
+
if keep_w < src_w and sw > 0:
|
|
77
|
+
out[:sh, sw - 1] |= mask[:keep_h, keep_w:].reshape(
|
|
78
|
+
keep_h // ys, ys, 1, src_w - keep_w).any(axis=(1, 3))[:sh, 0]
|
|
79
|
+
return out
|
|
80
|
+
|
|
81
|
+
|
|
48
82
|
class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
49
83
|
"""2-D image plot panel.
|
|
50
84
|
|
|
@@ -1115,6 +1149,13 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
|
1115
1149
|
Boolean array aligned to the image data. ``True`` / non-zero
|
|
1116
1150
|
pixels are filled with *color* at transparency *alpha*.
|
|
1117
1151
|
Pass ``None`` to clear the overlay.
|
|
1152
|
+
|
|
1153
|
+
In TILE mode pass the mask at the FULL image resolution: the
|
|
1154
|
+
renderer composites the mask against the OVERVIEW texture, so it
|
|
1155
|
+
is reduced to the base grid here. A mask already at the base grid
|
|
1156
|
+
is accepted unchanged. (Both are accepted because neither is
|
|
1157
|
+
wrong; what was wrong was accepting only the full-resolution one
|
|
1158
|
+
and then shipping bytes the renderer silently discards.)
|
|
1118
1159
|
color : str, optional
|
|
1119
1160
|
CSS hex colour for the overlay, e.g. ``"#ff4444"``. Default red.
|
|
1120
1161
|
Must be in ``#RRGGBB`` format.
|
|
@@ -1137,14 +1178,32 @@ class Plot2D(_BasePlot, _PanelMixin, _MarkerMixin):
|
|
|
1137
1178
|
self._state["overlay_mask_alpha"] = alpha
|
|
1138
1179
|
else:
|
|
1139
1180
|
arr = np.asarray(mask)
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1181
|
+
ih, iw = self._state["image_height"], self._state["image_width"]
|
|
1182
|
+
bh = int(self._state.get("base_height") or 0)
|
|
1183
|
+
bw = int(self._state.get("base_width") or 0)
|
|
1184
|
+
# THE TILE-MODE SHAPE TRAP. The renderer sizes the mask against
|
|
1185
|
+
# `base_width || image_width` -- the OVERVIEW grid -- but tile mode
|
|
1186
|
+
# sets image_width/height to the FULL native frame. So validating
|
|
1187
|
+
# only against the image shape rejected the one shape that renders
|
|
1188
|
+
# and accepted the one the renderer drops on the floor
|
|
1189
|
+
# (`mBytes.length===iw*ih` else `maskCache=null`, silently). Both
|
|
1190
|
+
# shapes are legal here; the overview is what actually ships.
|
|
1191
|
+
tiled = bw > 0 and bh > 0 and (bh, bw) != (ih, iw)
|
|
1192
|
+
if arr.shape != (ih, iw) and not (tiled and arr.shape == (bh, bw)):
|
|
1193
|
+
want = f"({ih} x {iw})"
|
|
1194
|
+
if tiled:
|
|
1195
|
+
want += f" or the tile overview ({bh} x {bw})"
|
|
1196
|
+
raise ValueError(f"mask shape {arr.shape} does not match image {want}")
|
|
1145
1197
|
# For origin='lower' the image data was flipped; flip mask to match.
|
|
1146
1198
|
if self._origin == "lower":
|
|
1147
1199
|
arr = np.flipud(arr)
|
|
1200
|
+
if tiled and arr.shape == (ih, iw):
|
|
1201
|
+
# Block ANY, never a subsample: a mask marks objects that are
|
|
1202
|
+
# often only a few pixels across, and striding a 4096² mask
|
|
1203
|
+
# down to 1024² drops three quarters of them at random. ANY
|
|
1204
|
+
# keeps every object visible at the cost of fattening it, which
|
|
1205
|
+
# is the right trade for an overlay you are looking at.
|
|
1206
|
+
arr = _reduce_mask_any(np.asarray(arr, dtype=bool), bh, bw)
|
|
1148
1207
|
# Convert to uint8: True/non-zero → 255, False/zero → 0
|
|
1149
1208
|
u8 = (np.asarray(arr, dtype=bool).view(np.uint8) * 255).astype(np.uint8)
|
|
1150
1209
|
self._state["overlay_mask_b64"] = base64.b64encode(u8.tobytes()).decode("ascii")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: anyplotlib
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A plotting library using python, javascript and anywidget for performant in browser plotting.
|
|
5
5
|
Project-URL: Homepage, https://cssfrancis.github.io/anyplotlib/
|
|
6
6
|
Project-URL: Repository, https://github.com/CSSFrancis/anyplotlib
|
|
@@ -7,13 +7,13 @@ anyplotlib/_utils.py,sha256=fvkMnNiCljG11p6Fsn2engKzsYhI1ILcZE5wog5Rcu0,14584
|
|
|
7
7
|
anyplotlib/callbacks.py,sha256=lTmL-UYPN6y0VY82MqDKSMTqBgUgezbSM7SoP8t5s0o,14620
|
|
8
8
|
anyplotlib/embed.py,sha256=cSDgjRSwqaJb6OvuJ5g3QFlNg0k0EEQH48VtVaR0K_I,7389
|
|
9
9
|
anyplotlib/figure_esm.js,sha256=bPU56QCD5lA0Ut_kjvWV79PeX9g4ABbrO5kMyTtjWEY,477090
|
|
10
|
-
anyplotlib/keys.py,sha256=
|
|
10
|
+
anyplotlib/keys.py,sha256=nmzrppnuuh3K-JYV6rsat-MGWiaZxfJjO-IDN8Ruzdo,9307
|
|
11
11
|
anyplotlib/markers.py,sha256=e6r2t8lFs9c5OjkxeRsV1Le34G7icJRgO2ciPx3e5nM,26870
|
|
12
12
|
anyplotlib/axes/__init__.py,sha256=vsMU_WjFcha17Xozxe7v4_oYQ_6qatk8GZlbwKm-cAc,186
|
|
13
13
|
anyplotlib/axes/_axes.py,sha256=ccTs4BQ6XexQjWtX1I7vUKBLEBRTJk7BJ2X8qCVlwQc,23581
|
|
14
14
|
anyplotlib/axes/_inset_axes.py,sha256=t40lFrflxx6GwLk7fYBElWzAd0mOCsag1RsEeMswJb0,17512
|
|
15
15
|
anyplotlib/figure/__init__.py,sha256=9492s6i5zyS47_x4tt9VrP2rFgeajSGuAm6EOH-2V1g,294
|
|
16
|
-
anyplotlib/figure/_figure.py,sha256=
|
|
16
|
+
anyplotlib/figure/_figure.py,sha256=6WCTPwifAc8d2fJktaMHftSgqcP8a6CsjdF13SZNpoY,41576
|
|
17
17
|
anyplotlib/figure/_gridspec.py,sha256=LkNepJxzb1ycM3C_uNQlhbq-4LwhGSWt9wIwycT7K5I,3756
|
|
18
18
|
anyplotlib/figure/_subplots.py,sha256=E7kpJ4Y6BA9iXEBLNfSDXJjxf-O-gHQUweHfMd0Nivs,3425
|
|
19
19
|
anyplotlib/plot1d/__init__.py,sha256=xjnzGsO0yjzoE1Rg9yVyFsMgtBzL90jJF7gAFuS0YR0,210
|
|
@@ -21,7 +21,7 @@ anyplotlib/plot1d/_plot1d.py,sha256=H0bK4ThKV-3haxs0ish48Ye65M-K9KRNwzoFRNTXjso,
|
|
|
21
21
|
anyplotlib/plot1d/_plotbar.py,sha256=2hbj3I86TJr5Zds4a179iNQ9CyQTG8ys9MqY8xBTX_Y,17356
|
|
22
22
|
anyplotlib/plot2d/__init__.py,sha256=_BVLJeGKuTmlZPpfPW2d99qRamBvOSyx3hsru7ylTzk,264
|
|
23
23
|
anyplotlib/plot2d/_layer.py,sha256=gONf5R2bYsf_Oymx3Y0RmPrTvFDzVnbDGBKFWkhyAKM,5598
|
|
24
|
-
anyplotlib/plot2d/_plot2d.py,sha256=
|
|
24
|
+
anyplotlib/plot2d/_plot2d.py,sha256=w1p0eIm_MQnacBZsvZ5yKV9YpR3ybM0qH-aHHwH-7Rc,119353
|
|
25
25
|
anyplotlib/plot2d/_plotmesh.py,sha256=qXOsiQQ13yhPar5KToAIz02sqivdKz3eXcHI7QZsv04,4547
|
|
26
26
|
anyplotlib/plot2d/_tile_backend.py,sha256=BfJ0X-AbWJvImeNcBW9PzkO3b-JQ_CDBP3vOfdNocOw,7225
|
|
27
27
|
anyplotlib/plot3d/__init__.py,sha256=m851XQKGzx5bywIbBK0qntBxzJU7zwxqf6xnOQ8pD1M,138
|
|
@@ -40,7 +40,7 @@ anyplotlib/widgets/_base.py,sha256=WMY_s_KKNVpd-3VceAwZeY5JHhtvJp1nuVbYkmqNiBw,9
|
|
|
40
40
|
anyplotlib/widgets/_widgets1d.py,sha256=SXnBDm25th1u9lPHlpQIdVACaQcVCvKUeF-ZJ9VMeVs,7784
|
|
41
41
|
anyplotlib/widgets/_widgets2d.py,sha256=XZmqmgRq7g0P2-xkC2hMRPfZFtq8t-nfjbRc7w2wIXA,19938
|
|
42
42
|
anyplotlib/widgets/_widgets3d.py,sha256=zRd2q5O2KePxU5raCnvO-LatMlpGQjnDMPhG61d40Ks,1638
|
|
43
|
-
anyplotlib-0.7.
|
|
44
|
-
anyplotlib-0.7.
|
|
45
|
-
anyplotlib-0.7.
|
|
46
|
-
anyplotlib-0.7.
|
|
43
|
+
anyplotlib-0.7.2.dist-info/METADATA,sha256=IyT9uH8qJxx6r-0zp3yeeayG9yBxI0B3DXk9rfImZY0,7163
|
|
44
|
+
anyplotlib-0.7.2.dist-info/WHEEL,sha256=lCkmxWfQsSc9CfIClYeavTdQeEX2toPqufh9gI35EQA,87
|
|
45
|
+
anyplotlib-0.7.2.dist-info/licenses/LICENSE,sha256=NonSXCjWxcYbaMxPnkgFzPpP0bVBTwUDpuDKyKC2uvQ,1071
|
|
46
|
+
anyplotlib-0.7.2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|