steer-core 0.1.16__py3-none-any.whl → 0.1.18__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.
@@ -7,16 +7,16 @@ from dash import Input, Output
7
7
  class RangeSliderWithTextInput:
8
8
  """
9
9
  A custom Dash component that combines a range slider with two text inputs for synchronized range control.
10
-
10
+
11
11
  This component creates a user interface element consisting of a range slider and two numeric input fields
12
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
13
+ adjustment of both start and end values, while the text inputs allow for precise value entry. All
14
14
  components are synchronized and share the same value constraints.
15
-
15
+
16
16
  The component is designed for use in Dash applications where users need to input numeric ranges
17
17
  within a specified domain, with the flexibility of both visual (range slider) and precise (text inputs)
18
18
  control methods.
19
-
19
+
20
20
  Attributes:
21
21
  id_base (dict): Base identifier dictionary used to construct unique IDs for child components
22
22
  min_val (float): Minimum allowed value for both slider and inputs
@@ -33,7 +33,7 @@ class RangeSliderWithTextInput:
33
33
  slider_id (dict): Computed ID for the range slider component
34
34
  input_start_id (dict): Computed ID for the start value input component
35
35
  input_end_id (dict): Computed ID for the end value input component
36
-
36
+
37
37
  Example:
38
38
  >>> range_component = RangeSliderWithTextInput(
39
39
  ... id_base={'type': 'parameter', 'index': 0},
@@ -61,12 +61,12 @@ class RangeSliderWithTextInput:
61
61
  default_val: Union[list[float], None] = None,
62
62
  with_slider_titles: bool = True,
63
63
  slider_disable: bool = False,
64
- div_width: str = 'calc(90%)',
64
+ div_width: str = "calc(90%)",
65
65
  message: str = None,
66
66
  ):
67
67
  """
68
68
  Initialize the RangeSliderWithTextInput component.
69
-
69
+
70
70
  Args:
71
71
  id_base (dict): Base dictionary for generating component IDs. Should contain
72
72
  identifying information that will be extended with component-specific
@@ -94,27 +94,31 @@ class RangeSliderWithTextInput:
94
94
  message (str, optional): Optional message to display between the title
95
95
  and slider. If None, no message is displayed.
96
96
  Defaults to None.
97
-
97
+
98
98
  Raises:
99
99
  ValueError: If min_val >= max_val, or if step <= 0, or if mark_interval <= 0,
100
100
  or if default_val contains invalid range values.
101
101
  TypeError: If default_val is provided but not a list of numeric values.
102
102
  """
103
-
103
+
104
104
  # Validate inputs
105
105
  if min_val >= max_val:
106
- raise ValueError(f"min_val ({min_val}) must be less than max_val ({max_val})")
106
+ raise ValueError(
107
+ f"min_val ({min_val}) must be less than max_val ({max_val})"
108
+ )
107
109
  if step <= 0:
108
110
  raise ValueError(f"step ({step}) must be positive")
109
111
  if mark_interval <= 0:
110
112
  raise ValueError(f"mark_interval ({mark_interval}) must be positive")
111
-
113
+
112
114
  # Validate and set default values
113
115
  if default_val is None:
114
116
  default_val = [min_val, max_val]
115
117
  else:
116
118
  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]")
119
+ raise TypeError(
120
+ "default_val must be a list of two numeric values [start, end]"
121
+ )
118
122
  try:
119
123
  default_val = [float(default_val[0]), float(default_val[1])]
120
124
  except (ValueError, TypeError):
@@ -135,42 +139,42 @@ class RangeSliderWithTextInput:
135
139
  self.slider_disable = slider_disable
136
140
  self.message = message
137
141
 
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')
142
+ self.slider_id = self._make_id("rangeslider")
143
+ self.input_start_id = self._make_id("input_start")
144
+ self.input_end_id = self._make_id("input_end")
141
145
 
142
146
  def _make_id(self, subtype: str):
143
147
  """
144
148
  Generate a unique ID dictionary for component sub-elements.
145
-
149
+
146
150
  Combines the base ID with component-specific subtype and property information
147
151
  to create unique identifiers for Dash callbacks and component references.
148
-
152
+
149
153
  Args:
150
154
  subtype (str): The specific component subtype (e.g., 'rangeslider', 'input_start', 'input_end').
151
-
155
+
152
156
  Returns:
153
157
  dict: Complete ID dictionary containing base ID information plus subtype
154
158
  and property specifications.
155
-
159
+
156
160
  Example:
157
161
  >>> component._make_id('rangeslider')
158
162
  {'type': 'parameter', 'index': 0, 'subtype': 'rangeslider', 'property': 'temperature_range'}
159
163
  """
160
- return {**self.id_base, 'subtype': subtype, 'property': self.property_name}
164
+ return {**self.id_base, "subtype": subtype, "property": self.property_name}
161
165
 
162
166
  def _make_range_slider(self):
163
167
  """
164
168
  Create and configure the Dash range slider component.
165
-
169
+
166
170
  Generates a dcc.RangeSlider with the specified range, step size, default values,
167
171
  and tick marks. The range slider provides visual feedback for range selection
168
172
  and is synchronized with the text input components.
169
-
173
+
170
174
  Returns:
171
175
  dash.dcc.RangeSlider: Configured range slider component with ID, value constraints,
172
176
  tick marks, and styling options.
173
-
177
+
174
178
  Note:
175
179
  - Tick marks are generated at intervals specified by mark_interval
176
180
  - updatemode is set to 'mouseup' to reduce callback frequency
@@ -183,22 +187,28 @@ class RangeSliderWithTextInput:
183
187
  value=self.default_val,
184
188
  step=self.step,
185
189
  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'
190
+ marks={
191
+ int(i): ""
192
+ for i in np.arange(
193
+ self.min_val, self.max_val + self.mark_interval, self.mark_interval
194
+ )
195
+ },
196
+ updatemode="mouseup",
197
+ tooltip={"placement": "right", "always_visible": False}
188
198
  )
189
199
 
190
200
  def _make_start_input(self):
191
201
  """
192
202
  Create and configure the Dash numeric input component for the start value.
193
-
203
+
194
204
  Generates a dcc.Input with number type for precise start value entry.
195
205
  The input is synchronized with the range slider and provides an alternative
196
206
  method for users to specify exact start values.
197
-
207
+
198
208
  Returns:
199
209
  dash.dcc.Input: Configured numeric input component with ID, type,
200
210
  value constraints, styling, and step specification.
201
-
211
+
202
212
  Note:
203
213
  - Input type is set to 'number' for numeric validation
204
214
  - Width and margin styling provides visual alignment
@@ -206,25 +216,25 @@ class RangeSliderWithTextInput:
206
216
  """
207
217
  return ds.dcc.Input(
208
218
  id=self.input_start_id,
209
- type='number',
219
+ type="number",
210
220
  value=self.default_val[0],
211
221
  step=self.step,
212
- style={'width': '80px'},
222
+ style={"width": "80px"},
213
223
  disabled=self.slider_disable,
214
224
  )
215
225
 
216
226
  def _make_end_input(self):
217
227
  """
218
228
  Create and configure the Dash numeric input component for the end value.
219
-
229
+
220
230
  Generates a dcc.Input with number type for precise end value entry.
221
231
  The input is synchronized with the range slider and provides an alternative
222
232
  method for users to specify exact end values.
223
-
233
+
224
234
  Returns:
225
235
  dash.dcc.Input: Configured numeric input component with ID, type,
226
236
  value constraints, styling, and step specification.
227
-
237
+
228
238
  Note:
229
239
  - Input type is set to 'number' for numeric validation
230
240
  - Width and margin styling provides visual alignment
@@ -232,22 +242,22 @@ class RangeSliderWithTextInput:
232
242
  """
233
243
  return ds.dcc.Input(
234
244
  id=self.input_end_id,
235
- type='number',
245
+ type="number",
236
246
  value=self.default_val[1],
237
247
  step=self.step,
238
- style={'margin-left': '10px', 'width': '80px'},
248
+ style={"margin-left": "10px", "width": "80px"},
239
249
  disabled=self.slider_disable,
240
250
  )
241
251
 
242
252
  def __call__(self):
243
253
  """
244
254
  Generate the complete component layout as a callable object.
245
-
255
+
246
256
  Creates and returns a Dash HTML Div containing the title, optional message,
247
257
  range slider, and input components arranged in a cohesive layout. This method allows
248
258
  the class instance to be used as a callable that returns the complete
249
259
  component structure.
250
-
260
+
251
261
  Returns:
252
262
  dash.html.Div: Complete component layout containing:
253
263
  - Title paragraph (conditional based on with_slider_titles)
@@ -255,7 +265,7 @@ class RangeSliderWithTextInput:
255
265
  - Range slider component in a styled container
256
266
  - Start and end numeric input components with labels
257
267
  - Spacing elements (line breaks)
258
-
268
+
259
269
  Note:
260
270
  - Title display is controlled by with_slider_titles attribute
261
271
  - When title is hidden, a non-breaking space maintains layout
@@ -265,50 +275,70 @@ class RangeSliderWithTextInput:
265
275
  - Input labels provide clear indication of start/end values
266
276
  """
267
277
  slider_title = self.title if self.with_slider_titles else "\u00A0"
268
-
278
+
269
279
  # Build the component list
270
280
  components = [
271
- ds.html.P(slider_title, style={'margin-left': '20px', 'margin-bottom': '0px'})
281
+ ds.html.P(
282
+ slider_title, style={"margin-left": "20px", "margin-bottom": "0px"}
283
+ )
272
284
  ]
273
-
285
+
274
286
  # Add optional message if provided
275
287
  if self.message:
276
288
  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
- })
289
+ ds.html.P(
290
+ self.message,
291
+ style={
292
+ "margin-left": "20px",
293
+ "margin-bottom": "5px",
294
+ "margin-top": "2px",
295
+ "font-size": "0.9em",
296
+ "color": "#666666",
297
+ "font-style": "italic",
298
+ },
299
+ )
285
300
  )
286
-
301
+
287
302
  # 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'})
303
+ components.extend(
304
+ [
305
+ ds.html.Div(
306
+ [self._make_range_slider()], style={"margin-bottom": "-18px"}
307
+ ),
308
+ ds.html.Div(
309
+ [
310
+ ds.html.Span(
311
+ "Start:",
312
+ style={"margin-left": "20px", "margin-right": "5px"},
313
+ ),
314
+ self._make_start_input(),
315
+ ds.html.Span(
316
+ "End:", style={"margin-left": "15px", "margin-right": "5px"}
317
+ ),
318
+ self._make_end_input(),
319
+ ],
320
+ style={"display": "flex", "align-items": "center"},
321
+ ),
322
+ ds.html.Br(),
323
+ ds.html.Br(),
324
+ ]
325
+ )
326
+
327
+ return ds.html.Div(
328
+ components, style={"width": self.div_width, "margin-left": "-20px"}
329
+ )
300
330
 
301
331
  @property
302
332
  def components(self):
303
333
  """
304
334
  Get a dictionary mapping component types to their IDs.
305
-
335
+
306
336
  Provides easy access to the IDs of the range slider and input components
307
337
  for use in Dash callbacks and component interactions.
308
-
338
+
309
339
  Returns:
310
340
  dict: Dictionary with component type keys mapping to their ID dictionaries.
311
-
341
+
312
342
  Example:
313
343
  >>> component.components
314
344
  {
@@ -316,27 +346,27 @@ class RangeSliderWithTextInput:
316
346
  'input_start': {'type': 'parameter', 'subtype': 'input_start', 'property': 'temp_range'},
317
347
  'input_end': {'type': 'parameter', 'subtype': 'input_end', 'property': 'temp_range'}
318
348
  }
319
-
349
+
320
350
  Note:
321
351
  This property is particularly useful for setting up Dash callbacks
322
352
  that need to reference the specific component IDs.
323
353
  """
324
354
  return {
325
- 'rangeslider': self.slider_id,
326
- 'input_start': self.input_start_id,
327
- 'input_end': self.input_end_id
355
+ "rangeslider": self.slider_id,
356
+ "input_start": self.input_start_id,
357
+ "input_end": self.input_end_id,
328
358
  }
329
-
359
+
330
360
  def get_value_inputs(self):
331
361
  """
332
362
  Get Input objects for listening to component value changes.
333
-
363
+
334
364
  Returns a list of Dash Input objects that can be used in callbacks to
335
365
  listen for value changes from the range slider or input components.
336
-
366
+
337
367
  Returns:
338
368
  list: List containing [Input(slider_id, 'value'), Input(input_start_id, 'value'), Input(input_end_id, 'value')]
339
-
369
+
340
370
  Example:
341
371
  >>> @app.callback(
342
372
  ... Output('some-output', 'children'),
@@ -346,21 +376,21 @@ class RangeSliderWithTextInput:
346
376
  ... return f"Range: {slider_range or [start_val, end_val]}"
347
377
  """
348
378
  return [
349
- Input(self.slider_id, 'value'),
350
- Input(self.input_start_id, 'value'),
351
- Input(self.input_end_id, 'value')
379
+ Input(self.slider_id, "value"),
380
+ Input(self.input_start_id, "value"),
381
+ Input(self.input_end_id, "value"),
352
382
  ]
353
-
383
+
354
384
  def get_value_outputs(self):
355
385
  """
356
386
  Get Output objects for updating component values.
357
-
387
+
358
388
  Returns a list of Dash Output objects that can be used in callbacks to
359
389
  update the values of the range slider and input components.
360
-
390
+
361
391
  Returns:
362
392
  list: List containing [Output(slider_id, 'value'), Output(input_start_id, 'value'), Output(input_end_id, 'value')]
363
-
393
+
364
394
  Example:
365
395
  >>> @app.callback(
366
396
  ... component.get_value_outputs(),
@@ -370,25 +400,25 @@ class RangeSliderWithTextInput:
370
400
  ... return [new_range, new_range[0], new_range[1]]
371
401
  """
372
402
  return [
373
- Output(self.slider_id, 'value'),
374
- Output(self.input_start_id, 'value'),
375
- Output(self.input_end_id, 'value')
403
+ Output(self.slider_id, "value"),
404
+ Output(self.input_start_id, "value"),
405
+ Output(self.input_end_id, "value"),
376
406
  ]
377
-
378
- def get_pattern_matching_value_inputs(self, property_name='ALL'):
407
+
408
+ def get_pattern_matching_value_inputs(self, property_name="ALL"):
379
409
  """
380
410
  Get pattern-matching Input objects for listening to multiple component instances.
381
-
411
+
382
412
  Returns Input objects using pattern-matching to listen to all components
383
413
  with the specified property name, useful for multi-component callbacks.
384
-
414
+
385
415
  Args:
386
416
  property_name: The property to match. Use 'ALL' for all properties,
387
417
  or specify a specific property name.
388
-
418
+
389
419
  Returns:
390
420
  list: List containing pattern-matching Input objects
391
-
421
+
392
422
  Example:
393
423
  >>> @app.callback(
394
424
  ... Output('summary', 'children'),
@@ -397,30 +427,42 @@ class RangeSliderWithTextInput:
397
427
  ... def update_summary(slider_ranges, start_values, end_values):
398
428
  ... return f"Total range sliders: {len(slider_ranges)}"
399
429
  """
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
-
430
+ slider_pattern = {
431
+ "type": "parameter",
432
+ "subtype": "rangeslider",
433
+ "property": property_name,
434
+ }
435
+ start_pattern = {
436
+ "type": "parameter",
437
+ "subtype": "input_start",
438
+ "property": property_name,
439
+ }
440
+ end_pattern = {
441
+ "type": "parameter",
442
+ "subtype": "input_end",
443
+ "property": property_name,
444
+ }
445
+
404
446
  return [
405
- Input(slider_pattern, 'value'),
406
- Input(start_pattern, 'value'),
407
- Input(end_pattern, 'value')
447
+ Input(slider_pattern, "value"),
448
+ Input(start_pattern, "value"),
449
+ Input(end_pattern, "value"),
408
450
  ]
409
-
410
- def get_pattern_matching_value_outputs(self, property_name='ALL'):
451
+
452
+ def get_pattern_matching_value_outputs(self, property_name="ALL"):
411
453
  """
412
454
  Get pattern-matching Output objects for updating multiple component instances.
413
-
455
+
414
456
  Returns Output objects using pattern-matching to update all components
415
457
  with the specified property name.
416
-
458
+
417
459
  Args:
418
460
  property_name: The property to match. Use 'ALL' for all properties,
419
461
  or specify a specific property name.
420
-
462
+
421
463
  Returns:
422
464
  list: List containing pattern-matching Output objects
423
-
465
+
424
466
  Example:
425
467
  >>> @app.callback(
426
468
  ... component.get_pattern_matching_value_outputs('ALL'),
@@ -431,70 +473,80 @@ class RangeSliderWithTextInput:
431
473
  ... return [[[0, 100]] * len(ranges), [0] * len(starts), [100] * len(ends)]
432
474
  ... return [dash.no_update] * 3
433
475
  """
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
-
476
+ slider_pattern = {
477
+ "type": "parameter",
478
+ "subtype": "rangeslider",
479
+ "property": property_name,
480
+ }
481
+ start_pattern = {
482
+ "type": "parameter",
483
+ "subtype": "input_start",
484
+ "property": property_name,
485
+ }
486
+ end_pattern = {
487
+ "type": "parameter",
488
+ "subtype": "input_end",
489
+ "property": property_name,
490
+ }
491
+
438
492
  return [
439
- Output(slider_pattern, 'value'),
440
- Output(start_pattern, 'value'),
441
- Output(end_pattern, 'value')
493
+ Output(slider_pattern, "value"),
494
+ Output(start_pattern, "value"),
495
+ Output(end_pattern, "value"),
442
496
  ]
443
-
497
+
444
498
  def _validate_and_clamp_range(self, range_values):
445
499
  """
446
500
  Validate and clamp range values to the component's valid domain.
447
-
501
+
448
502
  Ensures that range values are within the min_val to max_val domain,
449
503
  maintains proper ordering (start <= end), and handles invalid values.
450
-
504
+
451
505
  Args:
452
506
  range_values (list): The range values to validate and clamp [start, end].
453
-
507
+
454
508
  Returns:
455
509
  list[float]: The clamped range values within the valid domain,
456
510
  with proper ordering maintained.
457
511
  """
458
512
  if not isinstance(range_values, list) or len(range_values) != 2:
459
513
  return [self.min_val, self.max_val]
460
-
514
+
461
515
  try:
462
516
  start, end = float(range_values[0]), float(range_values[1])
463
-
517
+
464
518
  # Clamp to valid domain
465
519
  start = max(self.min_val, min(self.max_val, start))
466
520
  end = max(self.min_val, min(self.max_val, end))
467
-
521
+
468
522
  # Ensure proper ordering
469
523
  if start > end:
470
524
  start, end = end, start
471
-
525
+
472
526
  return [start, end]
473
-
527
+
474
528
  except (ValueError, TypeError):
475
529
  return [self.min_val, self.max_val]
476
-
530
+
477
531
  def _validate_and_clamp_value(self, value):
478
532
  """
479
533
  Validate and clamp a single value to the component's valid range.
480
-
534
+
481
535
  Ensures that any input value is within the min_val to max_val range
482
536
  and handles None values appropriately.
483
-
537
+
484
538
  Args:
485
539
  value: The value to validate and clamp.
486
-
540
+
487
541
  Returns:
488
542
  float: The clamped value within the valid range, or min_val
489
543
  if the input value is invalid.
490
544
  """
491
545
  if value is None:
492
546
  return self.min_val
493
-
547
+
494
548
  try:
495
549
  numeric_value = float(value)
496
550
  return max(self.min_val, min(self.max_val, numeric_value))
497
551
  except (ValueError, TypeError):
498
552
  return self.min_val
499
-
500
-