steer-core 0.1.7__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.
@@ -0,0 +1,657 @@
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 SliderWithTextInput:
8
+ """
9
+ A custom Dash component that combines a slider and text input for synchronized value control.
10
+
11
+ This component creates a user interface element consisting of a slider and a numeric input field
12
+ that can be used together to set numeric values. The slider provides visual feedback and easy
13
+ adjustment, while the text input allows for precise value entry. Both components are synchronized
14
+ and share the same value constraints.
15
+
16
+ The component is designed for use in Dash applications where users need to input numeric values
17
+ within a specified range, with the flexibility of both visual (slider) and precise (text input)
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 input
23
+ max_val (float): Maximum allowed value for both slider and input
24
+ default_val (Union[float, list[float], None]): Default value to display on initialization
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 slider component
34
+ input_id (dict): Computed ID for the input component
35
+
36
+ Example:
37
+ >>> slider_component = SliderWithTextInput(
38
+ ... id_base={'type': 'parameter', 'index': 0},
39
+ ... min_val=0.0,
40
+ ... max_val=100.0,
41
+ ... step=1.0,
42
+ ... mark_interval=10.0,
43
+ ... property_name='temperature',
44
+ ... title='Temperature (°C)',
45
+ ... default_val=25.0,
46
+ ... message='Optimal range is 20-30°C' # Optional message
47
+ ... )
48
+ >>> layout_element = slider_component() # Returns Dash HTML Div component
49
+ """
50
+
51
+ def __init__(
52
+ self,
53
+ id_base: dict,
54
+ min_val: float,
55
+ max_val: float,
56
+ step: float,
57
+ mark_interval: float,
58
+ property_name: str,
59
+ title: str,
60
+ default_val: Union[float, list[float]] = None,
61
+ with_slider_titles: bool = True,
62
+ slider_disable: bool = False,
63
+ div_width: str = 'calc(90%)',
64
+ message: str = None,
65
+ ):
66
+ """
67
+ Initialize the SliderWithTextInput component.
68
+
69
+ Args:
70
+ id_base (dict): Base dictionary for generating component IDs. Should contain
71
+ identifying information that will be extended with component-specific
72
+ subtypes and properties.
73
+ min_val (float): Minimum value that can be selected on the slider or entered
74
+ in the text input.
75
+ max_val (float): Maximum value that can be selected on the slider or entered
76
+ in the text input.
77
+ step (float): The granularity of value changes. Determines the smallest
78
+ increment/decrement possible.
79
+ mark_interval (float): The spacing between tick marks displayed on the slider.
80
+ Should be a multiple of step for best visual alignment.
81
+ property_name (str): A string identifier for this specific property, used
82
+ in ID generation and callbacks.
83
+ title (str): Human-readable title displayed above the component.
84
+ default_val (Union[float, list[float]], optional): Initial value to display.
85
+ If None, defaults to min_val. Can be a single float or list
86
+ for compatibility with range sliders.
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 both 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
+ TypeError: If default_val is provided but not numeric.
101
+ """
102
+
103
+ self.id_base = id_base
104
+ self.min_val = min_val
105
+ self.max_val = max_val
106
+ self.default_val = default_val
107
+ self.step = step
108
+ self.mark_interval = mark_interval
109
+ self.property_name = property_name
110
+ self.title = title
111
+ self.with_slider_titles = with_slider_titles
112
+ self.div_width = div_width
113
+ self.slider_disable = slider_disable
114
+ self.message = message
115
+
116
+ self.slider_id = self._make_id('slider')
117
+ self.input_id = self._make_id('input')
118
+
119
+ def _make_id(self, subtype: str):
120
+ """
121
+ Generate a unique ID dictionary for component sub-elements.
122
+
123
+ Combines the base ID with component-specific subtype and property information
124
+ to create unique identifiers for Dash callbacks and component references.
125
+
126
+ Args:
127
+ subtype (str): The specific component subtype (e.g., 'slider', 'input').
128
+
129
+ Returns:
130
+ dict: Complete ID dictionary containing base ID information plus subtype
131
+ and property specifications.
132
+
133
+ Example:
134
+ >>> component._make_id('slider')
135
+ {'type': 'parameter', 'index': 0, 'subtype': 'slider', 'property': 'temperature'}
136
+ """
137
+ return {**self.id_base, 'subtype': subtype, 'property': self.property_name}
138
+
139
+ def _make_slider(self):
140
+ """
141
+ Create and configure the Dash slider component.
142
+
143
+ Generates a dcc.Slider with the specified range, step size, default value,
144
+ and tick marks. The slider provides visual feedback for value selection
145
+ and is synchronized with the text input component.
146
+
147
+ Returns:
148
+ dash.dcc.Slider: Configured slider component with ID, value constraints,
149
+ tick marks, and styling options.
150
+
151
+ Note:
152
+ - Tick marks are generated at intervals specified by mark_interval
153
+ - updatemode is set to 'mouseup' to reduce callback frequency
154
+ - The slider can be disabled via the slider_disable attribute
155
+ """
156
+ return ds.dcc.Slider(
157
+ id=self.slider_id,
158
+ min=self.min_val,
159
+ max=self.max_val,
160
+ value=self.default_val,
161
+ step=self.step,
162
+ disabled=self.slider_disable,
163
+ marks={int(i): "" for i in np.arange(self.min_val, self.max_val + self.mark_interval, self.mark_interval)},
164
+ updatemode='mouseup'
165
+ )
166
+
167
+ def _make_input(self):
168
+ """
169
+ Create and configure the Dash numeric input component.
170
+
171
+ Generates a dcc.Input with number type for precise value entry.
172
+ The input is synchronized with the slider and provides an alternative
173
+ method for users to specify exact values.
174
+
175
+ Returns:
176
+ dash.dcc.Input: Configured numeric input component with ID, type,
177
+ value constraints, styling, and step specification.
178
+
179
+ Note:
180
+ - Input type is set to 'number' for numeric validation
181
+ - Left margin styling provides visual alignment with slider
182
+ - Step size matches the slider for consistent granularity
183
+ """
184
+ return ds.dcc.Input(
185
+ id=self.input_id,
186
+ type='number',
187
+ value=self.default_val,
188
+ style={'margin-left': '20px'},
189
+ step=self.step,
190
+ disabled=self.slider_disable
191
+ )
192
+
193
+ def __call__(self):
194
+ """
195
+ Generate the complete component layout as a callable object.
196
+
197
+ Creates and returns a Dash HTML Div containing the title, optional message,
198
+ slider, and input components arranged in a cohesive layout. This method allows
199
+ the class instance to be used as a callable that returns the complete
200
+ component structure.
201
+
202
+ Returns:
203
+ dash.html.Div: Complete component layout containing:
204
+ - Title paragraph (conditional based on with_slider_titles)
205
+ - Optional message paragraph (if message is provided)
206
+ - Slider component in a styled container
207
+ - Numeric input component
208
+ - Spacing elements (line breaks)
209
+
210
+ Note:
211
+ - Title display is controlled by with_slider_titles attribute
212
+ - When title is hidden, a non-breaking space maintains layout
213
+ - Message is displayed only if provided during initialization
214
+ - Negative bottom margin on slider container reduces spacing
215
+ - Container width is controlled by div_width attribute
216
+ """
217
+ slider_title = self.title if self.with_slider_titles else "\u00A0"
218
+
219
+ # Build the component list
220
+ components = [
221
+ ds.html.P(slider_title, style={'margin-left': '20px', 'margin-bottom': '0px'})
222
+ ]
223
+
224
+ # Add optional message if provided
225
+ if self.message:
226
+ components.append(
227
+ ds.html.P(self.message, style={
228
+ 'margin-left': '20px',
229
+ 'margin-bottom': '5px',
230
+ 'margin-top': '2px',
231
+ 'font-size': '0.9em',
232
+ 'color': '#666666',
233
+ 'font-style': 'italic'
234
+ })
235
+ )
236
+
237
+ # Add slider, input, and breaks
238
+ components.extend([
239
+ ds.html.Div([self._make_slider()], style={'margin-bottom': '-18px'}),
240
+ self._make_input(),
241
+ ds.html.Br(), ds.html.Br()
242
+ ])
243
+
244
+ return ds.html.Div(components, style={'width': self.div_width, 'margin-left': '-20px'})
245
+
246
+ @property
247
+ def components(self):
248
+ """
249
+ Get a dictionary mapping component types to their IDs.
250
+
251
+ Provides easy access to the IDs of the slider and input components
252
+ for use in Dash callbacks and component interactions.
253
+
254
+ Returns:
255
+ dict: Dictionary with component type keys mapping to their ID dictionaries.
256
+
257
+ Example:
258
+ >>> component.components
259
+ {
260
+ 'slider': {'type': 'parameter', 'subtype': 'slider', 'property': 'temperature'},
261
+ 'input': {'type': 'parameter', 'subtype': 'input', 'property': 'temperature'}
262
+ }
263
+
264
+ Note:
265
+ This property is particularly useful for setting up Dash callbacks
266
+ that need to reference the specific component IDs.
267
+ """
268
+ return {
269
+ 'slider': self.slider_id,
270
+ 'input': self.input_id
271
+ }
272
+
273
+ def get_value_inputs(self):
274
+ """
275
+ Get Input objects for listening to component value changes.
276
+
277
+ Returns a list of Dash Input objects that can be used in callbacks to
278
+ listen for value changes from either the slider or input components.
279
+
280
+ Returns:
281
+ list: List containing [Input(slider_id, 'value'), Input(input_id, 'value')]
282
+
283
+ Example:
284
+ >>> @app.callback(
285
+ ... Output('some-output', 'children'),
286
+ ... component.get_value_inputs()
287
+ ... )
288
+ ... def update_display(slider_val, input_val):
289
+ ... return f"Value: {slider_val or input_val}"
290
+ """
291
+ return [
292
+ Input(self.slider_id, 'value'),
293
+ Input(self.input_id, 'value')
294
+ ]
295
+
296
+ def get_value_outputs(self):
297
+ """
298
+ Get Output objects for updating component values.
299
+
300
+ Returns a list of Dash Output objects that can be used in callbacks to
301
+ update the values of both the slider and input components.
302
+
303
+ Returns:
304
+ list: List containing [Output(slider_id, 'value'), Output(input_id, 'value')]
305
+
306
+ Example:
307
+ >>> @app.callback(
308
+ ... component.get_value_outputs(),
309
+ ... [Input('some-input', 'value')]
310
+ ... )
311
+ ... def update_components(new_value):
312
+ ... return [new_value, new_value]
313
+ """
314
+ return [
315
+ Output(self.slider_id, 'value'),
316
+ Output(self.input_id, 'value')
317
+ ]
318
+
319
+ def get_pattern_matching_value_inputs(self, property_name='ALL'):
320
+ """
321
+ Get pattern-matching Input objects for listening to multiple component instances.
322
+
323
+ Returns Input objects using pattern-matching to listen to all components
324
+ with the specified property name, useful for multi-component callbacks.
325
+
326
+ Args:
327
+ property_name: The property to match. Use 'ALL' for all properties,
328
+ or specify a specific property name.
329
+
330
+ Returns:
331
+ list: List containing pattern-matching Input objects
332
+
333
+ Example:
334
+ >>> @app.callback(
335
+ ... Output('summary', 'children'),
336
+ ... component.get_pattern_matching_value_inputs('ALL')
337
+ ... )
338
+ ... def update_summary(slider_values, input_values):
339
+ ... return f"Total sliders: {len(slider_values)}"
340
+ """
341
+ pattern = {'type': 'parameter', 'subtype': 'slider', 'property': property_name}
342
+ input_pattern = {'type': 'parameter', 'subtype': 'input', 'property': property_name}
343
+
344
+ return [
345
+ Input(pattern, 'value'),
346
+ Input(input_pattern, 'value')
347
+ ]
348
+
349
+ def get_pattern_matching_value_outputs(self, property_name='ALL'):
350
+ """
351
+ Get pattern-matching Output objects for updating multiple component instances.
352
+
353
+ Returns Output objects using pattern-matching to update all components
354
+ with the specified property name.
355
+
356
+ Args:
357
+ property_name: The property to match. Use 'ALL' for all properties,
358
+ or specify a specific property name.
359
+
360
+ Returns:
361
+ list: List containing pattern-matching Output objects
362
+
363
+ Example:
364
+ >>> @app.callback(
365
+ ... component.get_pattern_matching_value_outputs('ALL'),
366
+ ... [Input('reset-button', 'n_clicks')]
367
+ ... )
368
+ ... def reset_all_components(n_clicks):
369
+ ... if n_clicks:
370
+ ... return [[0] * len(slider_values), [0] * len(input_values)]
371
+ ... return [dash.no_update, dash.no_update]
372
+ """
373
+ pattern = {'type': 'parameter', 'subtype': 'slider', 'property': property_name}
374
+ input_pattern = {'type': 'parameter', 'subtype': 'input', 'property': property_name}
375
+
376
+ return [
377
+ Output(pattern, 'value'),
378
+ Output(input_pattern, 'value')
379
+ ]
380
+
381
+ def _validate_and_clamp_value(self, value):
382
+ """
383
+ Validate and clamp a value to the component's valid range.
384
+
385
+ Ensures that any input value is within the min_val to max_val range
386
+ and handles None values appropriately.
387
+
388
+ Args:
389
+ value: The value to validate and clamp.
390
+
391
+ Returns:
392
+ float: The clamped value within the valid range, or the current
393
+ default_val if the input value is invalid.
394
+ """
395
+ if value is None:
396
+ return self.default_val if self.default_val is not None else self.min_val
397
+
398
+ try:
399
+ numeric_value = float(value)
400
+ return max(self.min_val, min(self.max_val, numeric_value))
401
+ except (ValueError, TypeError):
402
+ return self.default_val if self.default_val is not None else self.min_val
403
+
404
+
405
+ class SliderWithTextInputAndCheckbox(SliderWithTextInput):
406
+ """
407
+ A custom Dash component that extends SliderWithTextInput by adding a checkbox with a message.
408
+
409
+ This component inherits all functionality from SliderWithTextInput and adds a checkbox
410
+ positioned beneath the input field. The checkbox can be used to enable/disable features,
411
+ toggle options, or provide additional control over the parameter being adjusted.
412
+
413
+ The checkbox includes an associated message that describes its purpose or provides
414
+ additional context for the user. The checkbox is larger than standard size and has
415
+ proper spacing between the checkbox and its label text.
416
+
417
+ Attributes:
418
+ checkbox_message (str): The message displayed next to the checkbox
419
+ checkbox_default (bool): Default checked state of the checkbox
420
+ checkbox_id (dict): Computed ID for the checkbox component
421
+
422
+ Note:
423
+ The checkbox value is [True] when checked, [] when unchecked.
424
+
425
+ Example:
426
+ >>> slider_component = SliderWithTextInputAndCheckbox(
427
+ ... id_base={'type': 'parameter', 'index': 0},
428
+ ... min_val=0.0,
429
+ ... max_val=100.0,
430
+ ... step=1.0,
431
+ ... mark_interval=10.0,
432
+ ... property_name='temperature',
433
+ ... title='Temperature (°C)',
434
+ ... default_val=25.0,
435
+ ... checkbox_message='Use automatic temperature control',
436
+ ... checkbox_default=True
437
+ ... )
438
+ >>> layout_element = slider_component() # Returns Dash HTML Div component
439
+ """
440
+
441
+ def __init__(
442
+ self,
443
+ id_base: dict,
444
+ min_val: float,
445
+ max_val: float,
446
+ step: float,
447
+ mark_interval: float,
448
+ property_name: str,
449
+ title: str,
450
+ checkbox_message: str,
451
+ default_val: Union[float, list[float]] = None,
452
+ checkbox_default: bool = False,
453
+ with_slider_titles: bool = True,
454
+ slider_disable: bool = False,
455
+ div_width: str = 'calc(90%)',
456
+ message: str = None,
457
+ ):
458
+ """
459
+ Initialize the SliderWithTextInputAndCheckbox component.
460
+
461
+ Args:
462
+ id_base (dict): Base dictionary for generating component IDs.
463
+ min_val (float): Minimum value for the slider and input.
464
+ max_val (float): Maximum value for the slider and input.
465
+ step (float): Step size for value increments/decrements.
466
+ mark_interval (float): Interval between tick marks on the slider.
467
+ property_name (str): String identifier for this property.
468
+ title (str): Title displayed above the component.
469
+ checkbox_message (str): Message displayed next to the checkbox.
470
+ default_val (Union[float, list[float]], optional): Initial value for slider/input.
471
+ checkbox_default (bool, optional): Initial checked state of checkbox. Defaults to False.
472
+ with_slider_titles (bool, optional): Whether to show title. Defaults to True.
473
+ slider_disable (bool, optional): Whether to disable interactions. Defaults to False.
474
+ div_width (str, optional): CSS width for container. Defaults to 'calc(90%)'.
475
+ message (str, optional): Optional message between title and slider.
476
+ """
477
+ # Initialize parent class
478
+ super().__init__(
479
+ id_base=id_base,
480
+ min_val=min_val,
481
+ max_val=max_val,
482
+ step=step,
483
+ mark_interval=mark_interval,
484
+ property_name=property_name,
485
+ title=title,
486
+ default_val=default_val,
487
+ with_slider_titles=with_slider_titles,
488
+ slider_disable=slider_disable,
489
+ div_width=div_width,
490
+ message=message,
491
+ )
492
+
493
+ # Add checkbox-specific attributes
494
+ self.checkbox_message = checkbox_message
495
+ self.checkbox_default = checkbox_default
496
+ self.checkbox_id = self._make_id('checkbox')
497
+
498
+ def _make_checkbox(self):
499
+ """
500
+ Create and configure the Dash checkbox component.
501
+
502
+ Returns:
503
+ dash.html.Div: Container with a larger checkbox and separated label text.
504
+ """
505
+ return ds.html.Div([
506
+ ds.dcc.Checklist(
507
+ id=self.checkbox_id,
508
+ options=[{'label': '', 'value': True}],
509
+ value=[True] if self.checkbox_default else [],
510
+ style={
511
+ 'display': 'inline-block',
512
+ 'margin-right': '10px',
513
+ 'transform': 'scale(1.3)', # Make checkbox larger
514
+ 'transform-origin': 'left top', # Changed to 'top' for better alignment
515
+ 'vertical-align': 'baseline' # Use baseline alignment
516
+ },
517
+ labelStyle={'margin': '0px'}
518
+ ),
519
+ ds.html.Span(
520
+ self.checkbox_message,
521
+ style={
522
+ 'display': 'inline-block',
523
+ 'vertical-align': 'baseline', # Match checkbox baseline
524
+ 'margin-left': '5px',
525
+ 'line-height': '1.3', # Slightly higher line height
526
+ 'margin-top': '-4px' # Pull text up more aggressively
527
+ }
528
+ )
529
+ ], style={'margin-left': '20px', 'margin-top': '10px', 'line-height': '1'})
530
+
531
+ def __call__(self):
532
+ """
533
+ Generate the complete component layout including the checkbox.
534
+
535
+ Creates and returns a Dash HTML Div containing the title, optional message,
536
+ slider, input, and checkbox components arranged in a cohesive layout.
537
+
538
+ Returns:
539
+ dash.html.Div: Complete component layout containing all elements plus checkbox.
540
+ """
541
+ slider_title = self.title if self.with_slider_titles else "\u00A0"
542
+
543
+ # Build the component list
544
+ components = [
545
+ ds.html.P(slider_title, style={'margin-left': '20px', 'margin-bottom': '0px'})
546
+ ]
547
+
548
+ # Add optional message if provided
549
+ if self.message:
550
+ components.append(
551
+ ds.html.P(self.message, style={
552
+ 'margin-left': '20px',
553
+ 'margin-bottom': '5px',
554
+ 'margin-top': '2px',
555
+ 'font-size': '0.9em',
556
+ 'color': '#666666',
557
+ 'font-style': 'italic'
558
+ })
559
+ )
560
+
561
+ # Add slider and input
562
+ components.extend([
563
+ ds.html.Div([self._make_slider()], style={'margin-bottom': '-18px'}),
564
+ self._make_input(),
565
+ ])
566
+
567
+ # Add checkbox beneath the input
568
+ components.extend([
569
+ self._make_checkbox(),
570
+ ds.html.Br(), ds.html.Br()
571
+ ])
572
+
573
+ return ds.html.Div(components, style={'width': self.div_width, 'margin-left': '-20px'})
574
+
575
+ @property
576
+ def components(self):
577
+ """
578
+ Get a dictionary mapping component types to their IDs, including the checkbox.
579
+
580
+ Returns:
581
+ dict: Dictionary with component type keys mapping to their ID dictionaries.
582
+ """
583
+ return {
584
+ 'slider': self.slider_id,
585
+ 'input': self.input_id,
586
+ 'checkbox': self.checkbox_id
587
+ }
588
+
589
+ def get_checkbox_input(self):
590
+ """
591
+ Get Input object for listening to checkbox value changes.
592
+
593
+ Returns:
594
+ Input: Dash Input object for the checkbox component.
595
+
596
+ Note:
597
+ The checkbox value will be [True] when checked, [] when unchecked.
598
+
599
+ Example:
600
+ >>> @app.callback(
601
+ ... Output('some-output', 'children'),
602
+ ... component.get_checkbox_input()
603
+ ... )
604
+ ... def update_display(checkbox_value):
605
+ ... is_checked = True in (checkbox_value or [])
606
+ ... return f"Checkbox is {'checked' if is_checked else 'unchecked'}"
607
+ """
608
+ return Input(self.checkbox_id, 'value')
609
+
610
+ def get_checkbox_output(self):
611
+ """
612
+ Get Output object for updating checkbox value.
613
+
614
+ Returns:
615
+ Output: Dash Output object for the checkbox component.
616
+
617
+ Note:
618
+ To check the checkbox, return [True]. To uncheck, return [].
619
+
620
+ Example:
621
+ >>> @app.callback(
622
+ ... component.get_checkbox_output(),
623
+ ... [Input('some-button', 'n_clicks')]
624
+ ... )
625
+ ... def toggle_checkbox(n_clicks):
626
+ ... if n_clicks and n_clicks % 2 == 1:
627
+ ... return [True]
628
+ ... return []
629
+ """
630
+ return Output(self.checkbox_id, 'value')
631
+
632
+ def get_all_inputs(self):
633
+ """
634
+ Get Input objects for all component values (slider, input, and checkbox).
635
+
636
+ Returns:
637
+ list: List containing Input objects for slider, input, and checkbox.
638
+
639
+ Note:
640
+ The checkbox value will be [True] when checked, [] when unchecked.
641
+
642
+ Example:
643
+ >>> @app.callback(
644
+ ... Output('summary', 'children'),
645
+ ... component.get_all_inputs()
646
+ ... )
647
+ ... def update_summary(slider_val, input_val, checkbox_val):
648
+ ... is_checked = True in (checkbox_val or [])
649
+ ... value = slider_val or input_val
650
+ ... return f"Value: {value}, Option enabled: {is_checked}"
651
+ """
652
+ return [
653
+ Input(self.slider_id, 'value'),
654
+ Input(self.input_id, 'value'),
655
+ Input(self.checkbox_id, 'value')
656
+ ]
657
+
File without changes