steer-core 0.1.5__py3-none-any.whl → 0.1.8__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.
- steer_core/Apps/Components/CompoundComponents.py +0 -0
- steer_core/Apps/Components/RangeSliderComponents.py +500 -0
- steer_core/Apps/Components/SliderComponents.py +657 -0
- steer_core/Apps/Components/__init__.py +0 -0
- steer_core/Apps/ContextManagers.py +55 -0
- steer_core/Apps/Performance/CallbackTimer.py +17 -0
- steer_core/Apps/Performance/__init__.py +0 -0
- steer_core/Apps/Utils/SliderControls.py +677 -0
- steer_core/Apps/Utils/__init__.py +0 -0
- steer_core/Apps/__init__.py +0 -0
- steer_core/Data/__init__.py +0 -0
- steer_core/__init__.py +1 -1
- {steer_core-0.1.5.dist-info → steer_core-0.1.8.dist-info}/METADATA +1 -1
- {steer_core-0.1.5.dist-info → steer_core-0.1.8.dist-info}/RECORD +16 -5
- {steer_core-0.1.5.dist-info → steer_core-0.1.8.dist-info}/WHEEL +0 -0
- {steer_core-0.1.5.dist-info → steer_core-0.1.8.dist-info}/top_level.txt +0 -0
File without changes
|
@@ -0,0 +1,500 @@
|
|
1
|
+
import dash as ds
|
2
|
+
import numpy as np
|
3
|
+
from typing import Union
|
4
|
+
from dash import Input, Output
|
5
|
+
|
6
|
+
|
7
|
+
class RangeSliderWithTextInput:
|
8
|
+
"""
|
9
|
+
A custom Dash component that combines a range slider with two text inputs for synchronized range control.
|
10
|
+
|
11
|
+
This component creates a user interface element consisting of a range slider and two numeric input fields
|
12
|
+
that can be used together to set numeric ranges. The range slider provides visual feedback and easy
|
13
|
+
adjustment of both start and end values, while the text inputs allow for precise value entry. All
|
14
|
+
components are synchronized and share the same value constraints.
|
15
|
+
|
16
|
+
The component is designed for use in Dash applications where users need to input numeric ranges
|
17
|
+
within a specified domain, with the flexibility of both visual (range slider) and precise (text inputs)
|
18
|
+
control methods.
|
19
|
+
|
20
|
+
Attributes:
|
21
|
+
id_base (dict): Base identifier dictionary used to construct unique IDs for child components
|
22
|
+
min_val (float): Minimum allowed value for both slider and inputs
|
23
|
+
max_val (float): Maximum allowed value for both slider and inputs
|
24
|
+
default_val (list[float]): Default range values to display on initialization [start, end]
|
25
|
+
step (float): Step size for value increments/decrements
|
26
|
+
mark_interval (float): Interval between tick marks on the slider
|
27
|
+
property_name (str): Property identifier used in component ID construction
|
28
|
+
title (str): Display title for the component
|
29
|
+
with_slider_titles (bool): Whether to show the title above the slider
|
30
|
+
div_width (str): CSS width specification for the container div
|
31
|
+
slider_disable (bool): Whether the components should be disabled
|
32
|
+
message (str): Optional message displayed between title and slider
|
33
|
+
slider_id (dict): Computed ID for the range slider component
|
34
|
+
input_start_id (dict): Computed ID for the start value input component
|
35
|
+
input_end_id (dict): Computed ID for the end value input component
|
36
|
+
|
37
|
+
Example:
|
38
|
+
>>> range_component = RangeSliderWithTextInput(
|
39
|
+
... id_base={'type': 'parameter', 'index': 0},
|
40
|
+
... min_val=0.0,
|
41
|
+
... max_val=100.0,
|
42
|
+
... step=1.0,
|
43
|
+
... mark_interval=10.0,
|
44
|
+
... property_name='temperature_range',
|
45
|
+
... title='Temperature Range (°C)',
|
46
|
+
... default_val=[20.0, 30.0],
|
47
|
+
... message='Select optimal temperature range' # Optional message
|
48
|
+
... )
|
49
|
+
>>> layout_element = range_component() # Returns Dash HTML Div component
|
50
|
+
"""
|
51
|
+
|
52
|
+
def __init__(
|
53
|
+
self,
|
54
|
+
id_base: dict,
|
55
|
+
min_val: float,
|
56
|
+
max_val: float,
|
57
|
+
step: float,
|
58
|
+
mark_interval: float,
|
59
|
+
property_name: str,
|
60
|
+
title: str,
|
61
|
+
default_val: Union[list[float], None] = None,
|
62
|
+
with_slider_titles: bool = True,
|
63
|
+
slider_disable: bool = False,
|
64
|
+
div_width: str = 'calc(90%)',
|
65
|
+
message: str = None,
|
66
|
+
):
|
67
|
+
"""
|
68
|
+
Initialize the RangeSliderWithTextInput component.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
id_base (dict): Base dictionary for generating component IDs. Should contain
|
72
|
+
identifying information that will be extended with component-specific
|
73
|
+
subtypes and properties.
|
74
|
+
min_val (float): Minimum value that can be selected on the slider or entered
|
75
|
+
in the text inputs.
|
76
|
+
max_val (float): Maximum value that can be selected on the slider or entered
|
77
|
+
in the text inputs.
|
78
|
+
step (float): The granularity of value changes. Determines the smallest
|
79
|
+
increment/decrement possible.
|
80
|
+
mark_interval (float): The spacing between tick marks displayed on the slider.
|
81
|
+
Should be a multiple of step for best visual alignment.
|
82
|
+
property_name (str): A string identifier for this specific property, used
|
83
|
+
in ID generation and callbacks.
|
84
|
+
title (str): Human-readable title displayed above the component.
|
85
|
+
default_val (list[float], optional): Initial range values to display [start, end].
|
86
|
+
If None, defaults to [min_val, max_val].
|
87
|
+
with_slider_titles (bool, optional): If True, displays the title above
|
88
|
+
the slider. If False, shows a non-breaking
|
89
|
+
space to maintain layout. Defaults to True.
|
90
|
+
slider_disable (bool, optional): If True, disables slider and input
|
91
|
+
interactions. Defaults to False.
|
92
|
+
div_width (str, optional): CSS width specification for the container div.
|
93
|
+
Defaults to 'calc(90%)'.
|
94
|
+
message (str, optional): Optional message to display between the title
|
95
|
+
and slider. If None, no message is displayed.
|
96
|
+
Defaults to None.
|
97
|
+
|
98
|
+
Raises:
|
99
|
+
ValueError: If min_val >= max_val, or if step <= 0, or if mark_interval <= 0,
|
100
|
+
or if default_val contains invalid range values.
|
101
|
+
TypeError: If default_val is provided but not a list of numeric values.
|
102
|
+
"""
|
103
|
+
|
104
|
+
# Validate inputs
|
105
|
+
if min_val >= max_val:
|
106
|
+
raise ValueError(f"min_val ({min_val}) must be less than max_val ({max_val})")
|
107
|
+
if step <= 0:
|
108
|
+
raise ValueError(f"step ({step}) must be positive")
|
109
|
+
if mark_interval <= 0:
|
110
|
+
raise ValueError(f"mark_interval ({mark_interval}) must be positive")
|
111
|
+
|
112
|
+
# Validate and set default values
|
113
|
+
if default_val is None:
|
114
|
+
default_val = [min_val, max_val]
|
115
|
+
else:
|
116
|
+
if not isinstance(default_val, list) or len(default_val) != 2:
|
117
|
+
raise TypeError("default_val must be a list of two numeric values [start, end]")
|
118
|
+
try:
|
119
|
+
default_val = [float(default_val[0]), float(default_val[1])]
|
120
|
+
except (ValueError, TypeError):
|
121
|
+
raise TypeError("default_val must contain numeric values")
|
122
|
+
if default_val[0] > default_val[1]:
|
123
|
+
raise ValueError("default_val start value must be <= end value")
|
124
|
+
|
125
|
+
self.id_base = id_base
|
126
|
+
self.min_val = min_val
|
127
|
+
self.max_val = max_val
|
128
|
+
self.default_val = self._validate_and_clamp_range(default_val)
|
129
|
+
self.step = step
|
130
|
+
self.mark_interval = mark_interval
|
131
|
+
self.property_name = property_name
|
132
|
+
self.title = title
|
133
|
+
self.with_slider_titles = with_slider_titles
|
134
|
+
self.div_width = div_width
|
135
|
+
self.slider_disable = slider_disable
|
136
|
+
self.message = message
|
137
|
+
|
138
|
+
self.slider_id = self._make_id('rangeslider')
|
139
|
+
self.input_start_id = self._make_id('input_start')
|
140
|
+
self.input_end_id = self._make_id('input_end')
|
141
|
+
|
142
|
+
def _make_id(self, subtype: str):
|
143
|
+
"""
|
144
|
+
Generate a unique ID dictionary for component sub-elements.
|
145
|
+
|
146
|
+
Combines the base ID with component-specific subtype and property information
|
147
|
+
to create unique identifiers for Dash callbacks and component references.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
subtype (str): The specific component subtype (e.g., 'rangeslider', 'input_start', 'input_end').
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
dict: Complete ID dictionary containing base ID information plus subtype
|
154
|
+
and property specifications.
|
155
|
+
|
156
|
+
Example:
|
157
|
+
>>> component._make_id('rangeslider')
|
158
|
+
{'type': 'parameter', 'index': 0, 'subtype': 'rangeslider', 'property': 'temperature_range'}
|
159
|
+
"""
|
160
|
+
return {**self.id_base, 'subtype': subtype, 'property': self.property_name}
|
161
|
+
|
162
|
+
def _make_range_slider(self):
|
163
|
+
"""
|
164
|
+
Create and configure the Dash range slider component.
|
165
|
+
|
166
|
+
Generates a dcc.RangeSlider with the specified range, step size, default values,
|
167
|
+
and tick marks. The range slider provides visual feedback for range selection
|
168
|
+
and is synchronized with the text input components.
|
169
|
+
|
170
|
+
Returns:
|
171
|
+
dash.dcc.RangeSlider: Configured range slider component with ID, value constraints,
|
172
|
+
tick marks, and styling options.
|
173
|
+
|
174
|
+
Note:
|
175
|
+
- Tick marks are generated at intervals specified by mark_interval
|
176
|
+
- updatemode is set to 'mouseup' to reduce callback frequency
|
177
|
+
- The slider can be disabled via the slider_disable attribute
|
178
|
+
"""
|
179
|
+
return ds.dcc.RangeSlider(
|
180
|
+
id=self.slider_id,
|
181
|
+
min=self.min_val,
|
182
|
+
max=self.max_val,
|
183
|
+
value=self.default_val,
|
184
|
+
step=self.step,
|
185
|
+
disabled=self.slider_disable,
|
186
|
+
marks={int(i): "" for i in np.arange(self.min_val, self.max_val + self.mark_interval, self.mark_interval)},
|
187
|
+
updatemode='mouseup'
|
188
|
+
)
|
189
|
+
|
190
|
+
def _make_start_input(self):
|
191
|
+
"""
|
192
|
+
Create and configure the Dash numeric input component for the start value.
|
193
|
+
|
194
|
+
Generates a dcc.Input with number type for precise start value entry.
|
195
|
+
The input is synchronized with the range slider and provides an alternative
|
196
|
+
method for users to specify exact start values.
|
197
|
+
|
198
|
+
Returns:
|
199
|
+
dash.dcc.Input: Configured numeric input component with ID, type,
|
200
|
+
value constraints, styling, and step specification.
|
201
|
+
|
202
|
+
Note:
|
203
|
+
- Input type is set to 'number' for numeric validation
|
204
|
+
- Width and margin styling provides visual alignment
|
205
|
+
- Step size matches the slider for consistent granularity
|
206
|
+
"""
|
207
|
+
return ds.dcc.Input(
|
208
|
+
id=self.input_start_id,
|
209
|
+
type='number',
|
210
|
+
value=self.default_val[0],
|
211
|
+
step=self.step,
|
212
|
+
style={'width': '80px'},
|
213
|
+
disabled=self.slider_disable,
|
214
|
+
)
|
215
|
+
|
216
|
+
def _make_end_input(self):
|
217
|
+
"""
|
218
|
+
Create and configure the Dash numeric input component for the end value.
|
219
|
+
|
220
|
+
Generates a dcc.Input with number type for precise end value entry.
|
221
|
+
The input is synchronized with the range slider and provides an alternative
|
222
|
+
method for users to specify exact end values.
|
223
|
+
|
224
|
+
Returns:
|
225
|
+
dash.dcc.Input: Configured numeric input component with ID, type,
|
226
|
+
value constraints, styling, and step specification.
|
227
|
+
|
228
|
+
Note:
|
229
|
+
- Input type is set to 'number' for numeric validation
|
230
|
+
- Width and margin styling provides visual alignment
|
231
|
+
- Step size matches the slider for consistent granularity
|
232
|
+
"""
|
233
|
+
return ds.dcc.Input(
|
234
|
+
id=self.input_end_id,
|
235
|
+
type='number',
|
236
|
+
value=self.default_val[1],
|
237
|
+
step=self.step,
|
238
|
+
style={'margin-left': '10px', 'width': '80px'},
|
239
|
+
disabled=self.slider_disable,
|
240
|
+
)
|
241
|
+
|
242
|
+
def __call__(self):
|
243
|
+
"""
|
244
|
+
Generate the complete component layout as a callable object.
|
245
|
+
|
246
|
+
Creates and returns a Dash HTML Div containing the title, optional message,
|
247
|
+
range slider, and input components arranged in a cohesive layout. This method allows
|
248
|
+
the class instance to be used as a callable that returns the complete
|
249
|
+
component structure.
|
250
|
+
|
251
|
+
Returns:
|
252
|
+
dash.html.Div: Complete component layout containing:
|
253
|
+
- Title paragraph (conditional based on with_slider_titles)
|
254
|
+
- Optional message paragraph (if message is provided)
|
255
|
+
- Range slider component in a styled container
|
256
|
+
- Start and end numeric input components with labels
|
257
|
+
- Spacing elements (line breaks)
|
258
|
+
|
259
|
+
Note:
|
260
|
+
- Title display is controlled by with_slider_titles attribute
|
261
|
+
- When title is hidden, a non-breaking space maintains layout
|
262
|
+
- Message is displayed only if provided during initialization
|
263
|
+
- Negative bottom margin on slider container reduces spacing
|
264
|
+
- Container width is controlled by div_width attribute
|
265
|
+
- Input labels provide clear indication of start/end values
|
266
|
+
"""
|
267
|
+
slider_title = self.title if self.with_slider_titles else "\u00A0"
|
268
|
+
|
269
|
+
# Build the component list
|
270
|
+
components = [
|
271
|
+
ds.html.P(slider_title, style={'margin-left': '20px', 'margin-bottom': '0px'})
|
272
|
+
]
|
273
|
+
|
274
|
+
# Add optional message if provided
|
275
|
+
if self.message:
|
276
|
+
components.append(
|
277
|
+
ds.html.P(self.message, style={
|
278
|
+
'margin-left': '20px',
|
279
|
+
'margin-bottom': '5px',
|
280
|
+
'margin-top': '2px',
|
281
|
+
'font-size': '0.9em',
|
282
|
+
'color': '#666666',
|
283
|
+
'font-style': 'italic'
|
284
|
+
})
|
285
|
+
)
|
286
|
+
|
287
|
+
# Add slider and input components
|
288
|
+
components.extend([
|
289
|
+
ds.html.Div([self._make_range_slider()], style={'margin-bottom': '-18px'}),
|
290
|
+
ds.html.Div([
|
291
|
+
ds.html.Span("Start:", style={'margin-left': '20px', 'margin-right': '5px'}),
|
292
|
+
self._make_start_input(),
|
293
|
+
ds.html.Span("End:", style={'margin-left': '15px', 'margin-right': '5px'}),
|
294
|
+
self._make_end_input(),
|
295
|
+
], style={'display': 'flex', 'align-items': 'center'}),
|
296
|
+
ds.html.Br(), ds.html.Br()
|
297
|
+
])
|
298
|
+
|
299
|
+
return ds.html.Div(components, style={'width': self.div_width, 'margin-left': '-20px'})
|
300
|
+
|
301
|
+
@property
|
302
|
+
def components(self):
|
303
|
+
"""
|
304
|
+
Get a dictionary mapping component types to their IDs.
|
305
|
+
|
306
|
+
Provides easy access to the IDs of the range slider and input components
|
307
|
+
for use in Dash callbacks and component interactions.
|
308
|
+
|
309
|
+
Returns:
|
310
|
+
dict: Dictionary with component type keys mapping to their ID dictionaries.
|
311
|
+
|
312
|
+
Example:
|
313
|
+
>>> component.components
|
314
|
+
{
|
315
|
+
'rangeslider': {'type': 'parameter', 'subtype': 'rangeslider', 'property': 'temp_range'},
|
316
|
+
'input_start': {'type': 'parameter', 'subtype': 'input_start', 'property': 'temp_range'},
|
317
|
+
'input_end': {'type': 'parameter', 'subtype': 'input_end', 'property': 'temp_range'}
|
318
|
+
}
|
319
|
+
|
320
|
+
Note:
|
321
|
+
This property is particularly useful for setting up Dash callbacks
|
322
|
+
that need to reference the specific component IDs.
|
323
|
+
"""
|
324
|
+
return {
|
325
|
+
'rangeslider': self.slider_id,
|
326
|
+
'input_start': self.input_start_id,
|
327
|
+
'input_end': self.input_end_id
|
328
|
+
}
|
329
|
+
|
330
|
+
def get_value_inputs(self):
|
331
|
+
"""
|
332
|
+
Get Input objects for listening to component value changes.
|
333
|
+
|
334
|
+
Returns a list of Dash Input objects that can be used in callbacks to
|
335
|
+
listen for value changes from the range slider or input components.
|
336
|
+
|
337
|
+
Returns:
|
338
|
+
list: List containing [Input(slider_id, 'value'), Input(input_start_id, 'value'), Input(input_end_id, 'value')]
|
339
|
+
|
340
|
+
Example:
|
341
|
+
>>> @app.callback(
|
342
|
+
... Output('some-output', 'children'),
|
343
|
+
... component.get_value_inputs()
|
344
|
+
... )
|
345
|
+
... def update_display(slider_range, start_val, end_val):
|
346
|
+
... return f"Range: {slider_range or [start_val, end_val]}"
|
347
|
+
"""
|
348
|
+
return [
|
349
|
+
Input(self.slider_id, 'value'),
|
350
|
+
Input(self.input_start_id, 'value'),
|
351
|
+
Input(self.input_end_id, 'value')
|
352
|
+
]
|
353
|
+
|
354
|
+
def get_value_outputs(self):
|
355
|
+
"""
|
356
|
+
Get Output objects for updating component values.
|
357
|
+
|
358
|
+
Returns a list of Dash Output objects that can be used in callbacks to
|
359
|
+
update the values of the range slider and input components.
|
360
|
+
|
361
|
+
Returns:
|
362
|
+
list: List containing [Output(slider_id, 'value'), Output(input_start_id, 'value'), Output(input_end_id, 'value')]
|
363
|
+
|
364
|
+
Example:
|
365
|
+
>>> @app.callback(
|
366
|
+
... component.get_value_outputs(),
|
367
|
+
... [Input('some-input', 'value')]
|
368
|
+
... )
|
369
|
+
... def update_components(new_range):
|
370
|
+
... return [new_range, new_range[0], new_range[1]]
|
371
|
+
"""
|
372
|
+
return [
|
373
|
+
Output(self.slider_id, 'value'),
|
374
|
+
Output(self.input_start_id, 'value'),
|
375
|
+
Output(self.input_end_id, 'value')
|
376
|
+
]
|
377
|
+
|
378
|
+
def get_pattern_matching_value_inputs(self, property_name='ALL'):
|
379
|
+
"""
|
380
|
+
Get pattern-matching Input objects for listening to multiple component instances.
|
381
|
+
|
382
|
+
Returns Input objects using pattern-matching to listen to all components
|
383
|
+
with the specified property name, useful for multi-component callbacks.
|
384
|
+
|
385
|
+
Args:
|
386
|
+
property_name: The property to match. Use 'ALL' for all properties,
|
387
|
+
or specify a specific property name.
|
388
|
+
|
389
|
+
Returns:
|
390
|
+
list: List containing pattern-matching Input objects
|
391
|
+
|
392
|
+
Example:
|
393
|
+
>>> @app.callback(
|
394
|
+
... Output('summary', 'children'),
|
395
|
+
... component.get_pattern_matching_value_inputs('ALL')
|
396
|
+
... )
|
397
|
+
... def update_summary(slider_ranges, start_values, end_values):
|
398
|
+
... return f"Total range sliders: {len(slider_ranges)}"
|
399
|
+
"""
|
400
|
+
slider_pattern = {'type': 'parameter', 'subtype': 'rangeslider', 'property': property_name}
|
401
|
+
start_pattern = {'type': 'parameter', 'subtype': 'input_start', 'property': property_name}
|
402
|
+
end_pattern = {'type': 'parameter', 'subtype': 'input_end', 'property': property_name}
|
403
|
+
|
404
|
+
return [
|
405
|
+
Input(slider_pattern, 'value'),
|
406
|
+
Input(start_pattern, 'value'),
|
407
|
+
Input(end_pattern, 'value')
|
408
|
+
]
|
409
|
+
|
410
|
+
def get_pattern_matching_value_outputs(self, property_name='ALL'):
|
411
|
+
"""
|
412
|
+
Get pattern-matching Output objects for updating multiple component instances.
|
413
|
+
|
414
|
+
Returns Output objects using pattern-matching to update all components
|
415
|
+
with the specified property name.
|
416
|
+
|
417
|
+
Args:
|
418
|
+
property_name: The property to match. Use 'ALL' for all properties,
|
419
|
+
or specify a specific property name.
|
420
|
+
|
421
|
+
Returns:
|
422
|
+
list: List containing pattern-matching Output objects
|
423
|
+
|
424
|
+
Example:
|
425
|
+
>>> @app.callback(
|
426
|
+
... component.get_pattern_matching_value_outputs('ALL'),
|
427
|
+
... [Input('reset-button', 'n_clicks')]
|
428
|
+
... )
|
429
|
+
... def reset_all_components(n_clicks):
|
430
|
+
... if n_clicks:
|
431
|
+
... return [[[0, 100]] * len(ranges), [0] * len(starts), [100] * len(ends)]
|
432
|
+
... return [dash.no_update] * 3
|
433
|
+
"""
|
434
|
+
slider_pattern = {'type': 'parameter', 'subtype': 'rangeslider', 'property': property_name}
|
435
|
+
start_pattern = {'type': 'parameter', 'subtype': 'input_start', 'property': property_name}
|
436
|
+
end_pattern = {'type': 'parameter', 'subtype': 'input_end', 'property': property_name}
|
437
|
+
|
438
|
+
return [
|
439
|
+
Output(slider_pattern, 'value'),
|
440
|
+
Output(start_pattern, 'value'),
|
441
|
+
Output(end_pattern, 'value')
|
442
|
+
]
|
443
|
+
|
444
|
+
def _validate_and_clamp_range(self, range_values):
|
445
|
+
"""
|
446
|
+
Validate and clamp range values to the component's valid domain.
|
447
|
+
|
448
|
+
Ensures that range values are within the min_val to max_val domain,
|
449
|
+
maintains proper ordering (start <= end), and handles invalid values.
|
450
|
+
|
451
|
+
Args:
|
452
|
+
range_values (list): The range values to validate and clamp [start, end].
|
453
|
+
|
454
|
+
Returns:
|
455
|
+
list[float]: The clamped range values within the valid domain,
|
456
|
+
with proper ordering maintained.
|
457
|
+
"""
|
458
|
+
if not isinstance(range_values, list) or len(range_values) != 2:
|
459
|
+
return [self.min_val, self.max_val]
|
460
|
+
|
461
|
+
try:
|
462
|
+
start, end = float(range_values[0]), float(range_values[1])
|
463
|
+
|
464
|
+
# Clamp to valid domain
|
465
|
+
start = max(self.min_val, min(self.max_val, start))
|
466
|
+
end = max(self.min_val, min(self.max_val, end))
|
467
|
+
|
468
|
+
# Ensure proper ordering
|
469
|
+
if start > end:
|
470
|
+
start, end = end, start
|
471
|
+
|
472
|
+
return [start, end]
|
473
|
+
|
474
|
+
except (ValueError, TypeError):
|
475
|
+
return [self.min_val, self.max_val]
|
476
|
+
|
477
|
+
def _validate_and_clamp_value(self, value):
|
478
|
+
"""
|
479
|
+
Validate and clamp a single value to the component's valid range.
|
480
|
+
|
481
|
+
Ensures that any input value is within the min_val to max_val range
|
482
|
+
and handles None values appropriately.
|
483
|
+
|
484
|
+
Args:
|
485
|
+
value: The value to validate and clamp.
|
486
|
+
|
487
|
+
Returns:
|
488
|
+
float: The clamped value within the valid range, or min_val
|
489
|
+
if the input value is invalid.
|
490
|
+
"""
|
491
|
+
if value is None:
|
492
|
+
return self.min_val
|
493
|
+
|
494
|
+
try:
|
495
|
+
numeric_value = float(value)
|
496
|
+
return max(self.min_val, min(self.max_val, numeric_value))
|
497
|
+
except (ValueError, TypeError):
|
498
|
+
return self.min_val
|
499
|
+
|
500
|
+
|