syd 0.2.0__py3-none-any.whl → 1.0.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.
@@ -60,16 +60,16 @@ body {
60
60
  #controls-container {
61
61
  display: grid;
62
62
  grid-template-columns: 1fr;
63
- gap: 20px;
63
+ gap: 10px;
64
64
  }
65
65
 
66
66
  /* Control groups */
67
67
  .control-group {
68
68
  display: flex;
69
69
  flex-direction: column;
70
- padding: 15px;
70
+ padding: 10px;
71
71
  border: 1px solid #eee;
72
- border-radius: 8px;
72
+ border-radius: 4px;
73
73
  background-color: white;
74
74
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
75
75
  }
@@ -95,7 +95,6 @@ input[type="number"] {
95
95
  /* Range inputs */
96
96
  input[type="range"] {
97
97
  width: 100%;
98
- -webkit-appearance: none;
99
98
  height: 6px;
100
99
  background: #ddd;
101
100
  border-radius: 3px;
@@ -205,29 +204,71 @@ button.active {
205
204
 
206
205
  .range-slider-container {
207
206
  position: relative;
208
- height: 30px;
209
207
  margin: 10px 0;
208
+ background: linear-gradient(to right,
209
+ #ddd 0%,
210
+ #ddd var(--min-pos, 0%),
211
+ #3f51b5 var(--min-pos, 0%),
212
+ #3f51b5 var(--max-pos, 100%),
213
+ #ddd var(--max-pos, 100%),
214
+ #ddd 100%);
215
+ border-radius: 3px;
216
+ height: 18px;
210
217
  }
211
218
 
212
219
  .range-slider {
213
220
  position: absolute;
214
- top: 0;
221
+ top: 50%;
222
+ transform: translateY(-50%);
223
+ left: 0;
215
224
  width: 100%;
216
225
  pointer-events: none;
217
- opacity: 0.7;
226
+ -webkit-appearance: none;
227
+ appearance: none;
228
+ background: transparent;
229
+ cursor: pointer;
230
+ margin: 0;
231
+ height: 18px;
232
+ }
233
+
234
+ /* Transparent Track for Webkit */
235
+ .range-slider::-webkit-slider-runnable-track {
236
+ background: transparent;
237
+ border: none;
238
+ border-radius: 3px;
239
+ }
240
+
241
+ /* Transparent Track for Firefox */
242
+ .range-slider::-moz-range-track {
243
+ background: transparent;
244
+ border: none;
245
+ border-radius: 3px;
218
246
  }
219
247
 
220
248
  .range-slider.active {
221
- opacity: 1;
222
249
  z-index: 2;
223
250
  }
224
251
 
225
252
  .range-slider::-webkit-slider-thumb {
226
253
  pointer-events: auto;
254
+ -webkit-appearance: none;
255
+ appearance: none;
256
+ width: 18px;
257
+ height: 18px;
258
+ border-radius: 50%;
259
+ background: #3f51b5;
260
+ cursor: pointer;
261
+ border: 1px solid #2c3e90;
227
262
  }
228
263
 
229
264
  .range-slider::-moz-range-thumb {
230
265
  pointer-events: auto;
266
+ width: 18px;
267
+ height: 18px;
268
+ border-radius: 50%;
269
+ background: #3f51b5;
270
+ cursor: pointer;
271
+ border: 1px solid #2c3e90;
231
272
  }
232
273
 
233
274
  .min-slider {
@@ -236,11 +277,4 @@ button.active {
236
277
 
237
278
  .max-slider {
238
279
  z-index: 2;
239
- }
240
-
241
- .range-display {
242
- text-align: center;
243
- font-size: 13px;
244
- margin-top: 5px;
245
- color: #666;
246
- }
280
+ }
@@ -1,23 +1,14 @@
1
- /**
2
- * Syd Viewer JavaScript for Flask deployment
3
- * Handles dynamic creation of UI components and interaction with the Flask backend
4
- */
5
-
6
- // State object to store current values
7
1
  let state = {};
8
2
  let paramInfo = {};
3
+ let paramOrder = [];
4
+ let isUpdating = false;
9
5
 
10
6
  // Config object parsed from HTML data attributes
11
7
  const config = {
12
- figureWidth: parseFloat(document.getElementById('viewer-config').dataset.figureWidth || 8.0),
13
- figureHeight: parseFloat(document.getElementById('viewer-config').dataset.figureHeight || 6.0),
14
8
  controlsPosition: document.getElementById('viewer-config').dataset.controlsPosition || 'left',
15
- controlsWidthPercent: parseInt(document.getElementById('viewer-config').dataset.controlsWidthPercent || 30)
9
+ controlsWidthPercent: parseInt(document.getElementById('viewer-config').dataset.controlsWidthPercent || 20)
16
10
  };
17
11
 
18
- // Track whether we're currently in an update operation
19
- let isUpdating = false;
20
-
21
12
  // Initialize the viewer
22
13
  document.addEventListener('DOMContentLoaded', function() {
23
14
  // Fetch initial parameter information from server
@@ -25,6 +16,7 @@ document.addEventListener('DOMContentLoaded', function() {
25
16
  .then(response => response.json())
26
17
  .then(data => {
27
18
  paramInfo = data.params;
19
+ paramOrder = data.param_order;
28
20
 
29
21
  // Initialize state from parameter info
30
22
  for (const [name, param] of Object.entries(paramInfo)) {
@@ -51,8 +43,14 @@ function createControls() {
51
43
  // Clear any existing controls
52
44
  controlsContainer.innerHTML = '';
53
45
 
54
- // Create controls for each parameter
55
- for (const [name, param] of Object.entries(paramInfo)) {
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
+
56
54
  // Create control group
57
55
  const controlGroup = createControlGroup(name, param);
58
56
 
@@ -60,7 +58,7 @@ function createControls() {
60
58
  if (controlGroup) {
61
59
  controlsContainer.appendChild(controlGroup);
62
60
  }
63
- }
61
+ });
64
62
  }
65
63
 
66
64
  /**
@@ -188,7 +186,7 @@ function createIntegerControl(name, param) {
188
186
  input.value = param.value;
189
187
 
190
188
  // Add event listeners
191
- slider.addEventListener('input', function() {
189
+ slider.addEventListener('change', function() {
192
190
  const value = parseInt(this.value, 10);
193
191
  input.value = value;
194
192
  updateParameter(name, value);
@@ -235,7 +233,7 @@ function createFloatControl(name, param) {
235
233
  input.value = param.value;
236
234
 
237
235
  // Add event listeners
238
- slider.addEventListener('input', function() {
236
+ slider.addEventListener('change', function() {
239
237
  const value = parseFloat(this.value);
240
238
  input.value = value;
241
239
  updateParameter(name, value);
@@ -378,7 +376,7 @@ function createRangeControl(name, param, converter) {
378
376
  minInput.className = 'range-input';
379
377
  minInput.min = param.min;
380
378
  minInput.max = param.max;
381
- minInput.step = param.step || 1;
379
+ minInput.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
382
380
  minInput.value = param.value[0];
383
381
 
384
382
  // Create slider container
@@ -392,7 +390,7 @@ function createRangeControl(name, param, converter) {
392
390
  minSlider.className = 'range-slider min-slider';
393
391
  minSlider.min = param.min;
394
392
  minSlider.max = param.max;
395
- minSlider.step = param.step || 1;
393
+ minSlider.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
396
394
  minSlider.value = param.value[0];
397
395
 
398
396
  // Create max slider
@@ -402,7 +400,7 @@ function createRangeControl(name, param, converter) {
402
400
  maxSlider.className = 'range-slider max-slider';
403
401
  maxSlider.min = param.min;
404
402
  maxSlider.max = param.max;
405
- maxSlider.step = param.step || 1;
403
+ maxSlider.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
406
404
  maxSlider.value = param.value[1];
407
405
 
408
406
  // Create max input
@@ -412,41 +410,39 @@ function createRangeControl(name, param, converter) {
412
410
  maxInput.className = 'range-input';
413
411
  maxInput.min = param.min;
414
412
  maxInput.max = param.max;
415
- maxInput.step = param.step || 1;
413
+ maxInput.step = param.step || (converter === parseInt ? 1 : 0.01); // Default step
416
414
  maxInput.value = param.value[1];
417
415
 
418
- // Range display
419
- const rangeDisplay = document.createElement('div');
420
- rangeDisplay.className = 'range-display';
421
- rangeDisplay.id = `${name}-range-display`;
422
- rangeDisplay.textContent = `Range: ${param.value[0]} - ${param.value[1]}`;
423
-
424
416
  // Add event listeners
425
- minSlider.addEventListener('input', function() {
417
+ minSlider.addEventListener('change', function() {
426
418
  const minVal = converter(this.value);
427
419
  const maxVal = converter(maxSlider.value);
428
420
 
429
421
  if (minVal <= maxVal) {
430
422
  state[name] = [minVal, maxVal];
431
423
  minInput.value = minVal;
432
- updateRangeDisplay(rangeDisplay, minVal, maxVal);
424
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
433
425
  updateParameter(name, [minVal, maxVal]);
434
426
  } else {
435
- this.value = maxVal;
427
+ this.value = maxVal; // Snap to maxVal if crossing
428
+ minInput.value = maxVal; // Also update input
429
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
436
430
  }
437
431
  });
438
432
 
439
- maxSlider.addEventListener('input', function() {
433
+ maxSlider.addEventListener('change', function() {
440
434
  const minVal = converter(minSlider.value);
441
435
  const maxVal = converter(this.value);
442
436
 
443
437
  if (maxVal >= minVal) {
444
438
  state[name] = [minVal, maxVal];
445
439
  maxInput.value = maxVal;
446
- updateRangeDisplay(rangeDisplay, minVal, maxVal);
440
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
447
441
  updateParameter(name, [minVal, maxVal]);
448
442
  } else {
449
- this.value = minVal;
443
+ this.value = minVal; // Snap to minVal if crossing
444
+ maxInput.value = minVal; // Also update input
445
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
450
446
  }
451
447
  });
452
448
 
@@ -457,10 +453,13 @@ function createRangeControl(name, param, converter) {
457
453
  if (!isNaN(minVal) && minVal >= param.min && minVal <= maxVal) {
458
454
  state[name] = [minVal, maxVal];
459
455
  minSlider.value = minVal;
460
- updateRangeDisplay(rangeDisplay, minVal, maxVal);
456
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
461
457
  updateParameter(name, [minVal, maxVal]);
462
458
  } else {
463
- this.value = state[name][0];
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);
464
463
  }
465
464
  });
466
465
 
@@ -471,10 +470,13 @@ function createRangeControl(name, param, converter) {
471
470
  if (!isNaN(maxVal) && maxVal <= param.max && maxVal >= minVal) {
472
471
  state[name] = [minVal, maxVal];
473
472
  maxSlider.value = maxVal;
474
- updateRangeDisplay(rangeDisplay, minVal, maxVal);
473
+ updateSliderGradient(minSlider, maxSlider, sliderContainer); // Update gradient
475
474
  updateParameter(name, [minVal, maxVal]);
476
475
  } else {
476
+ // Revert input value and ensure gradient matches state
477
477
  this.value = state[name][1];
478
+ maxSlider.value = state[name][1];
479
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
478
480
  }
479
481
  });
480
482
 
@@ -487,18 +489,13 @@ function createRangeControl(name, param, converter) {
487
489
 
488
490
  container.appendChild(inputsContainer);
489
491
  container.appendChild(sliderContainer);
490
- container.appendChild(rangeDisplay);
492
+
493
+ // Set initial gradient state
494
+ updateSliderGradient(minSlider, maxSlider, sliderContainer);
491
495
 
492
496
  return container;
493
497
  }
494
498
 
495
- /**
496
- * Update range display text
497
- */
498
- function updateRangeDisplay(displayElement, min, max) {
499
- displayElement.textContent = `Range: ${min} - ${max}`;
500
- }
501
-
502
499
  /**
503
500
  * Create unbounded integer control
504
501
  */
@@ -576,7 +573,7 @@ function createButtonControl(name, param) {
576
573
  console.error('Error:', data.error);
577
574
  } else {
578
575
  // Update state with any changes from callbacks
579
- updateStateFromServer(data.state);
576
+ updateStateFromServer(data.state, data.params);
580
577
  // Update plot if needed
581
578
  updatePlot();
582
579
  }
@@ -611,7 +608,8 @@ function updateParameter(name, value) {
611
608
  },
612
609
  body: JSON.stringify({
613
610
  name: name,
614
- value: value
611
+ value: value,
612
+ action: false
615
613
  }),
616
614
  })
617
615
  .then(response => response.json())
@@ -620,7 +618,7 @@ function updateParameter(name, value) {
620
618
  console.error('Error:', data.error);
621
619
  } else {
622
620
  // Update state with any changes from callbacks
623
- updateStateFromServer(data.state);
621
+ updateStateFromServer(data.state, data.params);
624
622
  // Update plot
625
623
  updatePlot();
626
624
  }
@@ -633,16 +631,16 @@ function updateParameter(name, value) {
633
631
  /**
634
632
  * Update local state from server response
635
633
  */
636
- function updateStateFromServer(serverState) {
634
+ function updateStateFromServer(serverState, serverParamInfo) {
637
635
  // Set updating flag to prevent recursive updates
638
636
  isUpdating = true;
639
637
 
640
638
  try {
641
639
  // Update any parameters that changed due to callbacks
642
640
  for (const [name, value] of Object.entries(serverState)) {
643
- if (JSON.stringify(state[name]) !== JSON.stringify(value)) {
641
+ if (JSON.stringify(state[name]) !== JSON.stringify(value) || JSON.stringify(paramInfo[name]) !== JSON.stringify(serverParamInfo[name])) {
644
642
  state[name] = value;
645
- updateControlValue(name, value);
643
+ updateControlValue(name, value, serverParamInfo[name]);
646
644
  }
647
645
  }
648
646
  } finally {
@@ -654,11 +652,9 @@ function updateStateFromServer(serverState) {
654
652
  /**
655
653
  * Update a control's value in the UI
656
654
  */
657
- function updateControlValue(name, value) {
655
+ function updateControlValue(name, value, param) {
658
656
  if (!paramInfo[name]) return;
659
-
660
- const param = paramInfo[name];
661
-
657
+
662
658
  switch (param.type) {
663
659
  case 'text':
664
660
  document.getElementById(`${name}-input`).value = value;
@@ -668,29 +664,97 @@ function updateControlValue(name, value) {
668
664
  break;
669
665
  case 'integer':
670
666
  case 'float':
671
- document.getElementById(`${name}-slider`).value = value;
672
- document.getElementById(`${name}-input`).value = value;
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;
673
677
  break;
674
678
  case 'selection':
675
- document.getElementById(`${name}-select`).value = value;
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
+ }
676
705
  break;
677
706
  case 'multiple-selection':
678
- const select = document.getElementById(`${name}-select`);
679
- if (select) {
680
- Array.from(select.options).forEach(option => {
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 => {
681
723
  option.selected = value.includes(option.value);
682
724
  });
683
725
  }
684
726
  break;
685
727
  case 'integer-range':
686
728
  case 'float-range':
687
- document.getElementById(`${name}-min-slider`).value = value[0];
688
- document.getElementById(`${name}-max-slider`).value = value[1];
689
- document.getElementById(`${name}-min-input`).value = value[0];
690
- document.getElementById(`${name}-max-input`).value = value[1];
691
- const display = document.getElementById(`${name}-range-display`);
692
- if (display) {
693
- updateRangeDisplay(display, value[0], value[1]);
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}`);
694
758
  }
695
759
  break;
696
760
  case 'unbounded-integer':
@@ -700,6 +764,29 @@ function updateControlValue(name, value) {
700
764
  }
701
765
  }
702
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
+
703
790
  /**
704
791
  * Format parameter name as a label (capitalize each word)
705
792
  */
@@ -1,7 +1,7 @@
1
1
  <!DOCTYPE html>
2
2
  <html>
3
3
  <head>
4
- <title>Syd Viewer</title>
4
+ <title>{{ title }}</title>
5
5
  <meta charset="UTF-8">
6
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
7
  <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}">
@@ -1 +0,0 @@
1
- from .deployer import NotebookDeployer