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