syd 0.1.7__py3-none-any.whl → 0.2.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.
- syd/__init__.py +1 -1
- syd/flask_deployment/__init__.py +7 -1
- syd/flask_deployment/deployer.py +548 -209
- syd/flask_deployment/static/__init__.py +1 -0
- syd/flask_deployment/static/css/styles.css +246 -0
- syd/flask_deployment/static/js/viewer.js +710 -140
- syd/flask_deployment/templates/__init__.py +1 -0
- syd/flask_deployment/templates/index.html +34 -0
- syd/flask_deployment/testing_principles.md +4 -4
- syd/notebook_deployment/deployer.py +15 -5
- syd/notebook_deployment/widgets.py +76 -58
- syd/parameters.py +239 -250
- syd/support.py +168 -0
- syd/viewer.py +305 -346
- syd-0.2.0.dist-info/METADATA +126 -0
- syd-0.2.0.dist-info/RECORD +19 -0
- syd/flask_deployment/components.py +0 -510
- syd/flask_deployment/static/css/viewer.css +0 -82
- syd/flask_deployment/templates/base.html +0 -29
- syd/flask_deployment/templates/viewer.html +0 -51
- syd/notebook_deployment/_ipympl_deployer.py +0 -258
- syd/plotly_deployment/__init__.py +0 -1
- syd/plotly_deployment/components.py +0 -531
- syd/plotly_deployment/deployer.py +0 -376
- syd-0.1.7.dist-info/METADATA +0 -120
- syd-0.1.7.dist-info/RECORD +0 -22
- {syd-0.1.7.dist-info → syd-0.2.0.dist-info}/WHEEL +0 -0
- {syd-0.1.7.dist-info → syd-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file exists to make the directory a proper Python package
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>Syd Viewer</title>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
|
|
8
|
+
</head>
|
|
9
|
+
<body>
|
|
10
|
+
<div class="viewer-container" data-controls-position="{{ config.controls_position }}">
|
|
11
|
+
<div class="controls-container" data-width-percent="{{ config.controls_width_percent }}"
|
|
12
|
+
{% if config.is_horizontal %}style="width: {{ config.controls_width_percent }}%;"{% else %}style="height: {{ config.controls_width_percent }}%;"{% endif %}>
|
|
13
|
+
<div id="controls-container">
|
|
14
|
+
<!-- Controls will be dynamically generated via JavaScript -->
|
|
15
|
+
</div>
|
|
16
|
+
</div>
|
|
17
|
+
|
|
18
|
+
<div class="plot-container"
|
|
19
|
+
{% if config.is_horizontal %}style="width: calc(100% - {{ config.controls_width_percent }}%);"{% else %}style="height: calc(100% - {{ config.controls_width_percent }}%);"{% endif %}>
|
|
20
|
+
<img id="plot-image" width="100%" height="100%">
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<!-- Store config as data attributes for JS to access -->
|
|
25
|
+
<div id="viewer-config"
|
|
26
|
+
data-figure-width="{{ config.figure_width }}"
|
|
27
|
+
data-figure-height="{{ config.figure_height }}"
|
|
28
|
+
data-controls-position="{{ config.controls_position }}"
|
|
29
|
+
data-controls-width-percent="{{ config.controls_width_percent }}"
|
|
30
|
+
style="display:none;"></div>
|
|
31
|
+
|
|
32
|
+
<script src="{{ url_for('static', filename='js/viewer.js') }}"></script>
|
|
33
|
+
</body>
|
|
34
|
+
</html>
|
|
@@ -57,7 +57,7 @@ class TestFlaskDeployerComponents(unittest.TestCase):
|
|
|
57
57
|
def test_parameter_update_state_sync():
|
|
58
58
|
# Create real viewer with test parameters
|
|
59
59
|
viewer = Viewer()
|
|
60
|
-
viewer.add_float('test_param', value=1.0,
|
|
60
|
+
viewer.add_float('test_param', value=1.0, min=0, max=10)
|
|
61
61
|
|
|
62
62
|
# Create deployer with this viewer
|
|
63
63
|
deployer = FlaskDeployer(viewer)
|
|
@@ -82,7 +82,7 @@ def test_parameter_update_state_sync():
|
|
|
82
82
|
```python
|
|
83
83
|
def test_update_parameter_endpoint():
|
|
84
84
|
viewer = Viewer()
|
|
85
|
-
viewer.add_float('test_param', value=1.0,
|
|
85
|
+
viewer.add_float('test_param', value=1.0, min=0, max=10)
|
|
86
86
|
deployer = FlaskDeployer(viewer)
|
|
87
87
|
app = deployer.app
|
|
88
88
|
|
|
@@ -218,8 +218,8 @@ def standard_test_viewer():
|
|
|
218
218
|
return fig
|
|
219
219
|
|
|
220
220
|
viewer.set_plot(plot)
|
|
221
|
-
viewer.add_float('amplitude', value=1.0,
|
|
222
|
-
viewer.add_float('frequency', value=1.0,
|
|
221
|
+
viewer.add_float('amplitude', value=1.0, min=0, max=2)
|
|
222
|
+
viewer.add_float('frequency', value=1.0, min=0.1, max=5)
|
|
223
223
|
|
|
224
224
|
return viewer
|
|
225
225
|
|
|
@@ -67,7 +67,7 @@ class LayoutConfig:
|
|
|
67
67
|
controls_position: str = "left" # Options are: 'left', 'top', 'right', 'bottom'
|
|
68
68
|
figure_width: float = 8.0
|
|
69
69
|
figure_height: float = 6.0
|
|
70
|
-
controls_width_percent: int =
|
|
70
|
+
controls_width_percent: int = 20
|
|
71
71
|
|
|
72
72
|
def __post_init__(self):
|
|
73
73
|
valid_positions = ["left", "top", "right", "bottom"]
|
|
@@ -93,7 +93,7 @@ class NotebookDeployer:
|
|
|
93
93
|
controls_position: str = "left",
|
|
94
94
|
figure_width: float = 8.0,
|
|
95
95
|
figure_height: float = 6.0,
|
|
96
|
-
controls_width_percent: int =
|
|
96
|
+
controls_width_percent: int = 20,
|
|
97
97
|
continuous: bool = False,
|
|
98
98
|
suppress_warnings: bool = False,
|
|
99
99
|
):
|
|
@@ -111,7 +111,7 @@ class NotebookDeployer:
|
|
|
111
111
|
self.backend_type = get_backend_type()
|
|
112
112
|
if self.backend_type not in ["inline", "widget"]:
|
|
113
113
|
warnings.warn(
|
|
114
|
-
"The current backend is not supported. Please use %matplotlib widget or %matplotlib inline.\n"
|
|
114
|
+
f"The current backend ({self.backend_type}) is not supported. Please use %matplotlib widget or %matplotlib inline.\n"
|
|
115
115
|
"The behavior of the viewer will almost definitely not work as expected."
|
|
116
116
|
)
|
|
117
117
|
self.parameter_widgets: Dict[str, BaseWidget] = {}
|
|
@@ -134,8 +134,8 @@ class NotebookDeployer:
|
|
|
134
134
|
if self.config.is_horizontal:
|
|
135
135
|
controls["controls_width"] = widgets.IntSlider(
|
|
136
136
|
value=self.config.controls_width_percent,
|
|
137
|
-
min=
|
|
138
|
-
max=
|
|
137
|
+
min=10,
|
|
138
|
+
max=50,
|
|
139
139
|
description="Controls Width %",
|
|
140
140
|
continuous=True,
|
|
141
141
|
layout=widgets.Layout(width="95%"),
|
|
@@ -227,6 +227,9 @@ class NotebookDeployer:
|
|
|
227
227
|
with _plot_context():
|
|
228
228
|
figure = self.viewer.plot(state)
|
|
229
229
|
|
|
230
|
+
# Update widgets if plot function updated a parameter
|
|
231
|
+
self._sync_widgets_with_state()
|
|
232
|
+
|
|
230
233
|
# Close the last figure if it exists to keep matplotlib clean
|
|
231
234
|
# (just moved this from after clear_output.... noting!)
|
|
232
235
|
if self._last_figure is not None:
|
|
@@ -269,6 +272,13 @@ class NotebookDeployer:
|
|
|
269
272
|
+ list(self.layout_widgets.values()),
|
|
270
273
|
layout=widgets.Layout(margin="10px 0px"),
|
|
271
274
|
)
|
|
275
|
+
|
|
276
|
+
# Register the controls_width slider's observer
|
|
277
|
+
if "controls_width" in self.layout_widgets:
|
|
278
|
+
self.layout_widgets["controls_width"].observe(
|
|
279
|
+
self._handle_container_width_change, names="value"
|
|
280
|
+
)
|
|
281
|
+
|
|
272
282
|
widgets_elements = [param_box, layout_box]
|
|
273
283
|
else:
|
|
274
284
|
widgets_elements = [param_box]
|
|
@@ -240,30 +240,33 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
|
|
|
240
240
|
margin: str = "3px 0px",
|
|
241
241
|
description_width: str = "initial",
|
|
242
242
|
) -> widgets.IntSlider:
|
|
243
|
+
"""Create the integer slider widget."""
|
|
243
244
|
return widgets.IntSlider(
|
|
244
245
|
value=parameter.value,
|
|
245
|
-
min=parameter.
|
|
246
|
-
max=parameter.
|
|
246
|
+
min=parameter.min,
|
|
247
|
+
max=parameter.max,
|
|
248
|
+
step=1,
|
|
247
249
|
description=parameter.name,
|
|
248
|
-
|
|
249
|
-
layout=widgets.Layout(width=width, margin=margin),
|
|
250
|
+
continuous_update=continuous,
|
|
250
251
|
style={"description_width": description_width},
|
|
252
|
+
layout=widgets.Layout(width=width, margin=margin),
|
|
251
253
|
)
|
|
252
254
|
|
|
253
255
|
def matches_parameter(self, parameter: IntegerParameter) -> bool:
|
|
254
|
-
"""Check if the widget
|
|
256
|
+
"""Check if the widget values match the parameter."""
|
|
255
257
|
return (
|
|
256
|
-
self.
|
|
257
|
-
and self._widget.
|
|
258
|
-
and self._widget.
|
|
258
|
+
self._widget.description == parameter.name
|
|
259
|
+
and self._widget.value == parameter.value
|
|
260
|
+
and self._widget.min == parameter.min
|
|
261
|
+
and self._widget.max == parameter.max
|
|
259
262
|
)
|
|
260
263
|
|
|
261
264
|
def extra_updates_from_parameter(self, parameter: IntegerParameter) -> None:
|
|
262
|
-
"""
|
|
265
|
+
"""Update the widget attributes from the parameter."""
|
|
263
266
|
current_value = self._widget.value
|
|
264
|
-
self._widget.min = parameter.
|
|
265
|
-
self._widget.max = parameter.
|
|
266
|
-
self.value = max(parameter.
|
|
267
|
+
self._widget.min = parameter.min
|
|
268
|
+
self._widget.max = parameter.max
|
|
269
|
+
self.value = max(parameter.min, min(parameter.max, current_value))
|
|
267
270
|
|
|
268
271
|
|
|
269
272
|
class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
|
|
@@ -277,32 +280,35 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
|
|
|
277
280
|
margin: str = "3px 0px",
|
|
278
281
|
description_width: str = "initial",
|
|
279
282
|
) -> widgets.FloatSlider:
|
|
283
|
+
"""Create the float slider widget."""
|
|
280
284
|
return widgets.FloatSlider(
|
|
281
285
|
value=parameter.value,
|
|
282
|
-
min=parameter.
|
|
283
|
-
max=parameter.
|
|
286
|
+
min=parameter.min,
|
|
287
|
+
max=parameter.max,
|
|
284
288
|
step=parameter.step,
|
|
285
289
|
description=parameter.name,
|
|
286
|
-
|
|
287
|
-
layout=widgets.Layout(width=width, margin=margin),
|
|
290
|
+
continuous_update=continuous,
|
|
288
291
|
style={"description_width": description_width},
|
|
292
|
+
layout=widgets.Layout(width=width, margin=margin),
|
|
289
293
|
)
|
|
290
294
|
|
|
291
295
|
def matches_parameter(self, parameter: FloatParameter) -> bool:
|
|
292
|
-
"""Check if the widget
|
|
296
|
+
"""Check if the widget values match the parameter."""
|
|
293
297
|
return (
|
|
294
|
-
self.
|
|
295
|
-
and self._widget.
|
|
296
|
-
and self._widget.
|
|
298
|
+
self._widget.description == parameter.name
|
|
299
|
+
and self._widget.value == parameter.value
|
|
300
|
+
and self._widget.min == parameter.min
|
|
301
|
+
and self._widget.max == parameter.max
|
|
302
|
+
and self._widget.step == parameter.step
|
|
297
303
|
)
|
|
298
304
|
|
|
299
305
|
def extra_updates_from_parameter(self, parameter: FloatParameter) -> None:
|
|
300
|
-
"""
|
|
306
|
+
"""Update the widget attributes from the parameter."""
|
|
301
307
|
current_value = self._widget.value
|
|
302
|
-
self._widget.min = parameter.
|
|
303
|
-
self._widget.max = parameter.
|
|
308
|
+
self._widget.min = parameter.min
|
|
309
|
+
self._widget.max = parameter.max
|
|
304
310
|
self._widget.step = parameter.step
|
|
305
|
-
self.value = max(parameter.
|
|
311
|
+
self.value = max(parameter.min, min(parameter.max, current_value))
|
|
306
312
|
|
|
307
313
|
|
|
308
314
|
class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlider]):
|
|
@@ -316,33 +322,39 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
|
|
|
316
322
|
margin: str = "3px 0px",
|
|
317
323
|
description_width: str = "initial",
|
|
318
324
|
) -> widgets.IntRangeSlider:
|
|
325
|
+
"""Create the integer range slider widget."""
|
|
326
|
+
low, high = parameter.value
|
|
319
327
|
return widgets.IntRangeSlider(
|
|
320
|
-
value=
|
|
321
|
-
min=parameter.
|
|
322
|
-
max=parameter.
|
|
328
|
+
value=[low, high],
|
|
329
|
+
min=parameter.min,
|
|
330
|
+
max=parameter.max,
|
|
331
|
+
step=1,
|
|
323
332
|
description=parameter.name,
|
|
324
|
-
|
|
325
|
-
layout=widgets.Layout(width=width, margin=margin),
|
|
333
|
+
continuous_update=continuous,
|
|
326
334
|
style={"description_width": description_width},
|
|
335
|
+
layout=widgets.Layout(width=width, margin=margin),
|
|
327
336
|
)
|
|
328
337
|
|
|
329
338
|
def matches_parameter(self, parameter: IntegerRangeParameter) -> bool:
|
|
330
|
-
"""Check if the widget
|
|
339
|
+
"""Check if the widget values match the parameter."""
|
|
340
|
+
low, high = parameter.value
|
|
331
341
|
return (
|
|
332
|
-
self.
|
|
333
|
-
and self._widget.
|
|
334
|
-
and self._widget.
|
|
342
|
+
self._widget.description == parameter.name
|
|
343
|
+
and self._widget.value[0] == low
|
|
344
|
+
and self._widget.value[1] == high
|
|
345
|
+
and self._widget.min == parameter.min
|
|
346
|
+
and self._widget.max == parameter.max
|
|
335
347
|
)
|
|
336
348
|
|
|
337
349
|
def extra_updates_from_parameter(self, parameter: IntegerRangeParameter) -> None:
|
|
338
|
-
"""
|
|
339
|
-
|
|
340
|
-
self._widget.min = parameter.
|
|
341
|
-
self._widget.max = parameter.
|
|
342
|
-
|
|
343
|
-
low = max(parameter.
|
|
344
|
-
high = max(parameter.
|
|
345
|
-
self.value =
|
|
350
|
+
"""Update the widget attributes from the parameter."""
|
|
351
|
+
low, high = self._widget.value
|
|
352
|
+
self._widget.min = parameter.min
|
|
353
|
+
self._widget.max = parameter.max
|
|
354
|
+
# Ensure values stay within bounds
|
|
355
|
+
low = max(parameter.min, min(parameter.max, low))
|
|
356
|
+
high = max(parameter.min, min(parameter.max, high))
|
|
357
|
+
self.value = [low, high]
|
|
346
358
|
|
|
347
359
|
|
|
348
360
|
class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]):
|
|
@@ -356,35 +368,41 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
|
|
|
356
368
|
margin: str = "3px 0px",
|
|
357
369
|
description_width: str = "initial",
|
|
358
370
|
) -> widgets.FloatRangeSlider:
|
|
371
|
+
"""Create the float range slider widget."""
|
|
372
|
+
low, high = parameter.value
|
|
359
373
|
return widgets.FloatRangeSlider(
|
|
360
|
-
value=
|
|
361
|
-
min=parameter.
|
|
362
|
-
max=parameter.
|
|
374
|
+
value=[low, high],
|
|
375
|
+
min=parameter.min,
|
|
376
|
+
max=parameter.max,
|
|
363
377
|
step=parameter.step,
|
|
364
378
|
description=parameter.name,
|
|
365
|
-
|
|
366
|
-
layout=widgets.Layout(width=width, margin=margin),
|
|
379
|
+
continuous_update=continuous,
|
|
367
380
|
style={"description_width": description_width},
|
|
381
|
+
layout=widgets.Layout(width=width, margin=margin),
|
|
368
382
|
)
|
|
369
383
|
|
|
370
384
|
def matches_parameter(self, parameter: FloatRangeParameter) -> bool:
|
|
371
|
-
"""Check if the widget
|
|
385
|
+
"""Check if the widget values match the parameter."""
|
|
386
|
+
low, high = parameter.value
|
|
372
387
|
return (
|
|
373
|
-
self.
|
|
374
|
-
and self._widget.
|
|
375
|
-
and self._widget.
|
|
388
|
+
self._widget.description == parameter.name
|
|
389
|
+
and self._widget.value[0] == low
|
|
390
|
+
and self._widget.value[1] == high
|
|
391
|
+
and self._widget.min == parameter.min
|
|
392
|
+
and self._widget.max == parameter.max
|
|
393
|
+
and self._widget.step == parameter.step
|
|
376
394
|
)
|
|
377
395
|
|
|
378
396
|
def extra_updates_from_parameter(self, parameter: FloatRangeParameter) -> None:
|
|
379
|
-
"""
|
|
380
|
-
|
|
381
|
-
self._widget.min = parameter.
|
|
382
|
-
self._widget.max = parameter.
|
|
397
|
+
"""Update the widget attributes from the parameter."""
|
|
398
|
+
low, high = self._widget.value
|
|
399
|
+
self._widget.min = parameter.min
|
|
400
|
+
self._widget.max = parameter.max
|
|
383
401
|
self._widget.step = parameter.step
|
|
384
|
-
|
|
385
|
-
low = max(parameter.
|
|
386
|
-
high = max(parameter.
|
|
387
|
-
self.value =
|
|
402
|
+
# Ensure values stay within bounds
|
|
403
|
+
low = max(parameter.min, min(parameter.max, low))
|
|
404
|
+
high = max(parameter.min, min(parameter.max, high))
|
|
405
|
+
self.value = [low, high]
|
|
388
406
|
|
|
389
407
|
|
|
390
408
|
class UnboundedIntegerWidget(BaseWidget[UnboundedIntegerParameter, widgets.IntText]):
|