syd 1.0.1__py3-none-any.whl → 1.1.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 +2 -10
- syd/flask_deployment/deployer.py +12 -3
- syd/flask_deployment/static/css/styles.css +26 -8
- syd/flask_deployment/static/css/viewer.css +90 -0
- syd/flask_deployment/static/js/viewer.js +394 -30
- syd/notebook_deployment/deployer.py +1 -3
- syd/notebook_deployment/widgets.py +45 -27
- syd/viewer.py +80 -5
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/METADATA +30 -26
- syd-1.1.0.dist-info/RECORD +20 -0
- syd-1.0.1.dist-info/RECORD +0 -19
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/WHEEL +0 -0
- {syd-1.0.1.dist-info → syd-1.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -2,21 +2,56 @@ let state = {};
|
|
|
2
2
|
let paramInfo = {};
|
|
3
3
|
let paramOrder = [];
|
|
4
4
|
let isUpdating = false;
|
|
5
|
+
let updateThreshold = 1.0; // Default update threshold
|
|
6
|
+
let loadingTimeout = null; // Timeout for showing loading state
|
|
7
|
+
let slowLoadingImage = null; // Cache for slow loading image
|
|
5
8
|
|
|
6
9
|
// Config object parsed from HTML data attributes
|
|
7
10
|
const config = {
|
|
8
11
|
controlsPosition: document.getElementById('viewer-config').dataset.controlsPosition || 'left',
|
|
9
|
-
controlsWidthPercent: parseInt(document.getElementById('viewer-config').dataset.controlsWidthPercent || 20)
|
|
12
|
+
controlsWidthPercent: parseInt(document.getElementById('viewer-config').dataset.controlsWidthPercent || 20),
|
|
13
|
+
plotMarginPercent: parseInt(document.getElementById('viewer-config').dataset.plotMarginPercent || 0)
|
|
10
14
|
};
|
|
11
15
|
|
|
12
16
|
// Initialize the viewer
|
|
13
17
|
document.addEventListener('DOMContentLoaded', function() {
|
|
18
|
+
// Create main controls container if it doesn't exist
|
|
19
|
+
const mainContainer = document.getElementById('controls-container');
|
|
20
|
+
|
|
21
|
+
// Create parameter controls section first
|
|
22
|
+
const paramControls = document.createElement('div');
|
|
23
|
+
paramControls.id = 'parameter-controls';
|
|
24
|
+
paramControls.className = 'parameter-controls';
|
|
25
|
+
|
|
26
|
+
// Add Parameters header
|
|
27
|
+
const paramHeader = document.createElement('div');
|
|
28
|
+
paramHeader.className = 'section-header';
|
|
29
|
+
paramHeader.innerHTML = '<b>Parameters</b>';
|
|
30
|
+
paramControls.appendChild(paramHeader);
|
|
31
|
+
|
|
32
|
+
// Create system controls section
|
|
33
|
+
const systemControls = document.createElement('div');
|
|
34
|
+
systemControls.id = 'system-controls';
|
|
35
|
+
systemControls.className = 'system-controls';
|
|
36
|
+
|
|
37
|
+
// Create status element
|
|
38
|
+
const statusElement = document.createElement('div');
|
|
39
|
+
statusElement.id = 'status-display';
|
|
40
|
+
statusElement.className = 'status-display';
|
|
41
|
+
systemControls.appendChild(statusElement);
|
|
42
|
+
updateStatus('Initializing...');
|
|
43
|
+
|
|
44
|
+
// Add sections to main container in the desired order
|
|
45
|
+
mainContainer.appendChild(paramControls);
|
|
46
|
+
mainContainer.appendChild(systemControls);
|
|
47
|
+
|
|
14
48
|
// Fetch initial parameter information from server
|
|
15
49
|
fetch('/init-data')
|
|
16
50
|
.then(response => response.json())
|
|
17
51
|
.then(data => {
|
|
18
52
|
paramInfo = data.params;
|
|
19
53
|
paramOrder = data.param_order;
|
|
54
|
+
updateThreshold = data.config.update_threshold;
|
|
20
55
|
|
|
21
56
|
// Initialize state from parameter info
|
|
22
57
|
for (const [name, param] of Object.entries(paramInfo)) {
|
|
@@ -25,23 +60,271 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
25
60
|
|
|
26
61
|
// Create UI controls for each parameter
|
|
27
62
|
createControls();
|
|
63
|
+
|
|
64
|
+
// Create system controls if horizontal layout
|
|
65
|
+
if (config.controlsPosition === 'left' || config.controlsPosition === 'right') {
|
|
66
|
+
createSystemControls(systemControls);
|
|
67
|
+
}
|
|
28
68
|
|
|
29
69
|
// Generate initial plot
|
|
30
70
|
updatePlot();
|
|
71
|
+
updateStatus('Ready!');
|
|
31
72
|
})
|
|
32
73
|
.catch(error => {
|
|
33
74
|
console.error('Error initializing viewer:', error);
|
|
75
|
+
updateStatus('Error initializing viewer');
|
|
34
76
|
});
|
|
35
77
|
});
|
|
36
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Create system controls (width and threshold)
|
|
81
|
+
*/
|
|
82
|
+
function createSystemControls(container) {
|
|
83
|
+
// Create controls width slider
|
|
84
|
+
const widthControl = createFloatController('controls_width', {
|
|
85
|
+
type: 'float',
|
|
86
|
+
value: config.controlsWidthPercent,
|
|
87
|
+
min: 10,
|
|
88
|
+
max: 50,
|
|
89
|
+
step: 1
|
|
90
|
+
});
|
|
91
|
+
widthControl.className = 'numeric-control system-control';
|
|
92
|
+
|
|
93
|
+
// Add label for width control
|
|
94
|
+
const widthLabel = document.createElement('span');
|
|
95
|
+
widthLabel.className = 'control-label';
|
|
96
|
+
widthLabel.textContent = 'Controls Width %';
|
|
97
|
+
|
|
98
|
+
const widthGroup = document.createElement('div');
|
|
99
|
+
widthGroup.className = 'control-group';
|
|
100
|
+
widthGroup.appendChild(widthLabel);
|
|
101
|
+
widthGroup.appendChild(widthControl);
|
|
102
|
+
|
|
103
|
+
// Create update threshold slider
|
|
104
|
+
const thresholdControl = createFloatController('update_threshold', {
|
|
105
|
+
type: 'float',
|
|
106
|
+
value: updateThreshold,
|
|
107
|
+
min: 0.1,
|
|
108
|
+
max: 10.0,
|
|
109
|
+
step: 0.1
|
|
110
|
+
});
|
|
111
|
+
thresholdControl.className = 'numeric-control system-control';
|
|
112
|
+
|
|
113
|
+
// Add label for threshold control
|
|
114
|
+
const thresholdLabel = document.createElement('span');
|
|
115
|
+
thresholdLabel.className = 'control-label';
|
|
116
|
+
thresholdLabel.textContent = 'Update Threshold';
|
|
117
|
+
|
|
118
|
+
const thresholdGroup = document.createElement('div');
|
|
119
|
+
thresholdGroup.className = 'control-group';
|
|
120
|
+
thresholdGroup.appendChild(thresholdLabel);
|
|
121
|
+
thresholdGroup.appendChild(thresholdControl);
|
|
122
|
+
|
|
123
|
+
// Create plot margin slider
|
|
124
|
+
const plotMarginControl = createFloatController('plot_margin', {
|
|
125
|
+
type: 'float',
|
|
126
|
+
value: config.plotMarginPercent,
|
|
127
|
+
min: 0,
|
|
128
|
+
max: 50,
|
|
129
|
+
step: 1
|
|
130
|
+
});
|
|
131
|
+
plotMarginControl.className = 'numeric-control system-control';
|
|
132
|
+
|
|
133
|
+
// Add label for margin control
|
|
134
|
+
const marginLabel = document.createElement('span');
|
|
135
|
+
marginLabel.className = 'control-label';
|
|
136
|
+
marginLabel.textContent = 'Plot Margin %';
|
|
137
|
+
|
|
138
|
+
const marginGroup = document.createElement('div');
|
|
139
|
+
marginGroup.className = 'control-group';
|
|
140
|
+
marginGroup.appendChild(marginLabel);
|
|
141
|
+
marginGroup.appendChild(plotMarginControl);
|
|
142
|
+
|
|
143
|
+
// Add custom event listeners
|
|
144
|
+
// Width Control Listeners
|
|
145
|
+
const widthSlider = widthControl.querySelector('input[type="range"]');
|
|
146
|
+
const widthInput = widthControl.querySelector('input[type="number"]');
|
|
147
|
+
|
|
148
|
+
widthSlider.addEventListener('input', function() { // Real-time update for number input
|
|
149
|
+
widthInput.value = this.value;
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
widthSlider.addEventListener('change', function() {
|
|
153
|
+
const width = parseFloat(this.value);
|
|
154
|
+
config.controlsWidthPercent = width;
|
|
155
|
+
|
|
156
|
+
// Update the root containers using querySelector for classes
|
|
157
|
+
const rootContainer = document.querySelector('.viewer-container');
|
|
158
|
+
const controlsContainer = document.querySelector('.controls-container'); // Select the outer div by class
|
|
159
|
+
const plotContainer = document.querySelector('.plot-container');
|
|
160
|
+
|
|
161
|
+
if (rootContainer && controlsContainer && plotContainer) {
|
|
162
|
+
if (config.controlsPosition === 'left' || config.controlsPosition === 'right') {
|
|
163
|
+
controlsContainer.style.width = `${width}%`;
|
|
164
|
+
plotContainer.style.width = `${100 - width}%`;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Update the slider to match
|
|
169
|
+
widthSlider.value = width;
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
// Threshold Control Listeners
|
|
173
|
+
const thresholdSlider = thresholdControl.querySelector('input[type="range"]');
|
|
174
|
+
const thresholdInput = thresholdControl.querySelector('input[type="number"]');
|
|
175
|
+
|
|
176
|
+
thresholdSlider.addEventListener('input', function() { // Real-time update for number input
|
|
177
|
+
thresholdInput.value = this.value;
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
thresholdSlider.addEventListener('change', function() {
|
|
181
|
+
updateThreshold = parseFloat(this.value);
|
|
182
|
+
thresholdInput.value = updateThreshold; // Ensure input matches final value
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Plot Margin Control Listeners
|
|
186
|
+
const marginSlider = plotMarginControl.querySelector('input[type="range"]');
|
|
187
|
+
const marginInput = plotMarginControl.querySelector('input[type="number"]');
|
|
188
|
+
const plotContainer = document.querySelector('.plot-container');
|
|
189
|
+
|
|
190
|
+
// Function to apply margin and adjust size of the plot image
|
|
191
|
+
function applyPlotMargin(marginPercent) {
|
|
192
|
+
const plotImage = document.getElementById('plot-image'); // Get the image element
|
|
193
|
+
if (plotImage) {
|
|
194
|
+
const effectiveMargin = parseFloat(marginPercent); // Ensure it's a number
|
|
195
|
+
// Apply margin to the image
|
|
196
|
+
plotImage.style.margin = `${effectiveMargin}%`;
|
|
197
|
+
// Adjust width and height to account for the margin
|
|
198
|
+
plotImage.style.width = `calc(100% - ${2 * effectiveMargin}%)`;
|
|
199
|
+
plotImage.style.height = `calc(100% - ${2 * effectiveMargin}%)`;
|
|
200
|
+
// Reset container padding just in case
|
|
201
|
+
if (plotContainer) {
|
|
202
|
+
plotContainer.style.padding = '0';
|
|
203
|
+
}
|
|
204
|
+
config.plotMarginPercent = effectiveMargin; // Update config
|
|
205
|
+
} else {
|
|
206
|
+
console.warn('Plot image element not found when applying margin.');
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
marginSlider.addEventListener('input', function() { // Real-time update for number input
|
|
211
|
+
marginInput.value = this.value;
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
marginSlider.addEventListener('change', function() {
|
|
215
|
+
const margin = parseFloat(this.value);
|
|
216
|
+
marginInput.value = margin; // Ensure input matches final value
|
|
217
|
+
applyPlotMargin(margin);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Add wheel event listener to plot container for margin control
|
|
221
|
+
if (plotContainer) {
|
|
222
|
+
plotContainer.addEventListener('wheel', function(event) {
|
|
223
|
+
event.preventDefault(); // Prevent page scrolling
|
|
224
|
+
|
|
225
|
+
const currentValue = parseFloat(marginSlider.value);
|
|
226
|
+
const step = parseFloat(marginSlider.step) || 1;
|
|
227
|
+
const min = parseFloat(marginSlider.min);
|
|
228
|
+
const max = parseFloat(marginSlider.max);
|
|
229
|
+
|
|
230
|
+
let newValue;
|
|
231
|
+
if (event.deltaY < 0) {
|
|
232
|
+
// Scrolling up (or zoom in) -> decrease margin
|
|
233
|
+
newValue = currentValue - step;
|
|
234
|
+
} else {
|
|
235
|
+
// Scrolling down (or zoom out) -> increase margin
|
|
236
|
+
newValue = currentValue + step;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Clamp the value within min/max bounds
|
|
240
|
+
newValue = Math.max(min, Math.min(max, newValue));
|
|
241
|
+
|
|
242
|
+
// Only update if the value actually changed
|
|
243
|
+
if (newValue !== currentValue) {
|
|
244
|
+
marginSlider.value = newValue;
|
|
245
|
+
marginInput.value = newValue;
|
|
246
|
+
applyPlotMargin(newValue);
|
|
247
|
+
}
|
|
248
|
+
}, { passive: false }); // Need passive: false to call preventDefault()
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Apply initial margin
|
|
252
|
+
applyPlotMargin(config.plotMarginPercent);
|
|
253
|
+
|
|
254
|
+
container.appendChild(widthGroup);
|
|
255
|
+
container.appendChild(thresholdGroup);
|
|
256
|
+
container.appendChild(marginGroup);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Create update threshold control
|
|
261
|
+
*/
|
|
262
|
+
function createUpdateThresholdControl() {
|
|
263
|
+
const container = document.createElement('div');
|
|
264
|
+
container.className = 'control-group';
|
|
265
|
+
|
|
266
|
+
const label = document.createElement('span');
|
|
267
|
+
label.className = 'control-label';
|
|
268
|
+
label.textContent = 'Update Threshold';
|
|
269
|
+
|
|
270
|
+
const input = document.createElement('input');
|
|
271
|
+
input.type = 'range';
|
|
272
|
+
input.min = '0.1';
|
|
273
|
+
input.max = '10.0';
|
|
274
|
+
input.step = '0.1';
|
|
275
|
+
input.value = updateThreshold;
|
|
276
|
+
input.className = 'update-threshold-slider';
|
|
277
|
+
|
|
278
|
+
input.addEventListener('change', function() {
|
|
279
|
+
updateThreshold = parseFloat(this.value);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
container.appendChild(label);
|
|
283
|
+
container.appendChild(input);
|
|
284
|
+
return container;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Update the status display
|
|
289
|
+
*/
|
|
290
|
+
function updateStatus(message) {
|
|
291
|
+
const statusElement = document.getElementById('status-display');
|
|
292
|
+
if (statusElement) {
|
|
293
|
+
statusElement.innerHTML = `<b>Syd Controls</b> <span class="status-message">Status: ${message}</span>`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Create and cache the slow loading image
|
|
299
|
+
*/
|
|
300
|
+
function createSlowLoadingImage() {
|
|
301
|
+
const canvas = document.createElement('canvas');
|
|
302
|
+
canvas.width = 1200;
|
|
303
|
+
canvas.height = 900;
|
|
304
|
+
const ctx = canvas.getContext('2d');
|
|
305
|
+
|
|
306
|
+
// Fill background
|
|
307
|
+
ctx.fillStyle = '#ffffff';
|
|
308
|
+
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
309
|
+
|
|
310
|
+
// Add loading text
|
|
311
|
+
ctx.fillStyle = '#000000';
|
|
312
|
+
ctx.font = 'bold 16px Arial';
|
|
313
|
+
ctx.textAlign = 'center';
|
|
314
|
+
ctx.textBaseline = 'middle';
|
|
315
|
+
ctx.fillText('waiting for next figure...', canvas.width/2, canvas.height/2);
|
|
316
|
+
|
|
317
|
+
return canvas.toDataURL();
|
|
318
|
+
}
|
|
319
|
+
|
|
37
320
|
/**
|
|
38
321
|
* Create UI controls based on parameter types
|
|
39
322
|
*/
|
|
40
323
|
function createControls() {
|
|
41
|
-
const
|
|
324
|
+
const paramControls = document.getElementById('parameter-controls');
|
|
42
325
|
|
|
43
|
-
// Clear any existing controls
|
|
44
|
-
|
|
326
|
+
// Clear any existing parameter controls
|
|
327
|
+
paramControls.innerHTML = '';
|
|
45
328
|
|
|
46
329
|
// Create controls for each parameter in the order specified by the viewer
|
|
47
330
|
paramOrder.forEach(name => {
|
|
@@ -56,7 +339,7 @@ function createControls() {
|
|
|
56
339
|
|
|
57
340
|
// Add to container
|
|
58
341
|
if (controlGroup) {
|
|
59
|
-
|
|
342
|
+
paramControls.appendChild(controlGroup);
|
|
60
343
|
}
|
|
61
344
|
});
|
|
62
345
|
}
|
|
@@ -186,6 +469,10 @@ function createIntegerControl(name, param) {
|
|
|
186
469
|
input.value = param.value;
|
|
187
470
|
|
|
188
471
|
// Add event listeners
|
|
472
|
+
slider.addEventListener('input', function() {
|
|
473
|
+
input.value = this.value;
|
|
474
|
+
});
|
|
475
|
+
|
|
189
476
|
slider.addEventListener('change', function() {
|
|
190
477
|
const value = parseInt(this.value, 10);
|
|
191
478
|
input.value = value;
|
|
@@ -198,7 +485,7 @@ function createIntegerControl(name, param) {
|
|
|
198
485
|
slider.value = value;
|
|
199
486
|
updateParameter(name, value);
|
|
200
487
|
} else {
|
|
201
|
-
this.value = state[name];
|
|
488
|
+
this.value = state[name];
|
|
202
489
|
}
|
|
203
490
|
});
|
|
204
491
|
|
|
@@ -211,6 +498,40 @@ function createIntegerControl(name, param) {
|
|
|
211
498
|
* Create float control with slider and number input
|
|
212
499
|
*/
|
|
213
500
|
function createFloatControl(name, param) {
|
|
501
|
+
// create a container with the slider and input
|
|
502
|
+
const container = createFloatController(name, param);
|
|
503
|
+
const slider = container.querySelector('input[type="range"]');
|
|
504
|
+
const input = container.querySelector('input[type="number"]');
|
|
505
|
+
|
|
506
|
+
// Add event listeners
|
|
507
|
+
slider.addEventListener('input', function() {
|
|
508
|
+
input.value = this.value;
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
slider.addEventListener('change', function() {
|
|
512
|
+
const value = parseFloat(this.value);
|
|
513
|
+
input.value = value;
|
|
514
|
+
updateParameter(name, value);
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
input.addEventListener('change', function() {
|
|
518
|
+
const value = parseFloat(this.value);
|
|
519
|
+
if (!isNaN(value) && value >= param.min && value <= param.max) {
|
|
520
|
+
slider.value = value;
|
|
521
|
+
updateParameter(name, value);
|
|
522
|
+
} else {
|
|
523
|
+
this.value = state[name]; // Revert to current state
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
|
|
527
|
+
return container;
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Create float object with slider and number input
|
|
532
|
+
* Without the elements specific to "parameters"
|
|
533
|
+
*/
|
|
534
|
+
function createFloatController(name, param) {
|
|
214
535
|
const container = document.createElement('div');
|
|
215
536
|
container.className = 'numeric-control';
|
|
216
537
|
|
|
@@ -232,23 +553,7 @@ function createFloatControl(name, param) {
|
|
|
232
553
|
input.step = param.step || 0.01;
|
|
233
554
|
input.value = param.value;
|
|
234
555
|
|
|
235
|
-
//
|
|
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
|
-
|
|
556
|
+
// return the container with the slider and input
|
|
252
557
|
container.appendChild(slider);
|
|
253
558
|
container.appendChild(input);
|
|
254
559
|
return container;
|
|
@@ -414,6 +719,34 @@ function createRangeControl(name, param, converter) {
|
|
|
414
719
|
maxInput.value = param.value[1];
|
|
415
720
|
|
|
416
721
|
// Add event listeners
|
|
722
|
+
// Input listeners for real-time updates of number inputs and gradient
|
|
723
|
+
minSlider.addEventListener('input', function() {
|
|
724
|
+
const minVal = converter(this.value);
|
|
725
|
+
const maxVal = converter(maxSlider.value);
|
|
726
|
+
if (minVal <= maxVal) {
|
|
727
|
+
minInput.value = minVal;
|
|
728
|
+
} else {
|
|
729
|
+
// Prevent slider crossing visually, update input to maxVal
|
|
730
|
+
this.value = maxVal;
|
|
731
|
+
minInput.value = maxVal;
|
|
732
|
+
}
|
|
733
|
+
updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
maxSlider.addEventListener('input', function() {
|
|
737
|
+
const minVal = converter(minSlider.value);
|
|
738
|
+
const maxVal = converter(this.value);
|
|
739
|
+
if (maxVal >= minVal) {
|
|
740
|
+
maxInput.value = maxVal;
|
|
741
|
+
} else {
|
|
742
|
+
// Prevent slider crossing visually, update input to minVal
|
|
743
|
+
this.value = minVal;
|
|
744
|
+
maxInput.value = minVal;
|
|
745
|
+
}
|
|
746
|
+
updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
|
|
747
|
+
});
|
|
748
|
+
|
|
749
|
+
// Change listeners for updating state and triggering backend calls
|
|
417
750
|
minSlider.addEventListener('change', function() {
|
|
418
751
|
const minVal = converter(this.value);
|
|
419
752
|
const maxVal = converter(maxSlider.value);
|
|
@@ -596,7 +929,9 @@ function updateParameter(name, value) {
|
|
|
596
929
|
if (isUpdating) {
|
|
597
930
|
return;
|
|
598
931
|
}
|
|
599
|
-
|
|
932
|
+
// Indicate status update
|
|
933
|
+
updateStatus('Updating ' + name + '...');
|
|
934
|
+
|
|
600
935
|
// Update local state
|
|
601
936
|
state[name] = value;
|
|
602
937
|
|
|
@@ -626,6 +961,9 @@ function updateParameter(name, value) {
|
|
|
626
961
|
.catch(error => {
|
|
627
962
|
console.error('Error:', error);
|
|
628
963
|
});
|
|
964
|
+
|
|
965
|
+
// Indicate status update
|
|
966
|
+
updateStatus('Ready!');
|
|
629
967
|
}
|
|
630
968
|
|
|
631
969
|
/**
|
|
@@ -802,11 +1140,27 @@ function formatLabel(name) {
|
|
|
802
1140
|
* Update the plot with current state
|
|
803
1141
|
*/
|
|
804
1142
|
function updatePlot() {
|
|
1143
|
+
const plotImage = document.getElementById('plot-image');
|
|
1144
|
+
if (!plotImage) return;
|
|
1145
|
+
|
|
1146
|
+
// Clear any existing loading timeout
|
|
1147
|
+
if (loadingTimeout) {
|
|
1148
|
+
clearTimeout(loadingTimeout);
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
// Show loading state after threshold
|
|
1152
|
+
loadingTimeout = setTimeout(() => {
|
|
1153
|
+
// Create slow loading image if not cached
|
|
1154
|
+
if (!slowLoadingImage) {
|
|
1155
|
+
slowLoadingImage = createSlowLoadingImage();
|
|
1156
|
+
}
|
|
1157
|
+
plotImage.src = slowLoadingImage;
|
|
1158
|
+
plotImage.style.opacity = '0.5';
|
|
1159
|
+
}, updateThreshold * 1000);
|
|
1160
|
+
|
|
805
1161
|
// Build query string from state
|
|
806
1162
|
const queryParams = new URLSearchParams();
|
|
807
|
-
|
|
808
1163
|
for (const [name, value] of Object.entries(state)) {
|
|
809
|
-
// Handle arrays and special types by serializing to JSON
|
|
810
1164
|
if (Array.isArray(value) || typeof value === 'object') {
|
|
811
1165
|
queryParams.append(name, JSON.stringify(value));
|
|
812
1166
|
} else {
|
|
@@ -816,16 +1170,26 @@ function updatePlot() {
|
|
|
816
1170
|
|
|
817
1171
|
// Set the image source to the plot endpoint with parameters
|
|
818
1172
|
const url = `/plot?${queryParams.toString()}`;
|
|
819
|
-
const plotImage = document.getElementById('plot-image');
|
|
820
|
-
|
|
821
|
-
// Show loading indicator
|
|
822
|
-
plotImage.style.opacity = 0.5;
|
|
823
1173
|
|
|
824
1174
|
// Create a new image object
|
|
825
1175
|
const newImage = new Image();
|
|
826
1176
|
newImage.onload = function() {
|
|
1177
|
+
// Clear loading timeout
|
|
1178
|
+
if (loadingTimeout) {
|
|
1179
|
+
clearTimeout(loadingTimeout);
|
|
1180
|
+
loadingTimeout = null;
|
|
1181
|
+
}
|
|
827
1182
|
plotImage.src = url;
|
|
828
1183
|
plotImage.style.opacity = 1;
|
|
829
1184
|
};
|
|
1185
|
+
newImage.onerror = function() {
|
|
1186
|
+
// Clear loading timeout
|
|
1187
|
+
if (loadingTimeout) {
|
|
1188
|
+
clearTimeout(loadingTimeout);
|
|
1189
|
+
loadingTimeout = null;
|
|
1190
|
+
}
|
|
1191
|
+
updateStatus('Error loading plot');
|
|
1192
|
+
plotImage.style.opacity = 1;
|
|
1193
|
+
};
|
|
830
1194
|
newImage.src = url;
|
|
831
1195
|
}
|
|
@@ -39,7 +39,6 @@ class NotebookDeployer:
|
|
|
39
39
|
viewer: Viewer,
|
|
40
40
|
controls_position: Literal["left", "top", "right", "bottom"] = "left",
|
|
41
41
|
controls_width_percent: int = 20,
|
|
42
|
-
continuous: bool = False,
|
|
43
42
|
suppress_warnings: bool = True,
|
|
44
43
|
update_threshold: float = 1.0,
|
|
45
44
|
):
|
|
@@ -49,7 +48,6 @@ class NotebookDeployer:
|
|
|
49
48
|
self._updating = False # Flag to check circular updates
|
|
50
49
|
self.controls_position = controls_position
|
|
51
50
|
self.controls_width_percent = controls_width_percent
|
|
52
|
-
self.continuous = continuous
|
|
53
51
|
|
|
54
52
|
# Initialize containers
|
|
55
53
|
self.backend_type = get_backend_type()
|
|
@@ -117,7 +115,7 @@ class NotebookDeployer:
|
|
|
117
115
|
def build_components(self) -> None:
|
|
118
116
|
"""Create widget instances for all parameters and equip callbacks."""
|
|
119
117
|
for name, param in self.viewer.parameters.items():
|
|
120
|
-
widget = create_widget(param
|
|
118
|
+
widget = create_widget(param)
|
|
121
119
|
self.components[name] = widget
|
|
122
120
|
callback = lambda _, n=name: self.handle_component_engagement(n)
|
|
123
121
|
widget.observe(callback)
|