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.
- syd/__init__.py +3 -10
- syd/flask_deployment/deployer.py +119 -26
- syd/flask_deployment/static/css/styles.css +100 -67
- syd/flask_deployment/static/css/viewer.css +48 -0
- syd/flask_deployment/static/js/modules/api.js +89 -0
- syd/flask_deployment/static/js/modules/config.js +22 -0
- syd/flask_deployment/static/js/modules/plot.js +75 -0
- syd/flask_deployment/static/js/modules/state.js +89 -0
- syd/flask_deployment/static/js/modules/system_controls.js +191 -0
- syd/flask_deployment/static/js/modules/ui_controls.js +812 -0
- syd/flask_deployment/static/js/modules/utils.js +49 -0
- syd/flask_deployment/static/js/old_viewer.js +1195 -0
- syd/flask_deployment/static/js/viewer.js +53 -826
- syd/flask_deployment/templates/index.html +1 -1
- syd/notebook_deployment/deployer.py +1 -3
- syd/notebook_deployment/widgets.py +45 -27
- syd/support.py +25 -0
- syd/viewer.py +35 -4
- {syd-1.0.2.dist-info → syd-1.2.0.dist-info}/METADATA +24 -10
- syd-1.2.0.dist-info/RECORD +28 -0
- syd-1.0.2.dist-info/RECORD +0 -19
- {syd-1.0.2.dist-info → syd-1.2.0.dist-info}/WHEEL +0 -0
- {syd-1.0.2.dist-info → syd-1.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,831 +1,58 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
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
|
-
//
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
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
|
+
});
|