syd 0.1.7__py3-none-any.whl → 1.0.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 +1 -1
- syd/flask_deployment/deployer.py +563 -238
- syd/flask_deployment/static/__init__.py +1 -0
- syd/flask_deployment/static/css/styles.css +280 -0
- syd/flask_deployment/static/js/viewer.js +795 -138
- 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/__init__.py +0 -1
- syd/notebook_deployment/deployer.py +124 -213
- syd/notebook_deployment/widgets.py +78 -60
- syd/parameters.py +299 -345
- syd/support.py +195 -0
- syd/viewer.py +310 -347
- syd-1.0.0.dist-info/METADATA +219 -0
- syd-1.0.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-1.0.0.dist-info}/WHEEL +0 -0
- {syd-0.1.7.dist-info → syd-1.0.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>{{ title }}</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
|
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from .deployer import NotebookDeployer
|
|
@@ -1,27 +1,13 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import Literal, Optional
|
|
2
2
|
import warnings
|
|
3
|
-
from functools import wraps
|
|
4
|
-
from dataclasses import dataclass
|
|
5
|
-
from contextlib import contextmanager
|
|
6
|
-
from time import time
|
|
7
|
-
|
|
8
3
|
import ipywidgets as widgets
|
|
9
4
|
from IPython.display import display
|
|
10
5
|
import matplotlib as mpl
|
|
11
6
|
import matplotlib.pyplot as plt
|
|
12
7
|
|
|
13
|
-
from ..
|
|
8
|
+
from ..support import ParameterUpdateWarning, plot_context
|
|
14
9
|
from ..viewer import Viewer
|
|
15
|
-
from .widgets import
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
@contextmanager
|
|
19
|
-
def _plot_context():
|
|
20
|
-
plt.ioff()
|
|
21
|
-
try:
|
|
22
|
-
yield
|
|
23
|
-
finally:
|
|
24
|
-
plt.ion()
|
|
10
|
+
from .widgets import create_widget, BaseWidget
|
|
25
11
|
|
|
26
12
|
|
|
27
13
|
def get_backend_type():
|
|
@@ -40,47 +26,6 @@ def get_backend_type():
|
|
|
40
26
|
return "other"
|
|
41
27
|
|
|
42
28
|
|
|
43
|
-
def debounce(wait_time):
|
|
44
|
-
"""
|
|
45
|
-
Decorator to prevent a function from being called more than once every wait_time seconds.
|
|
46
|
-
"""
|
|
47
|
-
|
|
48
|
-
def decorator(fn):
|
|
49
|
-
last_called = [0.0] # Using list to maintain state in closure
|
|
50
|
-
|
|
51
|
-
@wraps(fn)
|
|
52
|
-
def debounced(*args, **kwargs):
|
|
53
|
-
current_time = time()
|
|
54
|
-
if current_time - last_called[0] >= wait_time:
|
|
55
|
-
fn(*args, **kwargs)
|
|
56
|
-
last_called[0] = current_time
|
|
57
|
-
|
|
58
|
-
return debounced
|
|
59
|
-
|
|
60
|
-
return decorator
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
@dataclass
|
|
64
|
-
class LayoutConfig:
|
|
65
|
-
"""Configuration for the viewer layout."""
|
|
66
|
-
|
|
67
|
-
controls_position: str = "left" # Options are: 'left', 'top', 'right', 'bottom'
|
|
68
|
-
figure_width: float = 8.0
|
|
69
|
-
figure_height: float = 6.0
|
|
70
|
-
controls_width_percent: int = 30
|
|
71
|
-
|
|
72
|
-
def __post_init__(self):
|
|
73
|
-
valid_positions = ["left", "top", "right", "bottom"]
|
|
74
|
-
if self.controls_position not in valid_positions:
|
|
75
|
-
raise ValueError(
|
|
76
|
-
f"Invalid controls position: {self.controls_position}. Must be one of {valid_positions}"
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def is_horizontal(self) -> bool:
|
|
81
|
-
return self.controls_position == "left" or self.controls_position == "right"
|
|
82
|
-
|
|
83
|
-
|
|
84
29
|
class NotebookDeployer:
|
|
85
30
|
"""
|
|
86
31
|
A deployment system for Viewer in Jupyter notebooks using ipywidgets.
|
|
@@ -90,74 +35,127 @@ class NotebookDeployer:
|
|
|
90
35
|
def __init__(
|
|
91
36
|
self,
|
|
92
37
|
viewer: Viewer,
|
|
93
|
-
controls_position:
|
|
94
|
-
|
|
95
|
-
figure_height: float = 6.0,
|
|
96
|
-
controls_width_percent: int = 30,
|
|
38
|
+
controls_position: Literal["left", "top", "right", "bottom"] = "left",
|
|
39
|
+
controls_width_percent: int = 20,
|
|
97
40
|
continuous: bool = False,
|
|
98
|
-
suppress_warnings: bool =
|
|
41
|
+
suppress_warnings: bool = True,
|
|
99
42
|
):
|
|
100
43
|
self.viewer = viewer
|
|
101
|
-
self.
|
|
102
|
-
controls_position=controls_position,
|
|
103
|
-
figure_width=figure_width,
|
|
104
|
-
figure_height=figure_height,
|
|
105
|
-
controls_width_percent=controls_width_percent,
|
|
106
|
-
)
|
|
107
|
-
self.continuous = continuous
|
|
44
|
+
self.components: dict[str, BaseWidget] = {}
|
|
108
45
|
self.suppress_warnings = suppress_warnings
|
|
46
|
+
self._updating = False # Flag to check circular updates
|
|
47
|
+
self.controls_position = controls_position
|
|
48
|
+
self.controls_width_percent = controls_width_percent
|
|
49
|
+
self.continuous = continuous
|
|
109
50
|
|
|
110
51
|
# Initialize containers
|
|
111
52
|
self.backend_type = get_backend_type()
|
|
112
53
|
if self.backend_type not in ["inline", "widget"]:
|
|
113
54
|
warnings.warn(
|
|
114
|
-
"The current backend is not supported. Please use %matplotlib widget or %matplotlib inline.\n"
|
|
115
|
-
"The behavior of the viewer will almost definitely not work as expected
|
|
55
|
+
f"The current backend ({self.backend_type}) is not supported. Please use %matplotlib widget or %matplotlib inline.\n"
|
|
56
|
+
"The behavior of the viewer will almost definitely not work as expected!"
|
|
116
57
|
)
|
|
117
|
-
self.
|
|
118
|
-
self.plot_output = widgets.Output()
|
|
58
|
+
self._last_figure = None
|
|
119
59
|
|
|
120
|
-
|
|
121
|
-
|
|
60
|
+
def deploy(self) -> None:
|
|
61
|
+
"""Deploy the viewer."""
|
|
62
|
+
self.build_components()
|
|
63
|
+
self.build_layout()
|
|
64
|
+
self.backend_type = get_backend_type()
|
|
65
|
+
display(self.layout)
|
|
66
|
+
self.update_plot()
|
|
122
67
|
|
|
123
|
-
|
|
124
|
-
|
|
68
|
+
def build_components(self) -> None:
|
|
69
|
+
"""Create widget instances for all parameters and equip callbacks."""
|
|
70
|
+
for name, param in self.viewer.parameters.items():
|
|
71
|
+
widget = create_widget(param, continuous=self.continuous)
|
|
72
|
+
self.components[name] = widget
|
|
73
|
+
callback = lambda _, n=name: self.handle_component_engagement(n)
|
|
74
|
+
widget.observe(callback)
|
|
125
75
|
|
|
126
|
-
|
|
127
|
-
|
|
76
|
+
def build_layout(self) -> None:
|
|
77
|
+
"""Create the main layout combining controls and plot."""
|
|
128
78
|
|
|
129
|
-
|
|
130
|
-
"""Create widgets for controlling the layout."""
|
|
131
|
-
controls: Dict[str, widgets.Widget] = {}
|
|
79
|
+
self.plot_output = widgets.Output()
|
|
132
80
|
|
|
133
81
|
# Controls width slider for horizontal layouts
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
82
|
+
self.controls = {}
|
|
83
|
+
if self.controls_position in ["left", "right"]:
|
|
84
|
+
self.controls["controls_width"] = widgets.IntSlider(
|
|
85
|
+
value=self.controls_width_percent,
|
|
86
|
+
min=10,
|
|
87
|
+
max=50,
|
|
139
88
|
description="Controls Width %",
|
|
140
89
|
continuous=True,
|
|
141
90
|
layout=widgets.Layout(width="95%"),
|
|
142
91
|
style={"description_width": "initial"},
|
|
143
92
|
)
|
|
144
93
|
|
|
145
|
-
|
|
94
|
+
# Create parameter controls section
|
|
95
|
+
param_box = widgets.VBox(
|
|
96
|
+
[widgets.HTML("<b>Parameters</b>")]
|
|
97
|
+
+ [w.widget for w in self.components.values()],
|
|
98
|
+
layout=widgets.Layout(margin="10px 0px"),
|
|
99
|
+
)
|
|
146
100
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
101
|
+
# Combine all controls
|
|
102
|
+
if self.controls_position in ["left", "right"]:
|
|
103
|
+
# Create layout controls section if horizontal (might include for vertical later when we have more permanent controls...)
|
|
104
|
+
layout_box = widgets.VBox(
|
|
105
|
+
[widgets.HTML("<b>Syd Controls</b>")] + list(self.controls.values()),
|
|
106
|
+
layout=widgets.Layout(margin="10px 0px"),
|
|
153
107
|
)
|
|
154
108
|
|
|
155
|
-
#
|
|
156
|
-
self.
|
|
109
|
+
# Register the controls_width slider's observer
|
|
110
|
+
if "controls_width" in self.controls:
|
|
111
|
+
self.controls["controls_width"].observe(
|
|
112
|
+
self._handle_container_width_change, names="value"
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
widgets_elements = [param_box, layout_box]
|
|
116
|
+
else:
|
|
117
|
+
widgets_elements = [param_box]
|
|
118
|
+
|
|
119
|
+
self.widgets_container = widgets.VBox(
|
|
120
|
+
widgets_elements,
|
|
121
|
+
layout=widgets.Layout(
|
|
122
|
+
width=(
|
|
123
|
+
f"{self.controls_width_percent}%"
|
|
124
|
+
if self.controls_position in ["left", "right"]
|
|
125
|
+
else "100%"
|
|
126
|
+
),
|
|
127
|
+
padding="10px",
|
|
128
|
+
overflow_y="scroll",
|
|
129
|
+
border="1px solid #e5e7eb",
|
|
130
|
+
border_radius="4px 4px 0px 0px",
|
|
131
|
+
),
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
# Create plot container
|
|
135
|
+
self.plot_container = widgets.VBox(
|
|
136
|
+
[self.plot_output],
|
|
137
|
+
layout=widgets.Layout(
|
|
138
|
+
width=(
|
|
139
|
+
f"{100 - self.controls_width_percent}%"
|
|
140
|
+
if self.controls_position in ["left", "right"]
|
|
141
|
+
else "100%"
|
|
142
|
+
),
|
|
143
|
+
padding="10px",
|
|
144
|
+
),
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# Create final layout based on configuration
|
|
148
|
+
if self.controls_position == "left":
|
|
149
|
+
self.layout = widgets.HBox([self.widgets_container, self.plot_container])
|
|
150
|
+
elif self.controls_position == "right":
|
|
151
|
+
self.layout = widgets.HBox([self.plot_container, self.widgets_container])
|
|
152
|
+
elif self.controls_position == "bottom":
|
|
153
|
+
self.layout = widgets.VBox([self.plot_container, self.widgets_container])
|
|
154
|
+
else:
|
|
155
|
+
self.layout = widgets.VBox([self.widgets_container, self.plot_container])
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
"""Handle engagement with an interactive widget."""
|
|
157
|
+
def handle_component_engagement(self, name: str) -> None:
|
|
158
|
+
"""Handle engagement with an interactive component."""
|
|
161
159
|
if self._updating:
|
|
162
160
|
print(
|
|
163
161
|
"Already updating -- there's a circular dependency!"
|
|
@@ -174,61 +172,46 @@ class NotebookDeployer:
|
|
|
174
172
|
if self.suppress_warnings:
|
|
175
173
|
warnings.filterwarnings("ignore", category=ParameterUpdateWarning)
|
|
176
174
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
if
|
|
175
|
+
# Get the component
|
|
176
|
+
component = self.components[name]
|
|
177
|
+
if component.is_action:
|
|
178
|
+
# If the component is an action, call the callback
|
|
180
179
|
parameter = self.viewer.parameters[name]
|
|
181
180
|
parameter.callback(self.viewer.state)
|
|
182
181
|
else:
|
|
183
|
-
|
|
182
|
+
# Otherwise, update the parameter value
|
|
183
|
+
self.viewer.set_parameter_value(name, component.value)
|
|
184
184
|
|
|
185
|
-
# Update any
|
|
186
|
-
self.
|
|
185
|
+
# Update any components that changed due to dependencies
|
|
186
|
+
self.sync_components_with_state()
|
|
187
187
|
|
|
188
188
|
# Update the plot
|
|
189
|
-
self.
|
|
189
|
+
self.update_plot()
|
|
190
190
|
|
|
191
191
|
finally:
|
|
192
192
|
self._updating = False
|
|
193
193
|
|
|
194
|
-
def
|
|
195
|
-
"""
|
|
196
|
-
|
|
197
|
-
def _sync_widgets_with_state(self, exclude: Optional[str] = None) -> None:
|
|
198
|
-
"""Sync widget values with viewer state."""
|
|
194
|
+
def sync_components_with_state(self, exclude: Optional[str] = None) -> None:
|
|
195
|
+
"""Sync component values with viewer state."""
|
|
199
196
|
for name, parameter in self.viewer.parameters.items():
|
|
200
197
|
if name == exclude:
|
|
201
198
|
continue
|
|
202
199
|
|
|
203
|
-
|
|
204
|
-
if not
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
def _handle_figure_size_change(self, change: Dict[str, Any]) -> None:
|
|
208
|
-
"""Handle changes to figure dimensions."""
|
|
209
|
-
if self._current_figure is None:
|
|
210
|
-
return
|
|
211
|
-
|
|
212
|
-
self._redraw_plot()
|
|
213
|
-
|
|
214
|
-
def _handle_container_width_change(self, change: Dict[str, Any]) -> None:
|
|
215
|
-
"""Handle changes to container width proportions."""
|
|
216
|
-
width_percent = self.layout_widgets["controls_width"].value
|
|
217
|
-
self.config.controls_width_percent = width_percent
|
|
200
|
+
component = self.components[name]
|
|
201
|
+
if not component.matches_parameter(parameter):
|
|
202
|
+
component.update_from_parameter(parameter)
|
|
218
203
|
|
|
219
|
-
|
|
220
|
-
self.widgets_container.layout.width = f"{width_percent}%"
|
|
221
|
-
self.plot_container.layout.width = f"{100 - width_percent}%"
|
|
222
|
-
|
|
223
|
-
def _update_plot(self) -> None:
|
|
204
|
+
def update_plot(self) -> None:
|
|
224
205
|
"""Update the plot with current state."""
|
|
225
206
|
state = self.viewer.state
|
|
226
207
|
|
|
227
|
-
with
|
|
208
|
+
with plot_context():
|
|
228
209
|
figure = self.viewer.plot(state)
|
|
229
210
|
|
|
211
|
+
# Update components if plot function updated a parameter
|
|
212
|
+
self.sync_components_with_state()
|
|
213
|
+
|
|
230
214
|
# Close the last figure if it exists to keep matplotlib clean
|
|
231
|
-
# (just moved this from after clear_output.... noting!)
|
|
232
215
|
if self._last_figure is not None:
|
|
233
216
|
plt.close(self._last_figure)
|
|
234
217
|
|
|
@@ -248,83 +231,11 @@ class NotebookDeployer:
|
|
|
248
231
|
|
|
249
232
|
self._last_figure = figure
|
|
250
233
|
|
|
251
|
-
def
|
|
252
|
-
"""
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
widget.observe(lambda change, n=name: self._handle_widget_engagement(n))
|
|
256
|
-
|
|
257
|
-
# Create parameter controls section
|
|
258
|
-
param_box = widgets.VBox(
|
|
259
|
-
[widgets.HTML("<b>Parameters</b>")]
|
|
260
|
-
+ [w.widget for w in self.parameter_widgets.values()],
|
|
261
|
-
layout=widgets.Layout(margin="10px 0px"),
|
|
262
|
-
)
|
|
263
|
-
|
|
264
|
-
# Combine all controls
|
|
265
|
-
if self.config.is_horizontal:
|
|
266
|
-
# Create layout controls section if horizontal (might include for vertical later when we have more permanent controls...)
|
|
267
|
-
layout_box = widgets.VBox(
|
|
268
|
-
[widgets.HTML("<b>Layout Controls</b>")]
|
|
269
|
-
+ list(self.layout_widgets.values()),
|
|
270
|
-
layout=widgets.Layout(margin="10px 0px"),
|
|
271
|
-
)
|
|
272
|
-
widgets_elements = [param_box, layout_box]
|
|
273
|
-
else:
|
|
274
|
-
widgets_elements = [param_box]
|
|
275
|
-
|
|
276
|
-
self.widgets_container = widgets.VBox(
|
|
277
|
-
widgets_elements,
|
|
278
|
-
layout=widgets.Layout(
|
|
279
|
-
width=(
|
|
280
|
-
f"{self.config.controls_width_percent}%"
|
|
281
|
-
if self.config.is_horizontal
|
|
282
|
-
else "100%"
|
|
283
|
-
),
|
|
284
|
-
padding="10px",
|
|
285
|
-
overflow_y="scroll",
|
|
286
|
-
border="1px solid #e5e7eb",
|
|
287
|
-
border_radius="4px 4px 0px 0px",
|
|
288
|
-
),
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
# Create plot container
|
|
292
|
-
self.plot_container = widgets.VBox(
|
|
293
|
-
[self.plot_output],
|
|
294
|
-
layout=widgets.Layout(
|
|
295
|
-
width=(
|
|
296
|
-
f"{100 - self.config.controls_width_percent}%"
|
|
297
|
-
if self.config.is_horizontal
|
|
298
|
-
else "100%"
|
|
299
|
-
),
|
|
300
|
-
padding="10px",
|
|
301
|
-
),
|
|
302
|
-
)
|
|
303
|
-
|
|
304
|
-
# Create final layout based on configuration
|
|
305
|
-
if self.config.controls_position == "left":
|
|
306
|
-
return widgets.HBox([self.widgets_container, self.plot_container])
|
|
307
|
-
elif self.config.controls_position == "right":
|
|
308
|
-
return widgets.HBox([self.plot_container, self.widgets_container])
|
|
309
|
-
elif self.config.controls_position == "bottom":
|
|
310
|
-
return widgets.VBox([self.plot_container, self.widgets_container])
|
|
311
|
-
else:
|
|
312
|
-
return widgets.VBox([self.widgets_container, self.plot_container])
|
|
313
|
-
|
|
314
|
-
def deploy(self) -> None:
|
|
315
|
-
"""Deploy the interactive viewer with proper state management."""
|
|
316
|
-
self.backend_type = get_backend_type()
|
|
317
|
-
|
|
318
|
-
# We used to use the deploy_app context, but notebook deployment works
|
|
319
|
-
# differently because it's asynchronous and this doesn't really behave
|
|
320
|
-
# as intended. (e.g. with self.viewer._deploy_app() ...)
|
|
321
|
-
|
|
322
|
-
# Create widgets
|
|
323
|
-
self._create_parameter_widgets()
|
|
324
|
-
|
|
325
|
-
# Create and display layout
|
|
326
|
-
self.layout = self._create_layout()
|
|
327
|
-
display(self.layout)
|
|
234
|
+
def _handle_container_width_change(self, _) -> None:
|
|
235
|
+
"""Handle changes to container width proportions."""
|
|
236
|
+
width_percent = self.controls["controls_width"].value
|
|
237
|
+
self.controls_width_percent = width_percent
|
|
328
238
|
|
|
329
|
-
#
|
|
330
|
-
self.
|
|
239
|
+
# Update container widths
|
|
240
|
+
self.widgets_container.layout.width = f"{width_percent}%"
|
|
241
|
+
self.plot_container.layout.width = f"{100 - width_percent}%"
|