matplotly 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.
matplotly/__init__.py ADDED
@@ -0,0 +1,124 @@
1
+ """matplotly — Inline Jupyter matplotlib figure editor.
2
+
3
+ Usage:
4
+ # Pass a figure directly
5
+ fig, ax = plt.subplots()
6
+ ax.plot(x, y)
7
+ matplotly(fig)
8
+
9
+ # Decorator
10
+ @matplotly
11
+ def my_plot():
12
+ plt.plot(x, y)
13
+
14
+ # Context manager
15
+ with matplotly() as pb:
16
+ fig, ax = plt.subplots()
17
+ ax.plot(x, y)
18
+ """
19
+ from __future__ import annotations
20
+
21
+ __version__ = "0.1.0"
22
+ __all__ = ["matplotly"]
23
+
24
+ import functools
25
+ from typing import Any
26
+
27
+ import matplotlib.pyplot as plt
28
+ from matplotlib.figure import Figure
29
+
30
+
31
+ def _suppress_auto_display(fig: Figure) -> None:
32
+ """Hide the ipympl auto-displayed canvas and remove from figure manager."""
33
+ # Remove from pyplot's figure manager so ipympl stops tracking it
34
+ try:
35
+ import matplotlib._pylab_helpers as _helpers
36
+ _helpers.Gcf.figs.pop(fig.number, None)
37
+ except Exception:
38
+ pass
39
+ # Hide the auto-displayed ipympl canvas widget (if ipympl is active,
40
+ # fig.canvas is an ipywidget that was already displayed by plt.subplots)
41
+ try:
42
+ fig.canvas.layout.display = 'none'
43
+ fig.canvas.layout.height = '0px'
44
+ except (AttributeError, Exception):
45
+ pass
46
+ # Clear any already-rendered static output from the cell
47
+ try:
48
+ from IPython.display import clear_output
49
+ clear_output(wait=False)
50
+ except Exception:
51
+ pass
52
+
53
+
54
+ class _PlotBuildContext:
55
+ """Context manager that captures the current figure on exit."""
56
+
57
+ def __enter__(self):
58
+ return self
59
+
60
+ def __exit__(self, exc_type, exc_val, exc_tb):
61
+ if exc_type is not None:
62
+ return False
63
+ from ._api import PlotBuildSession
64
+ fig = plt.gcf()
65
+ _suppress_auto_display(fig)
66
+ session = PlotBuildSession(fig)
67
+ session.display()
68
+ return False
69
+
70
+
71
+ def _capture_cell_source() -> str | None:
72
+ """Grab the source of the currently executing Jupyter cell."""
73
+ try:
74
+ from IPython import get_ipython
75
+ ip = get_ipython()
76
+ if ip is not None:
77
+ # In[] list holds all executed cell sources; last entry is current
78
+ in_list = ip.user_ns.get("In")
79
+ if in_list and len(in_list) > 0:
80
+ return in_list[-1]
81
+ except Exception:
82
+ pass
83
+ return None
84
+
85
+
86
+ def matplotly(target: Figure | Any | None = None):
87
+ """Interactive matplotlib figure editor.
88
+
89
+ Parameters
90
+ ----------
91
+ target : Figure, callable, or None
92
+ - Figure: opens the editor on that figure immediately.
93
+ - callable: decorator mode — calls the function, captures plt.gcf(),
94
+ and opens the editor.
95
+ - None: returns a context manager.
96
+ """
97
+ from ._api import PlotBuildSession
98
+
99
+ cell_source = _capture_cell_source()
100
+
101
+ # Case 1: Figure passed directly
102
+ if isinstance(target, Figure):
103
+ _suppress_auto_display(target)
104
+ session = PlotBuildSession(target, cell_source=cell_source)
105
+ session.display()
106
+ return None # suppress Jupyter auto-display of return value
107
+
108
+ # Case 2: Callable — decorator mode
109
+ if callable(target):
110
+ @functools.wraps(target)
111
+ def wrapper(*args, **kwargs):
112
+ target(*args, **kwargs)
113
+ fig = plt.gcf()
114
+ _suppress_auto_display(fig)
115
+ cs = _capture_cell_source()
116
+ session = PlotBuildSession(fig, cell_source=cs)
117
+ session.display()
118
+ return wrapper
119
+
120
+ # Case 3: None — context manager mode
121
+ if target is None:
122
+ return _PlotBuildContext()
123
+
124
+ raise TypeError(f"matplotly() expects a Figure, callable, or None, got {type(target)}")