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.
@@ -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 controlsContainer = document.getElementById('controls-container');
324
+ const paramControls = document.getElementById('parameter-controls');
42
325
 
43
- // Clear any existing controls
44
- controlsContainer.innerHTML = '';
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
- controlsContainer.appendChild(controlGroup);
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]; // Revert to current state
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
- // 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
-
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, continuous=self.continuous)
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)