syd 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.
- syd/__init__.py +1 -0
- syd/interactive_viewer.py +213 -0
- syd/notebook_deploy.py +270 -0
- syd/parameters.py +269 -0
- syd-0.1.0.dist-info/METADATA +33 -0
- syd-0.1.0.dist-info/RECORD +8 -0
- syd-0.1.0.dist-info/WHEEL +4 -0
- syd-0.1.0.dist-info/licenses/LICENSE +674 -0
syd/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
from typing import List, Any, Callable, Dict
|
|
2
|
+
from functools import wraps
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from matplotlib.figure import Figure
|
|
5
|
+
|
|
6
|
+
from .parameters import ParameterType, Parameter
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def validate_parameter_operation(operation: str, parameter_type: ParameterType) -> Callable:
|
|
10
|
+
"""
|
|
11
|
+
Decorator that validates parameter operations for the InteractiveViewer class.
|
|
12
|
+
|
|
13
|
+
This decorator ensures that:
|
|
14
|
+
1. The operation type matches the method name (add/update)
|
|
15
|
+
2. The parameter type matches the method's intended parameter type
|
|
16
|
+
3. Parameters can only be added when the app is not deployed
|
|
17
|
+
4. Parameters can only be updated when the app is deployed
|
|
18
|
+
5. For updates, validates that the parameter exists and is of the correct type
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
operation (str): The type of operation to validate. Must be either 'add' or 'update'.
|
|
22
|
+
parameter_type (ParameterType): The expected parameter type from the ParameterType enum.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
Callable: A decorated function that includes parameter validation.
|
|
26
|
+
|
|
27
|
+
Raises:
|
|
28
|
+
ValueError: If the operation type doesn't match the method name or if updating a non-existent parameter
|
|
29
|
+
TypeError: If updating a parameter with an incorrect type
|
|
30
|
+
RuntimeError: If adding parameters while deployed or updating while not deployed
|
|
31
|
+
|
|
32
|
+
Example:
|
|
33
|
+
@validate_parameter_operation('add', ParameterType.text)
|
|
34
|
+
def add_text(self, name: str, default: str = "") -> None:
|
|
35
|
+
...
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
def decorator(func: Callable) -> Callable:
|
|
39
|
+
@wraps(func)
|
|
40
|
+
def wrapper(self: "InteractiveViewer", name: str, *args, **kwargs):
|
|
41
|
+
# Validate operation matches method name (add/update)
|
|
42
|
+
if not func.__name__.startswith(operation):
|
|
43
|
+
raise ValueError(f"Invalid operation type specified ({operation}) for method {func.__name__}")
|
|
44
|
+
|
|
45
|
+
# Validate deployment state
|
|
46
|
+
if operation == "add" and self._app_deployed:
|
|
47
|
+
raise RuntimeError("The app is currently deployed, cannot add a new parameter right now.")
|
|
48
|
+
|
|
49
|
+
# For updates, validate parameter existence and type
|
|
50
|
+
if operation == "update":
|
|
51
|
+
if name not in self.parameters:
|
|
52
|
+
raise ValueError(f"Parameter called {name} not found - you can only update registered parameters!")
|
|
53
|
+
if type(self.parameters[name]) != parameter_type:
|
|
54
|
+
msg = f"Parameter called {name} was found but is registered as a different parameter type ({type(self.parameters[name])})"
|
|
55
|
+
raise TypeError(msg)
|
|
56
|
+
|
|
57
|
+
return func(self, name, *args, **kwargs)
|
|
58
|
+
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
return decorator
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class InteractiveViewer:
|
|
65
|
+
def __new__(cls):
|
|
66
|
+
instance = super().__new__(cls)
|
|
67
|
+
instance.parameters = {}
|
|
68
|
+
instance.callbacks = {}
|
|
69
|
+
instance.state = {}
|
|
70
|
+
instance._app_deployed = False
|
|
71
|
+
return instance
|
|
72
|
+
|
|
73
|
+
def __init__(self):
|
|
74
|
+
self.parameters: Dict[str, Parameter] = {}
|
|
75
|
+
self.callbacks: Dict[str, List[Callable]] = {}
|
|
76
|
+
self.state = {}
|
|
77
|
+
self._app_deployed = False
|
|
78
|
+
|
|
79
|
+
def param_dict(self) -> Dict[str, Any]:
|
|
80
|
+
return {name: param.value for name, param in self.parameters.items()}
|
|
81
|
+
|
|
82
|
+
def plot(self, **kwargs) -> Figure:
|
|
83
|
+
raise NotImplementedError("Subclasses must implement the plot method")
|
|
84
|
+
|
|
85
|
+
@contextmanager
|
|
86
|
+
def deploy_app(self):
|
|
87
|
+
"""Internal context manager to control app deployment state"""
|
|
88
|
+
self._app_deployed = True
|
|
89
|
+
try:
|
|
90
|
+
yield
|
|
91
|
+
finally:
|
|
92
|
+
self._app_deployed = False
|
|
93
|
+
|
|
94
|
+
def perform_callbacks(self, name: str) -> bool:
|
|
95
|
+
"""Perform callbacks for all parameters that have changed"""
|
|
96
|
+
if name in self.callbacks:
|
|
97
|
+
for callback in self.callbacks[name]:
|
|
98
|
+
callback(self.parameters[name].value)
|
|
99
|
+
|
|
100
|
+
def on_change(self, parameter_name: str, callback: Callable):
|
|
101
|
+
"""Register a function to be called when a parameter changes."""
|
|
102
|
+
if parameter_name not in self.parameters:
|
|
103
|
+
raise ValueError(f"Parameter '{parameter_name}' is not registered!")
|
|
104
|
+
if parameter_name not in self.callbacks:
|
|
105
|
+
self.callbacks[parameter_name] = []
|
|
106
|
+
self.callbacks[parameter_name].append(callback)
|
|
107
|
+
|
|
108
|
+
def set_parameter_value(self, name: str, value: Any) -> None:
|
|
109
|
+
"""Set a parameter value and trigger dependency updates"""
|
|
110
|
+
if name not in self.parameters:
|
|
111
|
+
raise ValueError(f"Parameter {name} not found")
|
|
112
|
+
|
|
113
|
+
# Update the parameter value
|
|
114
|
+
self.parameters[name].value = value
|
|
115
|
+
|
|
116
|
+
# Perform callbacks
|
|
117
|
+
self.perform_callbacks(name)
|
|
118
|
+
|
|
119
|
+
# -------------------- parameter registration methods --------------------
|
|
120
|
+
@validate_parameter_operation("add", ParameterType.text)
|
|
121
|
+
def add_text(self, name: str, default: str = "") -> None:
|
|
122
|
+
self.parameters[name] = ParameterType.text.value(name, default)
|
|
123
|
+
|
|
124
|
+
@validate_parameter_operation("add", ParameterType.selection)
|
|
125
|
+
def add_selection(self, name: str, options: List[Any], default: str = None) -> None:
|
|
126
|
+
self.parameters[name] = ParameterType.selection.value(name, options, default)
|
|
127
|
+
|
|
128
|
+
@validate_parameter_operation("add", ParameterType.multiple_selection)
|
|
129
|
+
def add_multiple_selection(self, name: str, options: List[Any], defaults: List[Any] = None) -> None:
|
|
130
|
+
self.parameters[name] = ParameterType.multiple_selection.value(name, options, defaults)
|
|
131
|
+
|
|
132
|
+
@validate_parameter_operation("add", ParameterType.boolean)
|
|
133
|
+
def add_boolean(self, name: str, default: bool = False) -> None:
|
|
134
|
+
self.parameters[name] = ParameterType.boolean.value(name, default)
|
|
135
|
+
|
|
136
|
+
@validate_parameter_operation("add", ParameterType.integer)
|
|
137
|
+
def add_integer(self, name: str, min_value: int, max_value: int, default: int = None) -> None:
|
|
138
|
+
self.parameters[name] = ParameterType.integer.value(name, min_value, max_value, default)
|
|
139
|
+
|
|
140
|
+
@validate_parameter_operation("add", ParameterType.float)
|
|
141
|
+
def add_float(self, name: str, min_value: float, max_value: float, step: float = 0.1, default: float = None) -> None:
|
|
142
|
+
self.parameters[name] = ParameterType.float.value(name, min_value, max_value, step, default)
|
|
143
|
+
|
|
144
|
+
@validate_parameter_operation("add", ParameterType.integer_pair)
|
|
145
|
+
def add_integer_pair(
|
|
146
|
+
self,
|
|
147
|
+
name: str,
|
|
148
|
+
min_value: int,
|
|
149
|
+
max_value: int,
|
|
150
|
+
default_low: int = None,
|
|
151
|
+
default_high: int = None,
|
|
152
|
+
) -> None:
|
|
153
|
+
self.parameters[name] = ParameterType.integer_pair.value(name, min_value, max_value, default_low, default_high)
|
|
154
|
+
|
|
155
|
+
@validate_parameter_operation("add", ParameterType.float_pair)
|
|
156
|
+
def add_float_pair(
|
|
157
|
+
self,
|
|
158
|
+
name: str,
|
|
159
|
+
min_value: float,
|
|
160
|
+
max_value: float,
|
|
161
|
+
step: float = 0.1,
|
|
162
|
+
default_low: float = None,
|
|
163
|
+
default_high: float = None,
|
|
164
|
+
) -> None:
|
|
165
|
+
self.parameters[name] = ParameterType.float_pair.value(name, min_value, max_value, step, default_low, default_high)
|
|
166
|
+
|
|
167
|
+
# -------------------- parameter update methods --------------------
|
|
168
|
+
@validate_parameter_operation("update", ParameterType.text)
|
|
169
|
+
def update_text(self, name: str, default: str = "") -> None:
|
|
170
|
+
self.parameters[name] = ParameterType.text.value(name, default)
|
|
171
|
+
|
|
172
|
+
@validate_parameter_operation("update", ParameterType.selection)
|
|
173
|
+
def update_selection(self, name: str, options: List[Any], default: str = None) -> None:
|
|
174
|
+
self.parameters[name] = ParameterType.selection.value(name, options, default)
|
|
175
|
+
|
|
176
|
+
@validate_parameter_operation("update", ParameterType.multiple_selection)
|
|
177
|
+
def update_multiple_selection(self, name: str, options: List[Any], defaults: List[Any] = None) -> None:
|
|
178
|
+
self.parameters[name] = ParameterType.multiple_selection.value(name, options, defaults)
|
|
179
|
+
|
|
180
|
+
@validate_parameter_operation("update", ParameterType.boolean)
|
|
181
|
+
def update_boolean(self, name: str, default: bool = False) -> None:
|
|
182
|
+
self.parameters[name] = ParameterType.boolean.value(name, default)
|
|
183
|
+
|
|
184
|
+
@validate_parameter_operation("update", ParameterType.integer)
|
|
185
|
+
def update_integer(self, name: str, min_value: int, max_value: int, default: int = None) -> None:
|
|
186
|
+
self.parameters[name] = ParameterType.integer.value(name, min_value, max_value, default)
|
|
187
|
+
|
|
188
|
+
@validate_parameter_operation("update", ParameterType.float)
|
|
189
|
+
def update_float(self, name: str, min_value: float, max_value: float, step: float = 0.1, default: float = None) -> None:
|
|
190
|
+
self.parameters[name] = ParameterType.float.value(name, min_value, max_value, step, default)
|
|
191
|
+
|
|
192
|
+
@validate_parameter_operation("update", ParameterType.integer_pair)
|
|
193
|
+
def update_integer_pair(
|
|
194
|
+
self,
|
|
195
|
+
name: str,
|
|
196
|
+
min_value: int,
|
|
197
|
+
max_value: int,
|
|
198
|
+
default_low: int = None,
|
|
199
|
+
default_high: int = None,
|
|
200
|
+
) -> None:
|
|
201
|
+
self.parameters[name] = ParameterType.integer_pair.value(name, min_value, max_value, default_low, default_high)
|
|
202
|
+
|
|
203
|
+
@validate_parameter_operation("update", ParameterType.float_pair)
|
|
204
|
+
def update_float_pair(
|
|
205
|
+
self,
|
|
206
|
+
name: str,
|
|
207
|
+
min_value: float,
|
|
208
|
+
max_value: float,
|
|
209
|
+
step: float = 0.1,
|
|
210
|
+
default_low: float = None,
|
|
211
|
+
default_high: float = None,
|
|
212
|
+
) -> None:
|
|
213
|
+
self.parameters[name] = ParameterType.float_pair.value(name, min_value, max_value, step, default_low, default_high)
|
syd/notebook_deploy.py
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
from typing import Dict, Any
|
|
2
|
+
import matplotlib.pyplot as plt
|
|
3
|
+
import ipywidgets as widgets
|
|
4
|
+
from IPython.display import display
|
|
5
|
+
from .parameters import (
|
|
6
|
+
Parameter,
|
|
7
|
+
TextParameter,
|
|
8
|
+
SingleSelectionParameter,
|
|
9
|
+
MultipleSelectionParameter,
|
|
10
|
+
BooleanParameter,
|
|
11
|
+
IntegerParameter,
|
|
12
|
+
FloatParameter,
|
|
13
|
+
IntegerPairParameter,
|
|
14
|
+
FloatPairParameter,
|
|
15
|
+
)
|
|
16
|
+
from .interactive_viewer import InteractiveViewer
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class NotebookDeployment:
|
|
20
|
+
"""
|
|
21
|
+
Deployment system for InteractiveViewer in Jupyter notebooks using ipywidgets.
|
|
22
|
+
Includes enhanced layout control and figure size management.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, viewer: InteractiveViewer):
|
|
26
|
+
"""Initialize with an InteractiveViewer instance."""
|
|
27
|
+
self.viewer = viewer
|
|
28
|
+
self.widgets: Dict[str, widgets.Widget] = {}
|
|
29
|
+
self._widget_callbacks = {}
|
|
30
|
+
self._current_figure = None
|
|
31
|
+
|
|
32
|
+
# Default figure size
|
|
33
|
+
self.fig_width = 8
|
|
34
|
+
self.fig_height = 6
|
|
35
|
+
|
|
36
|
+
def _create_text_widget(self, param: TextParameter) -> widgets.Text:
|
|
37
|
+
"""Create a text input widget."""
|
|
38
|
+
w = widgets.Text(value=param.default, description=param.name, layout=widgets.Layout(width="95%"), style={"description_width": "initial"})
|
|
39
|
+
return w
|
|
40
|
+
|
|
41
|
+
def _create_selection_widget(self, param: SingleSelectionParameter) -> widgets.Dropdown:
|
|
42
|
+
"""Create a dropdown selection widget."""
|
|
43
|
+
w = widgets.Dropdown(
|
|
44
|
+
options=param.options,
|
|
45
|
+
value=param.default if param.default else param.options[0],
|
|
46
|
+
description=param.name,
|
|
47
|
+
layout=widgets.Layout(width="95%"),
|
|
48
|
+
style={"description_width": "initial"},
|
|
49
|
+
)
|
|
50
|
+
return w
|
|
51
|
+
|
|
52
|
+
def _create_multiple_selection_widget(self, param: MultipleSelectionParameter) -> widgets.SelectMultiple:
|
|
53
|
+
"""Create a multiple selection widget."""
|
|
54
|
+
w = widgets.SelectMultiple(
|
|
55
|
+
options=param.options,
|
|
56
|
+
value=param.default if param.default else [],
|
|
57
|
+
description=param.name,
|
|
58
|
+
layout=widgets.Layout(width="95%"),
|
|
59
|
+
style={"description_width": "initial"},
|
|
60
|
+
)
|
|
61
|
+
return w
|
|
62
|
+
|
|
63
|
+
def _create_boolean_widget(self, param: BooleanParameter) -> widgets.Checkbox:
|
|
64
|
+
"""Create a checkbox widget."""
|
|
65
|
+
w = widgets.Checkbox(value=param.default, description=param.name, layout=widgets.Layout(width="95%"), style={"description_width": "initial"})
|
|
66
|
+
return w
|
|
67
|
+
|
|
68
|
+
def _create_integer_widget(self, param: IntegerParameter) -> widgets.IntSlider:
|
|
69
|
+
"""Create an integer slider widget."""
|
|
70
|
+
w = widgets.IntSlider(
|
|
71
|
+
value=param.default if param.default is not None else param.min_value,
|
|
72
|
+
min=param.min_value,
|
|
73
|
+
max=param.max_value,
|
|
74
|
+
description=param.name,
|
|
75
|
+
layout=widgets.Layout(width="95%"),
|
|
76
|
+
style={"description_width": "initial"},
|
|
77
|
+
)
|
|
78
|
+
return w
|
|
79
|
+
|
|
80
|
+
def _create_float_widget(self, param: FloatParameter) -> widgets.FloatSlider:
|
|
81
|
+
"""Create a float slider widget."""
|
|
82
|
+
w = widgets.FloatSlider(
|
|
83
|
+
value=param.default if param.default is not None else param.min_value,
|
|
84
|
+
min=param.min_value,
|
|
85
|
+
max=param.max_value,
|
|
86
|
+
step=param.step,
|
|
87
|
+
description=param.name,
|
|
88
|
+
layout=widgets.Layout(width="95%"),
|
|
89
|
+
style={"description_width": "initial"},
|
|
90
|
+
)
|
|
91
|
+
return w
|
|
92
|
+
|
|
93
|
+
def _create_integer_pair_widget(self, param: IntegerPairParameter) -> widgets.HBox:
|
|
94
|
+
"""Create a pair of integer input widgets."""
|
|
95
|
+
low = widgets.IntText(
|
|
96
|
+
value=param.default_low if param.default_low is not None else param.min_value,
|
|
97
|
+
description=f"{param.name} (low)",
|
|
98
|
+
layout=widgets.Layout(width="47%"),
|
|
99
|
+
style={"description_width": "initial"},
|
|
100
|
+
)
|
|
101
|
+
high = widgets.IntText(
|
|
102
|
+
value=param.default_high if param.default_high is not None else param.max_value,
|
|
103
|
+
description=f"{param.name} (high)",
|
|
104
|
+
layout=widgets.Layout(width="47%"),
|
|
105
|
+
style={"description_width": "initial"},
|
|
106
|
+
)
|
|
107
|
+
return widgets.HBox([low, high], layout=widgets.Layout(width="95%"))
|
|
108
|
+
|
|
109
|
+
def _create_float_pair_widget(self, param: FloatPairParameter) -> widgets.HBox:
|
|
110
|
+
"""Create a pair of float input widgets."""
|
|
111
|
+
low = widgets.FloatText(
|
|
112
|
+
value=param.default_low if param.default_low is not None else param.min_value,
|
|
113
|
+
description=f"{param.name} (low)",
|
|
114
|
+
layout=widgets.Layout(width="47%"),
|
|
115
|
+
style={"description_width": "initial"},
|
|
116
|
+
)
|
|
117
|
+
high = widgets.FloatText(
|
|
118
|
+
value=param.default_high if param.default_high is not None else param.max_value,
|
|
119
|
+
description=f"{param.name} (high)",
|
|
120
|
+
layout=widgets.Layout(width="47%"),
|
|
121
|
+
style={"description_width": "initial"},
|
|
122
|
+
)
|
|
123
|
+
return widgets.HBox([low, high], layout=widgets.Layout(width="95%"))
|
|
124
|
+
|
|
125
|
+
def _create_widget_for_parameter(self, param: Parameter) -> widgets.Widget:
|
|
126
|
+
"""Create the appropriate widget based on parameter type."""
|
|
127
|
+
widget_creators = {
|
|
128
|
+
TextParameter: self._create_text_widget,
|
|
129
|
+
SingleSelectionParameter: self._create_selection_widget,
|
|
130
|
+
MultipleSelectionParameter: self._create_multiple_selection_widget,
|
|
131
|
+
BooleanParameter: self._create_boolean_widget,
|
|
132
|
+
IntegerParameter: self._create_integer_widget,
|
|
133
|
+
FloatParameter: self._create_float_widget,
|
|
134
|
+
IntegerPairParameter: self._create_integer_pair_widget,
|
|
135
|
+
FloatPairParameter: self._create_float_pair_widget,
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
creator = widget_creators.get(type(param))
|
|
139
|
+
if not creator:
|
|
140
|
+
raise ValueError(f"Unsupported parameter type: {type(param)}")
|
|
141
|
+
|
|
142
|
+
return creator(param)
|
|
143
|
+
|
|
144
|
+
def _create_size_controls(self) -> widgets.VBox:
|
|
145
|
+
"""Create controls for adjusting the figure size."""
|
|
146
|
+
self.width_slider = widgets.FloatSlider(
|
|
147
|
+
value=self.fig_width,
|
|
148
|
+
min=4,
|
|
149
|
+
max=20,
|
|
150
|
+
step=0.5,
|
|
151
|
+
description="Figure Width",
|
|
152
|
+
layout=widgets.Layout(width="95%"),
|
|
153
|
+
style={"description_width": "initial"},
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
self.height_slider = widgets.FloatSlider(
|
|
157
|
+
value=self.fig_height,
|
|
158
|
+
min=3,
|
|
159
|
+
max=15,
|
|
160
|
+
step=0.5,
|
|
161
|
+
description="Figure Height",
|
|
162
|
+
layout=widgets.Layout(width="95%"),
|
|
163
|
+
style={"description_width": "initial"},
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
self.container_width = widgets.IntSlider(
|
|
167
|
+
value=30,
|
|
168
|
+
min=20,
|
|
169
|
+
max=80,
|
|
170
|
+
description="Controls Width %",
|
|
171
|
+
layout=widgets.Layout(width="95%"),
|
|
172
|
+
style={"description_width": "initial"},
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
# Add callbacks for size changes
|
|
176
|
+
self.width_slider.observe(self._handle_size_change, names="value")
|
|
177
|
+
self.height_slider.observe(self._handle_size_change, names="value")
|
|
178
|
+
self.container_width.observe(self._handle_container_width_change, names="value")
|
|
179
|
+
|
|
180
|
+
return widgets.VBox(
|
|
181
|
+
[widgets.HTML("<b>Layout Controls</b>"), self.width_slider, self.height_slider, self.container_width],
|
|
182
|
+
layout=widgets.Layout(margin="10px 0px"),
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
def _handle_size_change(self, change: Dict[str, Any]) -> None:
|
|
186
|
+
"""Handle changes to figure size."""
|
|
187
|
+
self.fig_width = self.width_slider.value
|
|
188
|
+
self.fig_height = self.height_slider.value
|
|
189
|
+
self._update_plot()
|
|
190
|
+
|
|
191
|
+
def _handle_container_width_change(self, change: Dict[str, Any]) -> None:
|
|
192
|
+
"""Handle changes to container widths."""
|
|
193
|
+
controls_width = f"{self.container_width.value}%"
|
|
194
|
+
plot_width = f"{100 - self.container_width.value}%"
|
|
195
|
+
|
|
196
|
+
self.widgets_container.layout.width = controls_width
|
|
197
|
+
self.plot_container.layout.width = plot_width
|
|
198
|
+
|
|
199
|
+
def _handle_widget_change(self, name: str, change: Dict[str, Any]) -> None:
|
|
200
|
+
"""Handle widget value changes and update the viewer parameter."""
|
|
201
|
+
if isinstance(self.widgets[name], widgets.HBox):
|
|
202
|
+
# Handle pair widgets
|
|
203
|
+
low_value = self.widgets[name].children[0].value
|
|
204
|
+
high_value = self.widgets[name].children[1].value
|
|
205
|
+
value = (low_value, high_value)
|
|
206
|
+
else:
|
|
207
|
+
value = change["new"]
|
|
208
|
+
|
|
209
|
+
self.viewer.set_parameter_value(name, value)
|
|
210
|
+
self._update_plot()
|
|
211
|
+
|
|
212
|
+
def _update_plot(self) -> None:
|
|
213
|
+
"""Update the plot with current parameters and size."""
|
|
214
|
+
self._current_fig.clear()
|
|
215
|
+
self._current_fig.set_size_inches(self.fig_width, self.fig_height)
|
|
216
|
+
self._current_fig = self.viewer.plot(fig=self._current_fig, state=self.viewer.param_dict())
|
|
217
|
+
|
|
218
|
+
# Apply tight layout to remove dead space
|
|
219
|
+
self._current_fig.tight_layout()
|
|
220
|
+
|
|
221
|
+
self.plot_output.clear_output(wait=True)
|
|
222
|
+
with self.plot_output:
|
|
223
|
+
display(self._current_fig)
|
|
224
|
+
|
|
225
|
+
def create_widgets(self) -> None:
|
|
226
|
+
"""Create widgets for all parameters in the viewer."""
|
|
227
|
+
for name, param in self.viewer.parameters.items():
|
|
228
|
+
widget = self._create_widget_for_parameter(param)
|
|
229
|
+
self.widgets[name] = widget
|
|
230
|
+
|
|
231
|
+
# Set up callback
|
|
232
|
+
if isinstance(widget, widgets.HBox):
|
|
233
|
+
# For pair widgets, observe both components
|
|
234
|
+
widget.children[0].observe(lambda change, n=name: self._handle_widget_change(n, change), names="value")
|
|
235
|
+
widget.children[1].observe(lambda change, n=name: self._handle_widget_change(n, change), names="value")
|
|
236
|
+
else:
|
|
237
|
+
widget.observe(lambda change, n=name: self._handle_widget_change(n, change), names="value")
|
|
238
|
+
|
|
239
|
+
def display_widgets(self) -> None:
|
|
240
|
+
"""Display all widgets and plot in an organized layout."""
|
|
241
|
+
# Create size controls
|
|
242
|
+
size_controls = self._create_size_controls()
|
|
243
|
+
|
|
244
|
+
# Create widgets container with parameters and size controls
|
|
245
|
+
all_widgets = list(self.widgets.values()) + [size_controls]
|
|
246
|
+
self.widgets_container = widgets.VBox(all_widgets, layout=widgets.Layout(width="30%", padding="10px", overflow_y="auto"))
|
|
247
|
+
|
|
248
|
+
# Create output widget for plot
|
|
249
|
+
self.plot_output = widgets.Output()
|
|
250
|
+
self.plot_container = widgets.VBox([self.plot_output], layout=widgets.Layout(width="70%", padding="10px"))
|
|
251
|
+
|
|
252
|
+
# Combine widgets and plot in layout
|
|
253
|
+
# Make the container height dynamic
|
|
254
|
+
layout = widgets.HBox(
|
|
255
|
+
[self.widgets_container, self.plot_container], layout=widgets.Layout(width="100%", height="auto") # Dynamic height based on content
|
|
256
|
+
)
|
|
257
|
+
display(layout)
|
|
258
|
+
|
|
259
|
+
# Create initial plot
|
|
260
|
+
self._current_fig = plt.figure(figsize=(self.fig_width, self.fig_height))
|
|
261
|
+
plt.close(self._current_fig) # close the figure to prevent it from being displayed
|
|
262
|
+
self._current_fig = self.viewer.plot(fig=self._current_fig, state=self.viewer.param_dict())
|
|
263
|
+
with self.plot_output:
|
|
264
|
+
display(self._current_fig)
|
|
265
|
+
|
|
266
|
+
def deploy(self) -> None:
|
|
267
|
+
"""Create and display all widgets with proper deployment state management."""
|
|
268
|
+
with self.viewer.deploy_app():
|
|
269
|
+
self.create_widgets()
|
|
270
|
+
self.display_widgets()
|