syd 1.0.0__py3-none-any.whl → 1.0.1__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.
- syd/__init__.py +1 -1
- syd/notebook_deployment/deployer.py +106 -22
- {syd-1.0.0.dist-info → syd-1.0.1.dist-info}/METADATA +33 -24
- {syd-1.0.0.dist-info → syd-1.0.1.dist-info}/RECORD +6 -6
- {syd-1.0.0.dist-info → syd-1.0.1.dist-info}/WHEEL +0 -0
- {syd-1.0.0.dist-info → syd-1.0.1.dist-info}/licenses/LICENSE +0 -0
syd/__init__.py
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from typing import Literal, Optional
|
|
2
2
|
import warnings
|
|
3
|
+
import threading
|
|
4
|
+
from contextlib import contextmanager
|
|
3
5
|
import ipywidgets as widgets
|
|
4
6
|
from IPython.display import display
|
|
5
7
|
import matplotlib as mpl
|
|
@@ -39,6 +41,7 @@ class NotebookDeployer:
|
|
|
39
41
|
controls_width_percent: int = 20,
|
|
40
42
|
continuous: bool = False,
|
|
41
43
|
suppress_warnings: bool = True,
|
|
44
|
+
update_threshold: float = 1.0,
|
|
42
45
|
):
|
|
43
46
|
self.viewer = viewer
|
|
44
47
|
self.components: dict[str, BaseWidget] = {}
|
|
@@ -56,12 +59,58 @@ class NotebookDeployer:
|
|
|
56
59
|
"The behavior of the viewer will almost definitely not work as expected!"
|
|
57
60
|
)
|
|
58
61
|
self._last_figure = None
|
|
62
|
+
self._update_event = threading.Event()
|
|
63
|
+
self.update_threshold = update_threshold
|
|
64
|
+
self._slow_loading_figure = None
|
|
65
|
+
self._display_lock = threading.Lock() # Lock for synchronizing display updates
|
|
66
|
+
|
|
67
|
+
def _show_slow_loading(self):
|
|
68
|
+
if self.backend_type == "inline":
|
|
69
|
+
if not self._update_event.wait(self.update_threshold):
|
|
70
|
+
if self._slow_loading_figure is None:
|
|
71
|
+
fig = plt.figure()
|
|
72
|
+
ax = fig.add_subplot(111)
|
|
73
|
+
ax.text(
|
|
74
|
+
0.5,
|
|
75
|
+
0.5,
|
|
76
|
+
"waiting for next figure...",
|
|
77
|
+
ha="center",
|
|
78
|
+
va="center",
|
|
79
|
+
fontsize=12,
|
|
80
|
+
weight="bold",
|
|
81
|
+
color="black",
|
|
82
|
+
)
|
|
83
|
+
ax.axis("off")
|
|
84
|
+
self._slow_loading_figure = fig
|
|
85
|
+
if not self._showing_new_figure:
|
|
86
|
+
self._display_figure(self._slow_loading_figure, store_figure=False)
|
|
87
|
+
self._showing_slow_loading_figure = True
|
|
88
|
+
|
|
89
|
+
@contextmanager
|
|
90
|
+
def _perform_update(self):
|
|
91
|
+
self._updating = True
|
|
92
|
+
self._showing_new_figure = False
|
|
93
|
+
self._showing_slow_loading_figure = False
|
|
94
|
+
self._update_event.clear()
|
|
95
|
+
|
|
96
|
+
thread = threading.Thread(target=self._show_slow_loading, daemon=True)
|
|
97
|
+
thread.start()
|
|
98
|
+
|
|
99
|
+
try:
|
|
100
|
+
yield
|
|
101
|
+
finally:
|
|
102
|
+
self._updating = False
|
|
103
|
+
self._update_event.set()
|
|
104
|
+
thread.join()
|
|
105
|
+
if self._showing_slow_loading_figure:
|
|
106
|
+
self._display_figure(self._last_figure)
|
|
107
|
+
self._update_status("Ready!")
|
|
59
108
|
|
|
60
109
|
def deploy(self) -> None:
|
|
61
110
|
"""Deploy the viewer."""
|
|
111
|
+
self.backend_type = get_backend_type()
|
|
62
112
|
self.build_components()
|
|
63
113
|
self.build_layout()
|
|
64
|
-
self.backend_type = get_backend_type()
|
|
65
114
|
display(self.layout)
|
|
66
115
|
self.update_plot()
|
|
67
116
|
|
|
@@ -80,6 +129,10 @@ class NotebookDeployer:
|
|
|
80
129
|
|
|
81
130
|
# Controls width slider for horizontal layouts
|
|
82
131
|
self.controls = {}
|
|
132
|
+
self.controls["status"] = widgets.HTML(
|
|
133
|
+
value="<b>Syd Controls</b>",
|
|
134
|
+
layout=widgets.Layout(width="95%"),
|
|
135
|
+
)
|
|
83
136
|
if self.controls_position in ["left", "right"]:
|
|
84
137
|
self.controls["controls_width"] = widgets.IntSlider(
|
|
85
138
|
value=self.controls_width_percent,
|
|
@@ -90,6 +143,15 @@ class NotebookDeployer:
|
|
|
90
143
|
layout=widgets.Layout(width="95%"),
|
|
91
144
|
style={"description_width": "initial"},
|
|
92
145
|
)
|
|
146
|
+
if self.backend_type == "inline":
|
|
147
|
+
self.controls["update_threshold"] = widgets.FloatSlider(
|
|
148
|
+
value=self.update_threshold,
|
|
149
|
+
min=0.1,
|
|
150
|
+
max=10.0,
|
|
151
|
+
description="Update Threshold",
|
|
152
|
+
layout=widgets.Layout(width="95%"),
|
|
153
|
+
style={"description_width": "initial"},
|
|
154
|
+
)
|
|
93
155
|
|
|
94
156
|
# Create parameter controls section
|
|
95
157
|
param_box = widgets.VBox(
|
|
@@ -102,7 +164,7 @@ class NotebookDeployer:
|
|
|
102
164
|
if self.controls_position in ["left", "right"]:
|
|
103
165
|
# Create layout controls section if horizontal (might include for vertical later when we have more permanent controls...)
|
|
104
166
|
layout_box = widgets.VBox(
|
|
105
|
-
|
|
167
|
+
list(self.controls.values()),
|
|
106
168
|
layout=widgets.Layout(margin="10px 0px"),
|
|
107
169
|
)
|
|
108
170
|
|
|
@@ -112,6 +174,11 @@ class NotebookDeployer:
|
|
|
112
174
|
self._handle_container_width_change, names="value"
|
|
113
175
|
)
|
|
114
176
|
|
|
177
|
+
if "update_threshold" in self.controls:
|
|
178
|
+
self.controls["update_threshold"].observe(
|
|
179
|
+
self._handle_update_threshold_change, names="value"
|
|
180
|
+
)
|
|
181
|
+
|
|
115
182
|
widgets_elements = [param_box, layout_box]
|
|
116
183
|
else:
|
|
117
184
|
widgets_elements = [param_box]
|
|
@@ -154,6 +221,8 @@ class NotebookDeployer:
|
|
|
154
221
|
else:
|
|
155
222
|
self.layout = widgets.VBox([self.widgets_container, self.plot_container])
|
|
156
223
|
|
|
224
|
+
self._update_status("Ready!")
|
|
225
|
+
|
|
157
226
|
def handle_component_engagement(self, name: str) -> None:
|
|
158
227
|
"""Handle engagement with an interactive component."""
|
|
159
228
|
if self._updating:
|
|
@@ -164,9 +233,8 @@ class NotebookDeployer:
|
|
|
164
233
|
)
|
|
165
234
|
return
|
|
166
235
|
|
|
167
|
-
|
|
168
|
-
self.
|
|
169
|
-
|
|
236
|
+
with self._perform_update():
|
|
237
|
+
self._update_status(f"Updating {name}")
|
|
170
238
|
# Optionally suppress warnings during parameter updates
|
|
171
239
|
with warnings.catch_warnings():
|
|
172
240
|
if self.suppress_warnings:
|
|
@@ -188,9 +256,6 @@ class NotebookDeployer:
|
|
|
188
256
|
# Update the plot
|
|
189
257
|
self.update_plot()
|
|
190
258
|
|
|
191
|
-
finally:
|
|
192
|
-
self._updating = False
|
|
193
|
-
|
|
194
259
|
def sync_components_with_state(self, exclude: Optional[str] = None) -> None:
|
|
195
260
|
"""Sync component values with viewer state."""
|
|
196
261
|
for name, parameter in self.viewer.parameters.items():
|
|
@@ -211,25 +276,32 @@ class NotebookDeployer:
|
|
|
211
276
|
# Update components if plot function updated a parameter
|
|
212
277
|
self.sync_components_with_state()
|
|
213
278
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
279
|
+
self._display_figure(figure)
|
|
280
|
+
|
|
281
|
+
self._showing_new_figure = True
|
|
217
282
|
|
|
218
|
-
|
|
219
|
-
with self.
|
|
220
|
-
if
|
|
221
|
-
|
|
283
|
+
def _display_figure(self, figure: plt.Figure, store_figure: bool = True) -> None:
|
|
284
|
+
with self._display_lock:
|
|
285
|
+
# Close the last figure if it exists to keep matplotlib clean
|
|
286
|
+
if self._last_figure is not None:
|
|
287
|
+
plt.close(self._last_figure)
|
|
222
288
|
|
|
223
|
-
|
|
224
|
-
|
|
289
|
+
self.plot_output.clear_output(wait=True)
|
|
290
|
+
with self.plot_output:
|
|
291
|
+
if self.backend_type == "inline":
|
|
292
|
+
display(figure)
|
|
225
293
|
|
|
226
|
-
|
|
227
|
-
|
|
294
|
+
# Also required to make sure a second figure window isn't opened
|
|
295
|
+
plt.close(figure)
|
|
228
296
|
|
|
229
|
-
|
|
230
|
-
|
|
297
|
+
elif self.backend_type == "widget":
|
|
298
|
+
display(figure.canvas)
|
|
231
299
|
|
|
232
|
-
|
|
300
|
+
else:
|
|
301
|
+
raise ValueError(f"Unsupported backend type: {self.backend_type}")
|
|
302
|
+
|
|
303
|
+
if store_figure:
|
|
304
|
+
self._last_figure = figure
|
|
233
305
|
|
|
234
306
|
def _handle_container_width_change(self, _) -> None:
|
|
235
307
|
"""Handle changes to container width proportions."""
|
|
@@ -239,3 +311,15 @@ class NotebookDeployer:
|
|
|
239
311
|
# Update container widths
|
|
240
312
|
self.widgets_container.layout.width = f"{width_percent}%"
|
|
241
313
|
self.plot_container.layout.width = f"{100 - width_percent}%"
|
|
314
|
+
|
|
315
|
+
def _handle_update_threshold_change(self, _) -> None:
|
|
316
|
+
"""Handle changes to update threshold."""
|
|
317
|
+
self.update_threshold = self.controls["update_threshold"].value
|
|
318
|
+
|
|
319
|
+
def _update_status(self, status: str) -> None:
|
|
320
|
+
"""Update the status text."""
|
|
321
|
+
value = "<b>Syd Controls</b> "
|
|
322
|
+
value += "<span style='background-color: #e0e0e0; color: #000; padding: 2px 6px; border-radius: 4px; font-size: 90%;'>"
|
|
323
|
+
value += f"Status: {status}"
|
|
324
|
+
value += "</span>"
|
|
325
|
+
self.controls["status"].value = value
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: syd
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.1
|
|
4
4
|
Summary: A Python package for making GUIs for data science easy.
|
|
5
5
|
Project-URL: Homepage, https://github.com/landoskape/syd
|
|
6
6
|
Author-email: Andrew Landau <andrew+tyler+landau+getridofthisanddtheplusses@gmail.com>
|
|
@@ -37,13 +37,17 @@ Description-Content-Type: text/markdown
|
|
|
37
37
|
[](https://github.com/psf/black)
|
|
38
38
|
|
|
39
39
|
|
|
40
|
+
<div>
|
|
41
|
+
<img src="./docs/assets/syd-logo-white.png" alt="Syd" width="350" align="right"/>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
40
44
|
A package to help you share your data!
|
|
41
45
|
|
|
42
46
|
Have you ever wanted to look through all your data really quickly interactively? Of course you have. Mo data mo problems, but only if you don't know what to do with it. And that's why Syd stands for show your data!
|
|
43
47
|
|
|
44
48
|
Syd is a system for creating a data viewing GUI that you can view in a jupyter notebook or in a web browser. And guess what? Since it can open in a web browser, you can even open it on any other computer on your local network! For example, your PI's computer. Gone are the days of single random examples that they make infinitely stubborn conclusions about. Now, you can look at all the examples, quickly and easily, on their computer. And that's why Syd stands for share your data!
|
|
45
49
|
|
|
46
|
-
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what**_ you want to plot and _**which**_ parameters you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And
|
|
50
|
+
Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what**_ you want to plot and _**which**_ parameters you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to spend your time _thinking_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
|
|
47
51
|
|
|
48
52
|
## Installation
|
|
49
53
|
It's easy, just use pip install. The dependencies are light so it should work in most environments.
|
|
@@ -55,28 +59,35 @@ pip install syd
|
|
|
55
59
|
The full documentation is available at [shareyourdata.readthedocs.io](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
|
|
56
60
|
|
|
57
61
|
## Quick Start
|
|
62
|
+
<!-- <div style="float: right; margin-left: 100px; margin-bottom: 10px;">
|
|
63
|
+
<img src="./docs/assets/viewer_screenshots/readme_example_gif.gif" alt="Syd" width="300" align="right"/>
|
|
64
|
+
</div> -->
|
|
65
|
+
|
|
58
66
|
This is an example of a sine wave viewer which is about as simple as it gets. You can choose which env to use - if you use ``env="notebook"`` then the GUI will deploy as the output of a jupyter cell (this only works in jupyter!). If you use ``env="browser"`` then the GUI will open a page in your default web browser and you can interact with the data there (works in jupyter notebooks and also from python scripts!).
|
|
67
|
+
|
|
59
68
|
```python
|
|
60
|
-
import matplotlib.pyplot as plt
|
|
61
69
|
import numpy as np
|
|
70
|
+
import matplotlib.pyplot as plt
|
|
62
71
|
from syd import make_viewer
|
|
63
|
-
def plot(
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
ax.
|
|
72
|
+
def plot(state):
|
|
73
|
+
# Here's a simple plot function that plots a sine wave
|
|
74
|
+
fig = plt.figure()
|
|
75
|
+
t = np.linspace(0, 2 * np.pi, 1000)
|
|
76
|
+
ax = plt.gca()
|
|
77
|
+
ax.plot(t, state["amplitude"] * np.sin(state["frequency"] * t), color=state["color"])
|
|
68
78
|
return fig
|
|
69
|
-
|
|
70
|
-
viewer = make_viewer()
|
|
71
|
-
viewer.set_plot(plot)
|
|
72
|
-
viewer.add_float('amplitude', value=1.0, min=0, max=2)
|
|
73
|
-
viewer.add_float('frequency', value=1.0, min=0.1, max=5)
|
|
74
79
|
|
|
75
|
-
|
|
80
|
+
viewer = make_viewer(plot)
|
|
81
|
+
viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
|
|
82
|
+
viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
|
|
83
|
+
viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
|
|
84
|
+
|
|
85
|
+
# env = "browser" # for viewing in a web browser (accessible via an IP address)
|
|
76
86
|
env = "notebook" # for viewing within a jupyter notebook
|
|
77
|
-
viewer.deploy(env=env)
|
|
87
|
+
viewer = viewer.deploy(env=env)
|
|
78
88
|
```
|
|
79
89
|
|
|
90
|
+

|
|
80
91
|
### More Examples
|
|
81
92
|
We have several examples of more complex viewers with detailed explanations in the comments. Here are the links and descriptions to each of them:
|
|
82
93
|
|
|
@@ -197,8 +208,11 @@ Contributions are welcome! Here's how you can help:
|
|
|
197
208
|
6. Push to the branch (`git push origin feature/amazing-feature`)
|
|
198
209
|
7. Open a Pull Request online
|
|
199
210
|
|
|
200
|
-
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines).
|
|
201
|
-
|
|
211
|
+
Please make sure to update tests as appropriate and adhere to the existing coding style (black, line-length=88, other style guidelines not capture by black, generally following pep8 guidelines). Try to make the code coverage report improve or stay the same rather than decrease (right now the deployment system isn't covered by tests). I don't have any precommit hooks or anything so you're responsible for checking this yourself. You can process the code with black as follows:
|
|
212
|
+
```bash
|
|
213
|
+
pip install black
|
|
214
|
+
black . # from the root directory of the repo
|
|
215
|
+
```
|
|
202
216
|
|
|
203
217
|
## To-Do List
|
|
204
218
|
- Layout controls
|
|
@@ -208,12 +222,7 @@ Please make sure to update tests as appropriate and adhere to the existing codin
|
|
|
208
222
|
- [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
|
|
209
223
|
- [ ] Add a window for capturing any error messages that might be thrown by the plot function. Maybe we could have a little interface for looking at each one (up to a point) and the user could press a button to throw an error for the traceback.
|
|
210
224
|
- [ ] Consider "app_deployed" context for each deployer...
|
|
211
|
-
- [ ] Consider adding a step to the integer parameters...
|
|
212
|
-
- Idea for figure management:
|
|
213
|
-
- [ ] We could make fig=?, ax=? arguments optional for the plot function and add a
|
|
214
|
-
"recycle_figure: bool = False" flag be part of the deploy API. This way, an
|
|
215
|
-
advanced user that wants snappy responsivity or complex figure management can
|
|
216
|
-
do so, but the default is for the user to generate a new figure object each time.
|
|
217
225
|
- Export options:
|
|
218
226
|
- [ ] Export lite: export the viewer as a HTML/Java package that contains an incomplete set of renderings of figures -- using a certain set of parameters.
|
|
219
|
-
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
227
|
+
- [ ] Export full: export the viewer in a way that contains the data to give full functionality.
|
|
228
|
+
- [ ] Keep deploy() for backwards compatibility, but deprecate it in favor of show() and share() (for notebook and browser, respectively)
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
syd/__init__.py,sha256=
|
|
1
|
+
syd/__init__.py,sha256=Lx1We_TNKsV2Co9v5RB05WgqXpFqieSBh-y3zF0q1LE,250
|
|
2
2
|
syd/parameters.py,sha256=dlnYOVsi1CDtC2toVECf0kNBRipVrtUjr6XVX86b5MA,42886
|
|
3
3
|
syd/support.py,sha256=7wztPMaL750opyBDnaYYRVyBR5QUJtVspDzQWpu50rk,6106
|
|
4
4
|
syd/viewer.py,sha256=UYDfH9TNds0CiC3vThINLrMNZE8ikSFqJHgjM8yMD94,49323
|
|
@@ -11,9 +11,9 @@ syd/flask_deployment/static/js/viewer.js,sha256=kSY24VGjlQLe-jtPOEU1nE7U0ALC_ZaV
|
|
|
11
11
|
syd/flask_deployment/templates/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
|
|
12
12
|
syd/flask_deployment/templates/index.html,sha256=fr1g9IOwNttULhQCIcw_fo0sNpmdgznSGfPStQllR_E,1594
|
|
13
13
|
syd/notebook_deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
14
|
-
syd/notebook_deployment/deployer.py,sha256=
|
|
14
|
+
syd/notebook_deployment/deployer.py,sha256=sf3f9STRXpU1ac-OGENYv1g5yBpUaiIcREBKtKWvIlA,12324
|
|
15
15
|
syd/notebook_deployment/widgets.py,sha256=UbkasRf8wY9beUwpwJYjv9X0Lus3DvgAEIORHwaC-zA,20058
|
|
16
|
-
syd-1.0.
|
|
17
|
-
syd-1.0.
|
|
18
|
-
syd-1.0.
|
|
19
|
-
syd-1.0.
|
|
16
|
+
syd-1.0.1.dist-info/METADATA,sha256=pHDBBxQoLRmHRBsWPtF5VlYI0hpd_tX6zcmYBNhdIJQ,13668
|
|
17
|
+
syd-1.0.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
18
|
+
syd-1.0.1.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
|
|
19
|
+
syd-1.0.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|