syd 1.0.2__py3-none-any.whl → 1.2.0__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,831 +1,58 @@
1
- let state = {};
2
- let paramInfo = {};
3
- let paramOrder = [];
4
- let isUpdating = false;
5
-
6
- // Config object parsed from HTML data attributes
7
- const config = {
8
- controlsPosition: document.getElementById('viewer-config').dataset.controlsPosition || 'left',
9
- controlsWidthPercent: parseInt(document.getElementById('viewer-config').dataset.controlsWidthPercent || 20)
10
- };
11
-
12
- // Initialize the viewer
13
- document.addEventListener('DOMContentLoaded', function() {
14
- // Fetch initial parameter information from server
15
- fetch('/init-data')
16
- .then(response => response.json())
17
- .then(data => {
18
- paramInfo = data.params;
19
- paramOrder = data.param_order;
20
-
21
- // Initialize state from parameter info
22
- for (const [name, param] of Object.entries(paramInfo)) {
23
- state[name] = param.value;
24
- }
25
-
26
- // Create UI controls for each parameter
27
- createControls();
28
-
29
- // Generate initial plot
30
- updatePlot();
31
- })
32
- .catch(error => {
33
- console.error('Error initializing viewer:', error);
34
- });
35
- });
36
-
37
- /**
38
- * Create UI controls based on parameter types
39
- */
40
- function createControls() {
41
- const controlsContainer = document.getElementById('controls-container');
42
-
43
- // Clear any existing controls
44
- controlsContainer.innerHTML = '';
45
-
46
- // Create controls for each parameter in the order specified by the viewer
47
- paramOrder.forEach(name => {
48
- const param = paramInfo[name];
49
- if (!param) {
50
- console.warn(`Parameter info not found for ${name} during control creation.`);
51
- return; // Skip if param info is missing for some reason
52
- }
53
-
54
- // Create control group
55
- const controlGroup = createControlGroup(name, param);
56
-
57
- // Add to container
58
- if (controlGroup) {
59
- controlsContainer.appendChild(controlGroup);
60
- }
61
- });
62
- }
63
-
64
- /**
65
- * Create a control group for a parameter
66
- */
67
- function createControlGroup(name, param) {
68
- // Skip if param type is unknown
69
- if (!param.type || param.type === 'unknown') {
70
- console.warn(`Unknown parameter type for ${name}`);
71
- return null;
1
+ import { fetchInitialData } from './modules/api.js';
2
+ import { createControls } from './modules/ui_controls.js';
3
+ import { createSystemControls } from './modules/system_controls.js';
4
+ import { config } from './modules/config.js';
5
+ import { updateStatus } from './modules/utils.js';
6
+ import { updatePlot } from './modules/plot.js';
7
+
8
+ document.addEventListener('DOMContentLoaded', async () => {
9
+ updateStatus('Initializing...');
10
+
11
+ // Get the main container where controls will be placed (assuming it exists in HTML)
12
+ const mainContainer = document.getElementById('controls-container');
13
+ if (!mainContainer) {
14
+ console.error("Fatal Error: Element with ID 'controls-container' not found.");
15
+ updateStatus("Error: Main controls container missing.");
16
+ return; // Stop execution if main container is missing
72
17
  }
73
-
74
- // Create control group div
75
- const controlGroup = document.createElement('div');
76
- controlGroup.className = 'control-group';
77
- controlGroup.id = `control-group-${name}`;
78
-
79
- // Add label
80
- const label = document.createElement('span');
81
- label.className = 'control-label';
82
- label.textContent = formatLabel(name);
83
- controlGroup.appendChild(label);
84
-
85
- // Create specific control based on parameter type
86
- const control = createControl(name, param);
87
- if (control) {
88
- controlGroup.appendChild(control);
89
- }
90
-
91
- return controlGroup;
92
- }
93
-
94
- /**
95
- * Create a specific control based on parameter type
96
- */
97
- function createControl(name, param) {
98
- switch (param.type) {
99
- case 'text':
100
- return createTextControl(name, param);
101
- case 'boolean':
102
- return createBooleanControl(name, param);
103
- case 'integer':
104
- return createIntegerControl(name, param);
105
- case 'float':
106
- return createFloatControl(name, param);
107
- case 'selection':
108
- return createSelectionControl(name, param);
109
- case 'multiple-selection':
110
- return createMultipleSelectionControl(name, param);
111
- case 'integer-range':
112
- return createIntegerRangeControl(name, param);
113
- case 'float-range':
114
- return createFloatRangeControl(name, param);
115
- case 'unbounded-integer':
116
- return createUnboundedIntegerControl(name, param);
117
- case 'unbounded-float':
118
- return createUnboundedFloatControl(name, param);
119
- case 'button':
120
- return createButtonControl(name, param);
121
- default:
122
- console.warn(`No control implementation for type: ${param.type}`);
123
- return null;
124
- }
125
- }
126
-
127
- /**
128
- * Create text input control
129
- */
130
- function createTextControl(name, param) {
131
- const input = document.createElement('input');
132
- input.type = 'text';
133
- input.id = `${name}-input`;
134
- input.value = param.value || '';
135
-
136
- input.addEventListener('change', function() {
137
- updateParameter(name, this.value);
138
- });
139
-
140
- return input;
141
- }
142
-
143
- /**
144
- * Create boolean checkbox control
145
- */
146
- function createBooleanControl(name, param) {
147
- const container = document.createElement('div');
148
- container.className = 'checkbox-container';
149
-
150
- const checkbox = document.createElement('input');
151
- checkbox.type = 'checkbox';
152
- checkbox.id = `${name}-checkbox`;
153
- checkbox.checked = param.value === true;
154
-
155
- checkbox.addEventListener('change', function() {
156
- updateParameter(name, this.checked);
157
- });
158
-
159
- container.appendChild(checkbox);
160
- return container;
161
- }
162
-
163
- /**
164
- * Create integer control with slider and number input
165
- */
166
- function createIntegerControl(name, param) {
167
- const container = document.createElement('div');
168
- container.className = 'numeric-control';
169
-
170
- // Create slider
171
- const slider = document.createElement('input');
172
- slider.type = 'range';
173
- slider.id = `${name}-slider`;
174
- slider.min = param.min;
175
- slider.max = param.max;
176
- slider.step = param.step || 1;
177
- slider.value = param.value;
178
-
179
- // Create number input
180
- const input = document.createElement('input');
181
- input.type = 'number';
182
- input.id = `${name}-input`;
183
- input.min = param.min;
184
- input.max = param.max;
185
- input.step = param.step || 1;
186
- input.value = param.value;
187
-
188
- // Add event listeners
189
- slider.addEventListener('change', function() {
190
- const value = parseInt(this.value, 10);
191
- input.value = value;
192
- updateParameter(name, value);
193
- });
194
-
195
- input.addEventListener('change', function() {
196
- const value = parseInt(this.value, 10);
197
- if (!isNaN(value) && value >= param.min && value <= param.max) {
198
- slider.value = value;
199
- updateParameter(name, value);
200
- } else {
201
- this.value = state[name]; // Revert to current state
202
- }
203
- });
204
-
205
- container.appendChild(slider);
206
- container.appendChild(input);
207
- return container;
208
- }
209
-
210
- /**
211
- * Create float control with slider and number input
212
- */
213
- function createFloatControl(name, param) {
214
- const container = document.createElement('div');
215
- container.className = 'numeric-control';
216
-
217
- // Create slider
218
- const slider = document.createElement('input');
219
- slider.type = 'range';
220
- slider.id = `${name}-slider`;
221
- slider.min = param.min;
222
- slider.max = param.max;
223
- slider.step = param.step || 0.01;
224
- slider.value = param.value;
225
-
226
- // Create number input
227
- const input = document.createElement('input');
228
- input.type = 'number';
229
- input.id = `${name}-input`;
230
- input.min = param.min;
231
- input.max = param.max;
232
- input.step = param.step || 0.01;
233
- input.value = param.value;
234
-
235
- // Add event listeners
236
- slider.addEventListener('change', function() {
237
- const value = parseFloat(this.value);
238
- input.value = value;
239
- updateParameter(name, value);
240
- });
241
-
242
- input.addEventListener('change', function() {
243
- const value = parseFloat(this.value);
244
- if (!isNaN(value) && value >= param.min && value <= param.max) {
245
- slider.value = value;
246
- updateParameter(name, value);
247
- } else {
248
- this.value = state[name]; // Revert to current state
249
- }
250
- });
251
-
252
- container.appendChild(slider);
253
- container.appendChild(input);
254
- return container;
255
- }
256
-
257
- /**
258
- * Create selection dropdown control
259
- */
260
- function createSelectionControl(name, param) {
261
- const select = document.createElement('select');
262
- select.id = `${name}-select`;
263
-
264
- // Add options
265
- param.options.forEach(option => {
266
- const optionElement = document.createElement('option');
267
- optionElement.value = option;
268
- optionElement.textContent = formatLabel(String(option));
269
- // Store the original type information as a data attribute
270
- optionElement.dataset.originalType = typeof option;
271
- // For float values, also store the original value for exact comparison
272
- if (typeof option === 'number') {
273
- optionElement.dataset.originalValue = option;
274
- }
275
- select.appendChild(optionElement);
276
- });
277
-
278
- // Set default value
279
- select.value = param.value;
280
-
281
- // Add event listener
282
- select.addEventListener('change', function() {
283
- // Get the selected option element
284
- const selectedOption = this.options[this.selectedIndex];
285
- let valueToSend = this.value;
286
-
287
- // Convert back to the original type if needed
288
- if (selectedOption.dataset.originalType === 'number') {
289
- // Use the original value from the dataset for exact precision with floats
290
- if (selectedOption.dataset.originalValue) {
291
- valueToSend = parseFloat(selectedOption.dataset.originalValue);
292
- } else {
293
- valueToSend = parseFloat(valueToSend);
294
- }
295
- }
296
-
297
- updateParameter(name, valueToSend);
298
- });
299
-
300
- return select;
301
- }
302
-
303
- /**
304
- * Create multiple selection control
305
- */
306
- function createMultipleSelectionControl(name, param) {
307
- const container = document.createElement('div');
308
- container.className = 'multiple-selection-container';
309
-
310
- // Create select element
311
- const select = document.createElement('select');
312
- select.id = `${name}-select`;
313
- select.className = 'multiple-select';
314
- select.multiple = true;
315
-
316
- // Add options
317
- param.options.forEach(option => {
318
- const optionElement = document.createElement('option');
319
- optionElement.value = option;
320
- optionElement.textContent = formatLabel(String(option));
321
-
322
- // Check if this option is selected
323
- if (param.value.includes(option)) {
324
- optionElement.selected = true;
325
- }
326
-
327
- select.appendChild(optionElement);
328
- });
329
-
330
- // Helper text
331
- const helperText = document.createElement('div');
332
- helperText.className = 'helper-text';
333
- helperText.textContent = 'Ctrl+click to select multiple';
334
-
335
- // Add event listener
336
- select.addEventListener('change', function() {
337
- // Get all selected options
338
- const selectedValues = Array.from(this.selectedOptions).map(option => option.value);
339
- updateParameter(name, selectedValues);
340
- });
341
-
342
- container.appendChild(select);
343
- container.appendChild(helperText);
344
- return container;
345
- }
346
-
347
- /**
348
- * Create integer range control with dual sliders
349
- */
350
- function createIntegerRangeControl(name, param) {
351
- return createRangeControl(name, param, parseInt);
352
- }
353
-
354
- /**
355
- * Create float range control with dual sliders
356
- */
357
- function createFloatRangeControl(name, param) {
358
- return createRangeControl(name, param, parseFloat);
359
- }
360
-
361
- /**
362
- * Generic range control creator
363
- */
364
- function createRangeControl(name, param, converter) {
365
- const container = document.createElement('div');
366
- container.className = 'range-container';
367
-
368
- // Create inputs container
369
- const inputsContainer = document.createElement('div');
370
- inputsContainer.className = 'range-inputs';
371
-
372
- // Create min input
373
- const minInput = document.createElement('input');
374
- minInput.type = 'number';
375
- minInput.id = `${name}-min-input`;
376
- minInput.className = 'range-input';
377
- minInput.min = param.min;
378
- minInput.max = param.max;
379
- minInput.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
380
- minInput.value = param.value[0];
381
-
382
- // Create slider container
383
- const sliderContainer = document.createElement('div');
384
- sliderContainer.className = 'range-slider-container';
385
-
386
- // Create min slider
387
- const minSlider = document.createElement('input');
388
- minSlider.type = 'range';
389
- minSlider.id = `${name}-min-slider`;
390
- minSlider.className = 'range-slider min-slider';
391
- minSlider.min = param.min;
392
- minSlider.max = param.max;
393
- minSlider.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
394
- minSlider.value = param.value[0];
395
-
396
- // Create max slider
397
- const maxSlider = document.createElement('input');
398
- maxSlider.type = 'range';
399
- maxSlider.id = `${name}-max-slider`;
400
- maxSlider.className = 'range-slider max-slider';
401
- maxSlider.min = param.min;
402
- maxSlider.max = param.max;
403
- maxSlider.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
404
- maxSlider.value = param.value[1];
405
-
406
- // Create max input
407
- const maxInput = document.createElement('input');
408
- maxInput.type = 'number';
409
- maxInput.id = `${name}-max-input`;
410
- maxInput.className = 'range-input';
411
- maxInput.min = param.min;
412
- maxInput.max = param.max;
413
- maxInput.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
414
- maxInput.value = param.value[1];
415
-
416
- // Add event listeners
417
- minSlider.addEventListener('change', function() {
418
- const minVal = converter(this.value);
419
- const maxVal = converter(maxSlider.value);
420
-
421
- if (minVal <= maxVal) {
422
- state[name] = [minVal, maxVal];
423
- minInput.value = minVal;
424
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
425
- updateParameter(name, [minVal, maxVal]);
426
- } else {
427
- this.value = maxVal; // Snap to maxVal if crossing
428
- minInput.value = maxVal; // Also update input
429
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
430
- }
431
- });
432
-
433
- maxSlider.addEventListener('change', function() {
434
- const minVal = converter(minSlider.value);
435
- const maxVal = converter(this.value);
436
-
437
- if (maxVal >= minVal) {
438
- state[name] = [minVal, maxVal];
439
- maxInput.value = maxVal;
440
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
441
- updateParameter(name, [minVal, maxVal]);
442
- } else {
443
- this.value = minVal; // Snap to minVal if crossing
444
- maxInput.value = minVal; // Also update input
445
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
446
- }
447
- });
448
-
449
- minInput.addEventListener('change', function() {
450
- const minVal = converter(this.value);
451
- const maxVal = converter(maxInput.value);
452
-
453
- if (!isNaN(minVal) && minVal >= param.min && minVal <= maxVal) {
454
- state[name] = [minVal, maxVal];
455
- minSlider.value = minVal;
456
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
457
- updateParameter(name, [minVal, maxVal]);
458
- } else {
459
- // Revert input value and ensure gradient matches state
460
- this.value = state[name][0];
461
- minSlider.value = state[name][0];
462
- updateSliderGradient(minSlider, maxSlider, sliderContainer);
463
- }
464
- });
465
-
466
- maxInput.addEventListener('change', function() {
467
- const minVal = converter(minInput.value);
468
- const maxVal = converter(this.value);
469
-
470
- if (!isNaN(maxVal) && maxVal <= param.max && maxVal >= minVal) {
471
- state[name] = [minVal, maxVal];
472
- maxSlider.value = maxVal;
473
- updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
474
- updateParameter(name, [minVal, maxVal]);
475
- } else {
476
- // Revert input value and ensure gradient matches state
477
- this.value = state[name][1];
478
- maxSlider.value = state[name][1];
479
- updateSliderGradient(minSlider, maxSlider, sliderContainer);
480
- }
481
- });
482
-
483
- // Assemble the control
484
- inputsContainer.appendChild(minInput);
485
- inputsContainer.appendChild(maxInput);
486
-
487
- sliderContainer.appendChild(minSlider);
488
- sliderContainer.appendChild(maxSlider);
489
-
490
- container.appendChild(inputsContainer);
491
- container.appendChild(sliderContainer);
492
-
493
- // Set initial gradient state
494
- updateSliderGradient(minSlider, maxSlider, sliderContainer);
495
-
496
- return container;
497
- }
498
-
499
- /**
500
- * Create unbounded integer control
501
- */
502
- function createUnboundedIntegerControl(name, param) {
503
- const input = document.createElement('input');
504
- input.type = 'number';
505
- input.id = `${name}-input`;
506
- input.value = param.value;
507
- input.step = 1;
508
-
509
- input.addEventListener('change', function() {
510
- const value = parseInt(this.value, 10);
511
- if (!isNaN(value)) {
512
- updateParameter(name, value);
513
- } else {
514
- this.value = state[name];
515
- }
516
- });
517
-
518
- return input;
519
- }
520
-
521
- /**
522
- * Create unbounded float control
523
- */
524
- function createUnboundedFloatControl(name, param) {
525
- const input = document.createElement('input');
526
- input.type = 'number';
527
- input.id = `${name}-input`;
528
- input.value = param.value;
529
- input.step = param.step || 'any';
530
-
531
- input.addEventListener('change', function() {
532
- const value = parseFloat(this.value);
533
- if (!isNaN(value)) {
534
- updateParameter(name, value);
535
- } else {
536
- this.value = state[name];
537
- }
538
- });
539
-
540
- return input;
541
- }
542
18
 
543
- /**
544
- * Create button control
545
- */
546
- function createButtonControl(name, param) {
547
- const button = document.createElement('button');
548
- button.id = `${name}-button`;
549
- button.textContent = param.label || name;
550
-
551
- button.addEventListener('click', function() {
552
- // Show button as active
553
- button.classList.add('active');
554
-
555
- // Send action to the server
556
- fetch('/update-param', {
557
- method: 'POST',
558
- headers: {
559
- 'Content-Type': 'application/json',
560
- },
561
- body: JSON.stringify({
562
- name: name,
563
- value: null, // Value is not used for buttons
564
- action: true
565
- }),
566
- })
567
- .then(response => response.json())
568
- .then(data => {
569
- // Remove active class
570
- button.classList.remove('active');
571
-
572
- if (data.error) {
573
- console.error('Error:', data.error);
574
- } else {
575
- // Update state with any changes from callbacks
576
- updateStateFromServer(data.state, data.params);
577
- // Update plot if needed
578
- updatePlot();
579
- }
580
- })
581
- .catch(error => {
582
- // Remove active class
583
- button.classList.remove('active');
584
- console.error('Error:', error);
585
- });
586
- });
587
-
588
- return button;
589
- }
19
+ // --- Create the necessary sub-containers dynamically ---
20
+ // Create parameter controls section
21
+ const paramControls = document.createElement('div');
22
+ paramControls.id = 'parameter-controls';
23
+ paramControls.className = 'parameter-controls';
24
+ // Header will be added by createControls() later
25
+
26
+ // Create system controls section
27
+ const systemControls = document.createElement('div');
28
+ systemControls.id = 'system-controls';
29
+ systemControls.className = 'system-controls';
30
+
31
+ // Create status element (needs to exist before first updateStatus call potentially)
32
+ const statusElement = document.createElement('div');
33
+ statusElement.id = 'status-display';
34
+ statusElement.className = 'status-display';
35
+ systemControls.appendChild(statusElement); // Add status display to system controls container
36
+
37
+ // Add sections to main container in the desired order
38
+ mainContainer.appendChild(paramControls); // Add parameter controls container
39
+ mainContainer.appendChild(systemControls); // Add system controls container
40
+ // --- End of dynamic container creation ---
590
41
 
591
- /**
592
- * Update a parameter value and send to server
593
- */
594
- function updateParameter(name, value) {
595
- // Prevent recursive updates
596
- if (isUpdating) {
597
- return;
598
- }
599
-
600
- // Update local state
601
- state[name] = value;
602
-
603
- // Send update to server
604
- fetch('/update-param', {
605
- method: 'POST',
606
- headers: {
607
- 'Content-Type': 'application/json',
608
- },
609
- body: JSON.stringify({
610
- name: name,
611
- value: value,
612
- action: false
613
- }),
614
- })
615
- .then(response => response.json())
616
- .then(data => {
617
- if (data.error) {
618
- console.error('Error:', data.error);
619
- } else {
620
- // Update state with any changes from callbacks
621
- updateStateFromServer(data.state, data.params);
622
- // Update plot
623
- updatePlot();
624
- }
625
- })
626
- .catch(error => {
627
- console.error('Error:', error);
628
- });
629
- }
630
-
631
- /**
632
- * Update local state from server response
633
- */
634
- function updateStateFromServer(serverState, serverParamInfo) {
635
- // Set updating flag to prevent recursive updates
636
- isUpdating = true;
637
-
638
42
  try {
639
- // Update any parameters that changed due to callbacks
640
- for (const [name, value] of Object.entries(serverState)) {
641
- if (JSON.stringify(state[name]) !== JSON.stringify(value) || JSON.stringify(paramInfo[name]) !== JSON.stringify(serverParamInfo[name])) {
642
- state[name] = value;
643
- updateControlValue(name, value, serverParamInfo[name]);
644
- }
645
- }
646
- } finally {
647
- // Clear updating flag
648
- isUpdating = false;
43
+ // Now fetch data and create controls, the containers exist
44
+ await fetchInitialData();
45
+ createControls(); // Populates #parameter-controls
46
+ if (['left', 'right'].includes(config.controlsPosition)) {
47
+ // Pass the already created systemControls element
48
+ createSystemControls(systemControls); // Populates #system-controls
49
+ }
50
+ updatePlot();
51
+ updateStatus('Ready!');
52
+ } catch (error) {
53
+ console.error("Initialization failed:", error);
54
+ // Status might have already been updated by the failing function (e.g., fetchInitialData)
55
+ // If not, uncomment the line below:
56
+ // updateStatus('Initialization failed.');
649
57
  }
650
- }
651
-
652
- /**
653
- * Update a control's value in the UI
654
- */
655
- function updateControlValue(name, value, param) {
656
- if (!paramInfo[name]) return;
657
-
658
- switch (param.type) {
659
- case 'text':
660
- document.getElementById(`${name}-input`).value = value;
661
- break;
662
- case 'boolean':
663
- document.getElementById(`${name}-checkbox`).checked = value === true;
664
- break;
665
- case 'integer':
666
- case 'float':
667
- const slider = document.getElementById(`${name}-slider`);
668
- slider.value = value;
669
- slider.min = param.min;
670
- slider.max = param.max;
671
- slider.step = param.step;
672
- const input = document.getElementById(`${name}-input`);
673
- input.value = value;
674
- input.min = param.min;
675
- input.max = param.max;
676
- input.step = param.step;
677
- break;
678
- case 'selection':
679
- const selectElement = document.getElementById(`${name}-select`);
680
- if (selectElement) {
681
- // 1. Clear existing options
682
- selectElement.innerHTML = '';
683
-
684
- // 2. Add new options from the updated param info
685
- if (param.options && Array.isArray(param.options)) {
686
- param.options.forEach(option => {
687
- const optionElement = document.createElement('option');
688
- optionElement.value = option;
689
- optionElement.textContent = formatLabel(String(option));
690
- // Store original type/value info
691
- optionElement.dataset.originalType = typeof option;
692
- if (typeof option === 'number') {
693
- optionElement.dataset.originalValue = option;
694
- }
695
- selectElement.appendChild(optionElement);
696
- });
697
- } else {
698
- console.warn(`No options found or options is not an array for parameter: ${name}`);
699
- }
700
-
701
- selectElement.value = value;
702
- } else {
703
- console.warn(`No select element found for parameter: ${name}`);
704
- }
705
- break;
706
- case 'multiple-selection':
707
- const multiSelect = document.getElementById(`${name}-select`);
708
- if (multiSelect) {
709
- multiSelect.innerHTML = '';
710
-
711
- if (param.options && Array.isArray(param.options)) {
712
- param.options.forEach(option => {
713
- const optionElement = document.createElement('option');
714
- optionElement.value = option;
715
- optionElement.textContent = formatLabel(String(option));
716
- multiSelect.appendChild(optionElement);
717
- });
718
- } else {
719
- console.warn(`No options found or options is not an array for parameter: ${name}`);
720
- }
721
-
722
- Array.from(multiSelect.options).forEach(option => {
723
- option.selected = value.includes(option.value);
724
- });
725
- }
726
- break;
727
- case 'integer-range':
728
- case 'float-range':
729
- const minSlider = document.getElementById(`${name}-min-slider`);
730
- const maxSlider = document.getElementById(`${name}-max-slider`);
731
- const minInput = document.getElementById(`${name}-min-input`);
732
- const maxInput = document.getElementById(`${name}-max-input`);
733
-
734
- minSlider.min = param.min;
735
- minSlider.max = param.max;
736
- minSlider.step = param.step;
737
- maxSlider.min = param.min;
738
- maxSlider.max = param.max;
739
- maxSlider.step = param.step;
740
- minInput.min = param.min;
741
- minInput.max = param.max;
742
- minInput.step = param.step;
743
- maxInput.min = param.min;
744
- maxInput.max = param.max;
745
- maxInput.step = param.step;
746
-
747
- minSlider.value = value[0];
748
- maxSlider.value = value[1];
749
- minInput.value = value[0];
750
- maxInput.value = value[1];
751
-
752
- // Update the gradient background
753
- const sliderContainer = minSlider ? minSlider.closest('.range-slider-container') : null;
754
- if (sliderContainer) {
755
- updateSliderGradient(minSlider, maxSlider, sliderContainer);
756
- } else {
757
- console.warn(`Could not find slider container for range control: ${name}`);
758
- }
759
- break;
760
- case 'unbounded-integer':
761
- case 'unbounded-float':
762
- document.getElementById(`${name}-input`).value = value;
763
- break;
764
- }
765
- }
766
-
767
- /**
768
- * Updates the background gradient for the dual range slider.
769
- * @param {HTMLInputElement} minSlider - The minimum value slider element.
770
- * @param {HTMLInputElement} maxSlider - The maximum value slider element.
771
- * @param {HTMLElement} container - The container element holding the sliders.
772
- */
773
- function updateSliderGradient(minSlider, maxSlider, container) {
774
- const rangeMin = parseFloat(minSlider.min);
775
- const rangeMax = parseFloat(minSlider.max);
776
- const minVal = parseFloat(minSlider.value);
777
- const maxVal = parseFloat(maxSlider.value);
778
-
779
- // Calculate percentages
780
- const range = rangeMax - rangeMin;
781
- // Prevent division by zero if min === max
782
- const minPercent = range === 0 ? 0 : ((minVal - rangeMin) / range) * 100;
783
- const maxPercent = range === 0 ? 100 : ((maxVal - rangeMin) / range) * 100;
784
-
785
- // Update CSS custom properties
786
- container.style.setProperty('--min-pos', `${minPercent}%`);
787
- container.style.setProperty('--max-pos', `${maxPercent}%`);
788
- }
789
-
790
- /**
791
- * Format parameter name as a label (capitalize each word)
792
- */
793
- function formatLabel(name) {
794
- return name
795
- .replace(/_/g, ' ') // Replace underscores with spaces
796
- .replace(/\w\S*/g, function(txt) {
797
- return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
798
- });
799
- }
800
-
801
- /**
802
- * Update the plot with current state
803
- */
804
- function updatePlot() {
805
- // Build query string from state
806
- const queryParams = new URLSearchParams();
807
-
808
- for (const [name, value] of Object.entries(state)) {
809
- // Handle arrays and special types by serializing to JSON
810
- if (Array.isArray(value) || typeof value === 'object') {
811
- queryParams.append(name, JSON.stringify(value));
812
- } else {
813
- queryParams.append(name, value);
814
- }
815
- }
816
-
817
- // Set the image source to the plot endpoint with parameters
818
- const url = `/plot?${queryParams.toString()}`;
819
- const plotImage = document.getElementById('plot-image');
820
-
821
- // Show loading indicator
822
- plotImage.style.opacity = 0.5;
823
-
824
- // Create a new image object
825
- const newImage = new Image();
826
- newImage.onload = function() {
827
- plotImage.src = url;
828
- plotImage.style.opacity = 1;
829
- };
830
- newImage.src = url;
831
- }
58
+ });