Skip to content

Commit a163324

Browse files
committed
Add Electron app bridge for anyplotlib figures
1 parent 364a33f commit a163324

1 file changed

Lines changed: 74 additions & 0 deletions

File tree

anyplotlib/_electron.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
"""
2+
_electron.py
3+
============
4+
Electron app bridge for anyplotlib figures.
5+
6+
Registers figures so their trait changes are forwarded to the Electron
7+
renderer via stdout, and provides dispatch_event() so the renderer can
8+
send interaction events back to Python.
9+
"""
10+
from __future__ import annotations
11+
12+
import json
13+
import sys
14+
import uuid
15+
16+
_figures: dict[str, object] = {} # fig_id -> Figure
17+
18+
19+
def register(fig) -> str:
20+
"""Register *fig* for bidirectional state sync and return its fig_id."""
21+
fig_id = uuid.uuid4().hex[:8]
22+
_figures[fig_id] = fig
23+
24+
def _on_change(change):
25+
name = change["name"]
26+
value = change["new"]
27+
if isinstance(value, (bytes, bytearray)):
28+
import base64
29+
value = {"buffer": base64.b64encode(value).decode()}
30+
emit({"type": "state_update", "fig_id": fig_id, "key": name, "value": value})
31+
32+
for name in fig.traits(sync=True):
33+
if not name.startswith("_"):
34+
try:
35+
fig.observe(_on_change, names=[name])
36+
except Exception:
37+
pass
38+
39+
return fig_id
40+
41+
42+
def resize_figure(fig_id: str, width: int, height: int) -> None:
43+
"""Update fig_width / fig_height and push new layout to the iframe."""
44+
fig = _figures.get(fig_id)
45+
if fig is None:
46+
return
47+
try:
48+
# Batch both trait changes so _on_resize fires only once each.
49+
with fig.hold_trait_notifications():
50+
fig.fig_width = int(width)
51+
fig.fig_height = int(height)
52+
except Exception:
53+
pass
54+
55+
56+
def dispatch_event(fig_id: str, event_json: str) -> None:
57+
"""Apply a frontend interaction event to the registered figure."""
58+
fig = _figures.get(fig_id)
59+
if fig is None:
60+
return
61+
try:
62+
# Figure.show() registers Figure objects which use _dispatch_event(raw_json_str).
63+
# Standalone widgets use _update_from_js(dict, event_type).
64+
if hasattr(fig, "_dispatch_event"):
65+
fig._dispatch_event(event_json)
66+
elif hasattr(fig, "_update_from_js"):
67+
fig._update_from_js(json.loads(event_json))
68+
except Exception:
69+
pass
70+
71+
72+
def emit(obj: dict) -> None:
73+
sys.stdout.write(f"PLOTAPP:{json.dumps(obj, default=str)}\n")
74+
sys.stdout.flush()

0 commit comments

Comments
 (0)