steer-core 0.1.25__py3-none-any.whl → 0.1.27__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.
@@ -1,728 +0,0 @@
1
- """
2
- Utility functions for slider controls and parameter management.
3
-
4
- This module provides helper functions for automatically determining appropriate
5
- slider configurations based on parameter ranges.
6
- """
7
-
8
- import math
9
- from typing import List
10
-
11
-
12
- def calculate_slider_steps(
13
- min_values: List[float], max_values: List[float]
14
- ) -> List[float]:
15
- """
16
- Calculate reasonable slider steps based on parameter ranges.
17
-
18
- This function analyzes the range of each parameter and suggests an appropriate
19
- step size that provides good granularity. The step sizes are the same as input
20
- steps to ensure consistent behavior between sliders and input fields:
21
- - Ranges < 100: step = 0.01
22
- - Ranges 100-999: step = 0.1
23
- - Ranges 1000+: step = 1.0
24
-
25
- Args:
26
- min_values (List[float]): List of minimum values for each parameter
27
- max_values (List[float]): List of maximum values for each parameter
28
-
29
- Returns:
30
- List[float]: List of recommended step sizes for each parameter
31
-
32
- Raises:
33
- ValueError: If lists have different lengths or if any max < min
34
-
35
- Examples:
36
- >>> min_vals = [0, 0, 0, 0]
37
- >>> max_vals = [10, 100, 1000, 0.1]
38
- >>> steps = calculate_slider_steps(min_vals, max_vals)
39
- >>> steps
40
- [0.01, 0.1, 1.0, 0.01]
41
-
42
- >>> # Temperature range
43
- >>> steps = calculate_slider_steps([20], [100])
44
- >>> steps[0]
45
- 0.01
46
- """
47
- if len(min_values) != len(max_values):
48
- raise ValueError("min_values and max_values must have the same length")
49
-
50
- steps = []
51
-
52
- for min_val, max_val in zip(min_values, max_values):
53
- if max_val < min_val:
54
- raise ValueError(
55
- f"max_value ({max_val}) cannot be less than min_value ({min_val})"
56
- )
57
-
58
- # Calculate the range
59
- range_val = max_val - min_val
60
-
61
- if range_val == 0:
62
- # If no range, use a very small step
63
- steps.append(0.001)
64
- continue
65
-
66
- # Calculate step based on range magnitude
67
- step = _calculate_step_for_range(range_val)
68
- steps.append(step)
69
-
70
- return steps
71
-
72
-
73
- def _calculate_step_for_range(range_val: float) -> float:
74
- """
75
- Calculate an appropriate step size for a given range.
76
-
77
- Uses the same step sizes as input steps to ensure sliders and inputs
78
- have consistent granularity:
79
- - Ranges < 100: step = 0.01
80
- - Ranges 100-999: step = 0.1
81
- - Ranges 1000+: step = 1.0
82
-
83
- Args:
84
- range_val (float): The range (max - min) for the parameter
85
-
86
- Returns:
87
- float: Recommended step size
88
- """
89
- if range_val <= 0:
90
- return 0.01
91
-
92
- # Use the same logic as input steps for consistency
93
- if range_val < 100:
94
- return 0.01
95
- elif range_val < 1000:
96
- return 0.1
97
- else:
98
- return 1.0
99
-
100
-
101
- def _calculate_input_step_for_range(range_val: float) -> float:
102
- """
103
- Calculate an appropriate input step size for a given range.
104
-
105
- Input steps are finer than slider steps to allow more precise control:
106
- - Ranges < 100: input step = 0.01
107
- - Ranges 100-999: input step = 0.1
108
- - Ranges 1000+: input step = 1.0
109
-
110
- Args:
111
- range_val (float): The range (max - min) for the parameter
112
-
113
- Returns:
114
- float: Recommended input step size
115
- """
116
- if range_val < 100:
117
- return 0.01
118
- elif range_val < 1000:
119
- return 0.1
120
- else:
121
- return 1.0
122
-
123
-
124
- def calculate_input_steps(
125
- min_values: List[float], max_values: List[float]
126
- ) -> List[float]:
127
- """
128
- Calculate appropriate input step sizes based on parameter ranges.
129
-
130
- Input steps are typically finer than slider steps to allow precise adjustment.
131
-
132
- Args:
133
- min_values (List[float]): List of minimum values for each parameter
134
- max_values (List[float]): List of maximum values for each parameter
135
-
136
- Returns:
137
- List[float]: List of recommended input step sizes for each parameter
138
- """
139
- if len(min_values) != len(max_values):
140
- raise ValueError("min_values and max_values must have the same length")
141
-
142
- input_steps = []
143
-
144
- for min_val, max_val in zip(min_values, max_values):
145
- if max_val < min_val:
146
- raise ValueError(
147
- f"max_value ({max_val}) cannot be less than min_value ({min_val})"
148
- )
149
-
150
- # Calculate the range
151
- range_val = max_val - min_val
152
-
153
- # Calculate input step based on range magnitude
154
- input_step = _calculate_input_step_for_range(range_val)
155
- input_steps.append(input_step)
156
-
157
- return input_steps
158
-
159
-
160
- def calculate_mark_intervals(
161
- min_values: List[float], max_values: List[float], target_marks: int = 5
162
- ) -> List[float]:
163
- """
164
- Calculate mark intervals for sliders based on range magnitude.
165
-
166
- Creates marks at regular intervals that align with intuitive values while
167
- avoiding overcrowding by ensuring no more than ~5-6 marks per slider:
168
- - Range < 0.5: marks on every 0.1, but max 5 marks
169
- - Range < 1: marks on every 0.2 (interval = 0.2)
170
- - Range < 5: marks on every 1.0 (interval = 1.0)
171
- - Range < 10: marks on every 2.0 (interval = 2.0)
172
- - Range < 50: marks on every 10.0 (interval = 10.0)
173
- - Range < 100: marks on every 20.0 (interval = 20.0)
174
- - Range < 500: marks on every 100.0 (interval = 100.0)
175
- - Range < 1000: marks on every 200.0 (interval = 200.0)
176
- - Range >= 1000: marks on every 500.0 (interval = 500.0)
177
-
178
- Args:
179
- min_values (List[float]): List of minimum values for each parameter
180
- max_values (List[float]): List of maximum values for each parameter
181
- target_marks (int): Target number of marks to display (unused, kept for compatibility)
182
-
183
- Returns:
184
- List[float]: List of recommended mark intervals for each parameter
185
-
186
- Examples:
187
- >>> min_vals = [0, 0, 0, 0]
188
- >>> max_vals = [0.3, 2, 250, 2000]
189
- >>> intervals = calculate_mark_intervals(min_vals, max_vals)
190
- >>> intervals
191
- [0.1, 1.0, 100.0, 500.0]
192
- """
193
- if len(min_values) != len(max_values):
194
- raise ValueError("min_values and max_values must have the same length")
195
-
196
- intervals = []
197
-
198
- for min_val, max_val in zip(min_values, max_values):
199
- range_val = max_val - min_val
200
-
201
- if range_val == 0:
202
- intervals.append(1.0)
203
- continue
204
-
205
- # Determine interval based on range magnitude to avoid overcrowding
206
- if range_val < 0.5:
207
- # Very small ranges: use 0.1 but ensure max 5 marks
208
- interval = 0.1
209
- num_marks = range_val / interval
210
- if num_marks > 5:
211
- interval = range_val / 4 # Limit to ~4 marks
212
- elif range_val < 1:
213
- interval = 0.2 # Every 0.2 for ranges like 0.5-0.9
214
- elif range_val < 5:
215
- interval = 1.0 # Every integer for small ranges
216
- elif range_val < 10:
217
- interval = 2.0 # Every 2 units
218
- elif range_val < 50:
219
- interval = 10.0 # Every multiple of 10
220
- elif range_val < 100:
221
- interval = 20.0 # Every multiple of 20
222
- elif range_val < 500:
223
- interval = 100.0 # Every multiple of 100
224
- elif range_val < 1000:
225
- interval = 200.0 # Every multiple of 200
226
- else:
227
- interval = 500.0 # Every multiple of 500 for very large ranges
228
-
229
- intervals.append(interval)
230
-
231
- return intervals
232
-
233
-
234
- def create_slider_config(
235
- min_values: List[float],
236
- max_values: List[float],
237
- property_values: List[float] = None,
238
- ) -> dict:
239
- """
240
- Create complete slider configurations with automatically calculated steps and marks.
241
-
242
- This is a convenience function that combines step and mark interval calculations
243
- to create ready-to-use slider configurations. If property values are provided,
244
- they will be validated and adjusted to align with the calculated slider grid.
245
-
246
- Step sizes are calculated based on the magnitude of the maximum values (0 to max)
247
- rather than the range (min to max), providing consistent granularity regardless
248
- of the minimum value.
249
-
250
- Args:
251
- min_values (List[float]): Minimum values for each slider
252
- max_values (List[float]): Maximum values for each slider
253
- property_values (List[float], optional): Current property values to validate
254
- against slider grid. If provided, values
255
- will be snapped to nearest valid step.
256
-
257
- Returns:
258
- dict: Configuration dictionary with keys:
259
- - 'min_vals' (List[float]): Minimum values for each slider (snapped to grid, >= original min)
260
- - 'max_vals' (List[float]): Maximum values for each slider (snapped to grid, <= original max)
261
- - 'step_vals' (List[float]): Calculated step values based on max value magnitude
262
- - 'input_step_vals' (List[float]): Step values for inputs (same as slider steps)
263
- - 'mark_vals' (List[dict]): Mark positions for each slider (string position → empty string mapping)
264
- - 'grid_slider_vals' (List[float], optional): Property values snapped to slider grid if provided
265
- - 'grid_input_vals' (List[float], optional): Property values snapped to input grid if provided
266
-
267
- Example:
268
- >>> config = create_slider_config(
269
- ... min_values=[0, 20, 0],
270
- ... max_values=[100, 80, 1000],
271
- ... property_values=[23.7, 45.3, 567.8]
272
- ... )
273
- >>> # Step sizes based on max values: 100->0.1, 80->0.01, 1000->1.0
274
- >>> config['step_vals']
275
- [0.1, 0.01, 1.0]
276
- >>> config['input_step_vals']
277
- [0.1, 0.01, 1.0]
278
- >>> config['grid_vals'][0] # 23.7 snapped to grid
279
- 23.7
280
- """
281
- # Validate inputs
282
- if len(min_values) != len(max_values):
283
- raise ValueError("min_values and max_values must have the same length")
284
-
285
- if property_values is not None and len(property_values) != len(min_values):
286
- raise ValueError(
287
- "property_values must have the same length as min_values and max_values"
288
- )
289
-
290
- # Calculate steps and mark intervals based on max values (0 to max) instead of range
291
- steps = []
292
- input_steps = []
293
- for max_val in max_values:
294
- # Calculate step based on maximum value magnitude (0 to max)
295
- step = _calculate_step_for_range(max_val)
296
- input_step = _calculate_input_step_for_range(max_val)
297
- steps.append(step)
298
- input_steps.append(input_step)
299
-
300
- # Snap min and max values to the slider grid (keeping within original range)
301
- grid_min_values = []
302
- grid_max_values = []
303
- for i, (min_val, max_val, step) in enumerate(zip(min_values, max_values, steps)):
304
- # For min value: round up to nearest grid point (ceil) to stay >= min_val
305
- if min_val % step == 0:
306
- grid_min = min_val
307
- else:
308
- grid_min = min_val + (step - (min_val % step))
309
-
310
- # For max value: round down to nearest grid point (floor) to stay <= max_val
311
- grid_max = max_val - (max_val % step) if max_val % step != 0 else max_val
312
-
313
- # Ensure we have a valid range (grid_min <= grid_max)
314
- if grid_min > grid_max:
315
- # If rounding inward creates invalid range, use original values
316
- grid_min = min_val
317
- grid_max = max_val
318
-
319
- grid_min_values.append(grid_min)
320
- grid_max_values.append(grid_max)
321
-
322
- mark_intervals = calculate_mark_intervals(grid_min_values, grid_max_values)
323
-
324
- # Create mark dictionaries for each slider
325
- mark_vals = []
326
- grid_slider_vals = []
327
- grid_input_vals = []
328
-
329
- for i, (min_val, max_val, step, input_step, mark_interval) in enumerate(
330
- zip(grid_min_values, grid_max_values, steps, input_steps, mark_intervals)
331
- ):
332
- # Generate marks dictionary for this slider (positions only, no labels)
333
- marks = {}
334
-
335
- # Find the first mark position that's a multiple of mark_interval
336
- # Start from the nearest multiple of mark_interval >= min_val
337
- if mark_interval > 0:
338
- first_mark = math.ceil(min_val / mark_interval) * mark_interval
339
- current_mark = first_mark
340
-
341
- while current_mark <= max_val:
342
- # Round to avoid floating-point precision issues
343
- rounded_mark = round(current_mark, 10) # Round to 10 decimal places
344
- marks[str(rounded_mark)] = "" # Convert float key to string, no label
345
- current_mark += mark_interval
346
-
347
- # Ensure we don't exceed max_val due to floating point precision
348
- if current_mark > max_val + mark_interval * 0.01:
349
- break
350
-
351
- mark_vals.append(marks)
352
-
353
- # Handle property value if provided
354
- if property_values is not None:
355
- property_val = property_values[i]
356
- # Snap to both slider grid (coarser) and input grid (finer) without clamping to range
357
- slider_grid_value = snap_to_grid_no_clamp(property_val, min_val, step)
358
- input_grid_value = snap_to_grid_no_clamp(property_val, min_val, input_step)
359
- grid_slider_vals.append(slider_grid_value)
360
- grid_input_vals.append(input_grid_value)
361
-
362
- # Create the configuration dictionary
363
- config = {
364
- "min_vals": grid_min_values,
365
- "max_vals": grid_max_values,
366
- "step_vals": steps,
367
- "input_step_vals": input_steps,
368
- "mark_vals": mark_vals,
369
- }
370
-
371
- # Add grid values if property values were provided
372
- if property_values is not None:
373
- config["grid_slider_vals"] = grid_slider_vals
374
- config["grid_input_vals"] = grid_input_vals
375
-
376
- return config
377
-
378
-
379
- def create_range_slider_config(
380
- min_values: List[float],
381
- max_values: List[float],
382
- lower_values: List[float] = None,
383
- upper_values: List[float] = None,
384
- ) -> dict:
385
- """
386
- Create complete range slider configurations with automatically calculated steps and marks.
387
-
388
- This is a convenience function that combines step and mark interval calculations
389
- to create ready-to-use range slider configurations. If lower and upper values are provided,
390
- they will be validated, adjusted to ensure proper ordering, and aligned with the
391
- calculated slider grids.
392
-
393
- Args:
394
- min_values (List[float]): Minimum values for each range slider
395
- max_values (List[float]): Maximum values for each range slider
396
- lower_values (List[float], optional): Current lower bound values for each range slider.
397
- Will be snapped to grid and validated.
398
- upper_values (List[float], optional): Current upper bound values for each range slider.
399
- Will be snapped to grid and validated.
400
-
401
- Returns:
402
- dict: Complete configuration dictionary containing:
403
- - 'min_vals' (List[float]): Minimum values for each range slider (snapped to grid, >= original min)
404
- - 'max_vals' (List[float]): Maximum values for each range slider (snapped to grid, <= original max)
405
- - 'step_vals' (List[float]): Calculated step values for each range slider (same as input steps)
406
- - 'input_step_vals' (List[float]): Step values for inputs (same as slider steps)
407
- - 'mark_vals' (List[dict]): Mark positions for each range slider (position → '' mapping)
408
- - 'grid_slider_vals' (List[tuple], optional): Range values snapped to slider grid if provided [(lower, upper), ...]
409
- - 'grid_input_vals' (List[tuple], optional): Range values snapped to input grid if provided [(lower, upper), ...]
410
-
411
- Example:
412
- >>> config = create_range_slider_config(
413
- ... min_values=[0, 20, 0],
414
- ... max_values=[100, 80, 1000],
415
- ... lower_values=[23.7, 45.3, 167.8],
416
- ... upper_values=[67.2, 55.1, 834.5]
417
- ... )
418
- >>> config['step_vals']
419
- [0.01, 0.01, 1.0]
420
- >>> config['input_step_vals']
421
- [0.01, 0.01, 1.0]
422
- >>> config['grid_slider_vals'][0] # (23.7, 67.2) snapped to slider grid
423
- (23.7, 67.2)
424
- """
425
- # Validate inputs
426
- if len(min_values) != len(max_values):
427
- raise ValueError("min_values and max_values must have the same length")
428
-
429
- if lower_values is not None and len(lower_values) != len(min_values):
430
- raise ValueError(
431
- "lower_values must have the same length as min_values and max_values"
432
- )
433
-
434
- if upper_values is not None and len(upper_values) != len(min_values):
435
- raise ValueError(
436
- "upper_values must have the same length as min_values and max_values"
437
- )
438
-
439
- # Both lower_values and upper_values must be provided together or not at all
440
- if (lower_values is None) != (upper_values is None):
441
- raise ValueError(
442
- "lower_values and upper_values must both be provided or both be None"
443
- )
444
-
445
- # Calculate steps and mark intervals
446
- steps = calculate_slider_steps(
447
- min_values, max_values
448
- ) # Use the calculated steps directly
449
-
450
- input_steps = calculate_input_steps(
451
- min_values, max_values
452
- ) # Calculate input steps based on range
453
-
454
- # Snap min and max values to the slider grid (keeping within original range)
455
- grid_min_values = []
456
- grid_max_values = []
457
- for i, (min_val, max_val, step) in enumerate(zip(min_values, max_values, steps)):
458
- # For min value: round up to nearest grid point (ceil) to stay >= min_val
459
- if min_val % step == 0:
460
- grid_min = min_val
461
- else:
462
- grid_min = min_val + (step - (min_val % step))
463
-
464
- # For max value: round down to nearest grid point (floor) to stay <= max_val
465
- grid_max = max_val - (max_val % step) if max_val % step != 0 else max_val
466
-
467
- # Ensure we have a valid range (grid_min <= grid_max)
468
- if grid_min > grid_max:
469
- # If rounding inward creates invalid range, use original values
470
- grid_min = min_val
471
- grid_max = max_val
472
-
473
- grid_min_values.append(grid_min)
474
- grid_max_values.append(grid_max)
475
-
476
- mark_intervals = calculate_mark_intervals(grid_min_values, grid_max_values)
477
-
478
- # Generate marks for each slider
479
- mark_vals = []
480
- for min_val, max_val, interval in zip(
481
- grid_min_values, grid_max_values, mark_intervals
482
- ):
483
- marks = {}
484
-
485
- # Find the first mark position that's a multiple of interval
486
- # Start from the nearest multiple of interval >= min_val
487
- if interval > 0:
488
- first_mark = math.ceil(min_val / interval) * interval
489
- current = first_mark
490
-
491
- while current <= max_val:
492
- # Round to avoid floating-point precision issues
493
- rounded_mark = round(current, 10) # Round to 10 decimal places
494
- marks[str(rounded_mark)] = "" # Convert float key to string, empty string for clean appearance
495
- current += interval
496
-
497
- mark_vals.append(marks)
498
-
499
- # Process property values if provided
500
- grid_slider_vals = []
501
- grid_input_vals = []
502
-
503
- if lower_values is not None and upper_values is not None:
504
- for i, (min_val, max_val, step, input_step, lower_val, upper_val) in enumerate(
505
- zip(
506
- grid_min_values,
507
- grid_max_values,
508
- steps,
509
- input_steps,
510
- lower_values,
511
- upper_values,
512
- )
513
- ):
514
- # Ensure proper ordering of lower/upper values
515
- actual_lower = min(lower_val, upper_val)
516
- actual_upper = max(lower_val, upper_val)
517
-
518
- # Snap to grids
519
- lower_slider_grid = snap_to_grid_no_clamp(actual_lower, min_val, step)
520
- upper_slider_grid = snap_to_grid_no_clamp(actual_upper, min_val, step)
521
- lower_input_grid = snap_to_grid_no_clamp(actual_lower, min_val, input_step)
522
- upper_input_grid = snap_to_grid_no_clamp(actual_upper, min_val, input_step)
523
-
524
- # Store as tuples
525
- grid_slider_vals.append((lower_slider_grid, upper_slider_grid))
526
- grid_input_vals.append((lower_input_grid, upper_input_grid))
527
-
528
- # Create the configuration dictionary
529
- config = {
530
- "min_vals": grid_min_values,
531
- "max_vals": grid_max_values,
532
- "step_vals": steps,
533
- "input_step_vals": input_steps,
534
- "mark_vals": mark_vals,
535
- }
536
-
537
- # Add grid values if property values were provided
538
- if lower_values is not None and upper_values is not None:
539
- config["grid_slider_vals"] = grid_slider_vals
540
- config["grid_input_vals"] = grid_input_vals
541
-
542
- return config
543
-
544
-
545
- def snap_to_slider_grid(
546
- value: float, min_val: float, max_val: float, step: float
547
- ) -> float:
548
- """
549
- Snap a value to the nearest valid position on a slider grid.
550
-
551
- This ensures that property values align with the discrete steps of a slider,
552
- preventing issues with values that fall between valid slider positions.
553
-
554
- Args:
555
- value (float): The value to snap to the grid
556
- min_val (float): Minimum value of the slider range
557
- max_val (float): Maximum value of the slider range
558
- step (float): Step size of the slider
559
-
560
- Returns:
561
- float: Value snapped to the nearest valid slider position
562
-
563
- Examples:
564
- >>> # Snap 23.7 to a slider with step 0.1
565
- >>> snap_to_slider_grid(23.7, 0, 100, 0.1)
566
- 23.7
567
-
568
- >>> # Snap 23.75 to a slider with step 0.1
569
- >>> snap_to_slider_grid(23.75, 0, 100, 0.1)
570
- 23.8
571
-
572
- >>> # Clamp value outside range
573
- >>> snap_to_slider_grid(150, 0, 100, 1.0)
574
- 100.0
575
- """
576
- # First clamp to range
577
- clamped_value = max(min_val, min(max_val, value))
578
-
579
- # Calculate offset from minimum
580
- offset = clamped_value - min_val
581
-
582
- # Round to nearest step
583
- num_steps = round(offset / step)
584
-
585
- # Calculate snapped value
586
- snapped_value = min_val + (num_steps * step)
587
-
588
- # Ensure we stay within bounds (floating point precision issues)
589
- snapped_value = max(min_val, min(max_val, snapped_value))
590
-
591
- # Round to appropriate precision based on step size
592
- if step >= 1:
593
- return round(snapped_value)
594
- else:
595
- decimal_places = max(0, -math.floor(math.log10(step)))
596
- return round(snapped_value, decimal_places)
597
-
598
-
599
- def snap_to_grid_no_clamp(value: float, min_val: float, step: float) -> float:
600
- """
601
- Snap a value to the nearest grid position without clamping to range.
602
-
603
- This function snaps values to the grid defined by min_val and step,
604
- but does NOT clamp values to stay within a min/max range. This allows
605
- property values outside the slider range to maintain their relative
606
- position on the extended grid.
607
-
608
- Args:
609
- value (float): The value to snap to the grid
610
- min_val (float): Minimum value of the slider range (grid reference point)
611
- step (float): Step size of the grid
612
-
613
- Returns:
614
- float: Value snapped to the nearest valid grid position
615
-
616
- Examples:
617
- >>> # Snap value within range
618
- >>> snap_to_grid_no_clamp(23.75, 0, 0.1)
619
- 23.8
620
-
621
- >>> # Snap value outside range (not clamped)
622
- >>> snap_to_grid_no_clamp(150.3, 0, 0.1)
623
- 150.3
624
-
625
- >>> # Snap value below range (not clamped)
626
- >>> snap_to_grid_no_clamp(-5.67, 0, 0.1)
627
- -5.7
628
- """
629
- # Calculate offset from minimum (no clamping)
630
- offset = value - min_val
631
-
632
- # Round to nearest step
633
- num_steps = round(offset / step)
634
-
635
- # Calculate snapped value
636
- snapped_value = min_val + (num_steps * step)
637
-
638
- # Round to appropriate precision based on step size
639
- if step >= 1:
640
- return round(snapped_value)
641
- else:
642
- decimal_places = max(0, -math.floor(math.log10(step)))
643
- return round(snapped_value, decimal_places)
644
-
645
-
646
- def format_slider_value(value: float, step: float) -> str:
647
- """
648
- Format a slider value for display based on the step size.
649
-
650
- Automatically determines appropriate decimal places based on the step size
651
- to avoid displaying unnecessary precision.
652
-
653
- Args:
654
- value (float): The value to format
655
- step (float): The step size of the slider
656
-
657
- Returns:
658
- str: Formatted value string
659
-
660
- Examples:
661
- >>> format_slider_value(123.456, 1.0)
662
- '123'
663
- >>> format_slider_value(123.456, 0.1)
664
- '123.5'
665
- >>> format_slider_value(123.456, 0.01)
666
- '123.46'
667
- """
668
- if step >= 1:
669
- return f"{value:.0f}"
670
- else:
671
- # Determine decimal places based on step size
672
- decimal_places = max(0, -math.floor(math.log10(step)))
673
- return f"{value:.{decimal_places}f}"
674
-
675
-
676
- def are_slider_input_values_incompatible(
677
- slider_value: float, input_value: float, slider_step: float, input_step: float
678
- ) -> bool:
679
- """
680
- Check if slider and input values are incompatible with each other.
681
-
682
- Two values are considered incompatible if there is no single original value
683
- that would snap to both the given slider grid value AND the given input grid value.
684
- This typically happens when the slider value and input value represent different
685
- original values that have been snapped to their respective grids.
686
-
687
- Args:
688
- slider_value (float): The value from the slider grid
689
- input_value (float): The value from the input grid
690
- slider_step (float): Step size for the slider grid
691
- input_step (float): Step size for the input grid
692
-
693
- Returns:
694
- bool: True if the values are incompatible, False if they could represent
695
- the same original value snapped to different grids
696
-
697
- Examples:
698
- >>> # Compatible values: both could come from original value 12.36
699
- >>> are_slider_input_values_incompatible(12.4, 12.36, 0, 0.1, 0.01)
700
- False
701
-
702
- >>> # Incompatible values: no single value could snap to both
703
- >>> are_slider_input_values_incompatible(12.3, 12.36, 0, 0.1, 0.01)
704
- True
705
-
706
- >>> # Compatible values: both could come from original value 12.35
707
- >>> are_slider_input_values_incompatible(12.4, 12.35, 0, 0.1, 0.01)
708
- False
709
- """
710
- # Calculate the range of original values that would snap to the slider_value
711
- slider_half_step = slider_step / 2.0
712
- slider_min_original = slider_value - slider_half_step
713
- slider_max_original = slider_value + slider_half_step
714
-
715
- # Calculate the range of original values that would snap to the input_value
716
- input_half_step = input_step / 2.0
717
- input_min_original = input_value - input_half_step
718
- input_max_original = input_value + input_half_step
719
-
720
- # Check if the ranges overlap
721
- # If they overlap, there exists at least one original value that would snap to both
722
- overlap_start = max(slider_min_original, input_min_original)
723
- overlap_end = min(slider_max_original, input_max_original)
724
-
725
- # If overlap_start > overlap_end, there's no overlap (incompatible)
726
- # Use a small tolerance for floating-point comparison
727
- tolerance = 1e-12
728
- return overlap_start > overlap_end + tolerance