syd 1.0.2__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 CHANGED
@@ -1,11 +1,3 @@
1
- from typing import Callable, Optional
2
- from .viewer import Viewer
1
+ __version__ = "1.1.0"
3
2
 
4
- __version__ = "1.0.2"
5
-
6
-
7
- def make_viewer(plot_func: Optional[Callable] = None):
8
- viewer = Viewer()
9
- if plot_func is not None:
10
- viewer.set_plot(plot_func)
11
- return viewer
3
+ from .viewer import make_viewer, Viewer
@@ -48,7 +48,7 @@ class FlaskLayoutConfig:
48
48
  """Configuration for the Flask viewer layout."""
49
49
 
50
50
  controls_position: str = "left" # Options are: 'left', 'top', 'right', 'bottom'
51
- controls_width_percent: int = 30
51
+ controls_width_percent: int = 15
52
52
 
53
53
  def __post_init__(self):
54
54
  valid_positions = ["left", "top", "right", "bottom"]
@@ -73,12 +73,13 @@ class FlaskDeployer:
73
73
  viewer: Viewer,
74
74
  controls_position: str = "left",
75
75
  fig_dpi: int = 300,
76
- controls_width_percent: int = 20,
76
+ controls_width_percent: int = 15,
77
77
  suppress_warnings: bool = True,
78
78
  debug: bool = False,
79
79
  host: str = "127.0.0.1",
80
80
  port: Optional[int] = None,
81
81
  open_browser: bool = True,
82
+ update_threshold: float = 1.0,
82
83
  ):
83
84
  """
84
85
  Initialize the Flask deployer.
@@ -107,10 +108,13 @@ class FlaskDeployer:
107
108
  Port for the server. If None, finds an available port (default: None).
108
109
  open_browser : bool, optional
109
110
  Whether to open the web application in a browser tab (default: True).
111
+ update_threshold : float, optional
112
+ Time in seconds to wait before showing the loading indicator (default: 1.0)
110
113
  """
111
114
  self.viewer = viewer
112
115
  self.suppress_warnings = suppress_warnings
113
116
  self._updating = False # Flag to check circular updates
117
+ self.update_threshold = update_threshold # Store update threshold
114
118
 
115
119
  # Flask specific configurations
116
120
  self.config = FlaskLayoutConfig(
@@ -167,12 +171,17 @@ class FlaskDeployer:
167
171
  }
168
172
  # Get the order of parameters
169
173
  param_order = list(self.viewer.parameters.keys())
170
- # Also include the initial state
174
+ # Also include the initial state and configuration
171
175
  return jsonify(
172
176
  {
173
177
  "params": param_info,
174
178
  "param_order": param_order,
175
179
  "state": self.viewer.state,
180
+ "config": {
181
+ "controls_position": self.config.controls_position,
182
+ "controls_width_percent": self.config.controls_width_percent,
183
+ "update_threshold": self.update_threshold,
184
+ },
176
185
  }
177
186
  )
178
187
 
@@ -60,14 +60,14 @@ body {
60
60
  #controls-container {
61
61
  display: grid;
62
62
  grid-template-columns: 1fr;
63
- gap: 10px;
63
+ gap: 5px;
64
64
  }
65
65
 
66
66
  /* Control groups */
67
67
  .control-group {
68
68
  display: flex;
69
69
  flex-direction: column;
70
- padding: 10px;
70
+ padding: 7px;
71
71
  border: 1px solid #eee;
72
72
  border-radius: 4px;
73
73
  background-color: white;
@@ -76,7 +76,7 @@ body {
76
76
 
77
77
  .control-label {
78
78
  font-weight: 600;
79
- margin-bottom: 10px;
79
+ margin-bottom: 0px;
80
80
  color: #333;
81
81
  text-transform: capitalize;
82
82
  }
@@ -87,7 +87,7 @@ input[type="number"] {
87
87
  padding: 8px 12px;
88
88
  border: 1px solid #ddd;
89
89
  border-radius: 4px;
90
- font-size: 14px;
90
+ font-size: 12px;
91
91
  width: 100%;
92
92
  box-sizing: border-box;
93
93
  }
@@ -95,11 +95,11 @@ input[type="number"] {
95
95
  /* Range inputs */
96
96
  input[type="range"] {
97
97
  width: 100%;
98
- height: 6px;
98
+ height: 12px;
99
99
  background: #ddd;
100
100
  border-radius: 3px;
101
101
  outline: none;
102
- margin: 10px 0;
102
+ margin: 5px 0;
103
103
  }
104
104
 
105
105
  input[type="range"]::-webkit-slider-thumb {
@@ -194,7 +194,7 @@ button.active {
194
194
  .range-inputs {
195
195
  display: flex;
196
196
  justify-content: space-between;
197
- margin-bottom: 10px;
197
+ margin-bottom: 5px;
198
198
  }
199
199
 
200
200
  .range-input {
@@ -204,7 +204,7 @@ button.active {
204
204
 
205
205
  .range-slider-container {
206
206
  position: relative;
207
- margin: 10px 0;
207
+ margin: 5px 0;
208
208
  background: linear-gradient(to right,
209
209
  #ddd 0%,
210
210
  #ddd var(--min-pos, 0%),
@@ -277,4 +277,22 @@ button.active {
277
277
 
278
278
  .max-slider {
279
279
  z-index: 2;
280
+ }
281
+
282
+ #status-display {
283
+ margin-top: 10px;
284
+ margin-bottom: 3px;
285
+ padding: 8px;
286
+ border-radius: 4px;
287
+ background-color: #ffffff;
288
+ border: 1px solid #e5e7eb;
289
+ }
290
+
291
+ .status-message {
292
+ background-color: #e0e0e0;
293
+ color: #000;
294
+ padding: 2px 6px;
295
+ border-radius: 4px;
296
+ font-size: 90%;
297
+ margin-left: 8px;
280
298
  }
@@ -0,0 +1,90 @@
1
+ #viewer-container {
2
+ width: 100%;
3
+ max-width: 100%;
4
+ margin: 0;
5
+ padding: 0;
6
+ box-sizing: border-box;
7
+ display: flex;
8
+ }
9
+
10
+ #controls-container {
11
+ padding: 15px;
12
+ box-sizing: border-box;
13
+ overflow-y: auto;
14
+ max-height: 100vh;
15
+ }
16
+
17
+ #plot-container {
18
+ padding: 15px;
19
+ box-sizing: border-box;
20
+ display: flex;
21
+ align-items: center;
22
+ justify-content: center;
23
+ }
24
+
25
+ #plot-container img {
26
+ max-width: 100%;
27
+ height: auto;
28
+ }
29
+
30
+ .system-controls {
31
+ margin: 10px 0px;
32
+ padding: 10px;
33
+ background-color: #ffffff;
34
+ border: 1px solid #e5e7eb;
35
+ border-radius: 4px;
36
+ }
37
+
38
+ .parameter-controls {
39
+ padding: 10px;
40
+ background-color: #ffffff;
41
+ border: 1px solid #e5e7eb;
42
+ border-radius: 4px;
43
+ }
44
+
45
+ .section-header {
46
+ margin-bottom: 15px;
47
+ font-size: 16px;
48
+ }
49
+
50
+ /* Style all numeric controls consistently */
51
+ .numeric-control {
52
+ display: flex;
53
+ align-items: center;
54
+ }
55
+
56
+ .numeric-control input[type="range"] {
57
+ flex: 1;
58
+ -webkit-appearance: none;
59
+ appearance: none;
60
+ height: 6px;
61
+ background: #ddd;
62
+ outline: none;
63
+ border-radius: 3px;
64
+ }
65
+
66
+ .numeric-control input[type="range"]::-webkit-slider-thumb {
67
+ -webkit-appearance: none;
68
+ appearance: none;
69
+ width: 16px;
70
+ height: 16px;
71
+ background: #4a90e2;
72
+ cursor: pointer;
73
+ border-radius: 50%;
74
+ }
75
+
76
+ .numeric-control input[type="range"]::-moz-range-thumb {
77
+ width: 16px;
78
+ height: 16px;
79
+ background: #4a90e2;
80
+ cursor: pointer;
81
+ border-radius: 50%;
82
+ border: none;
83
+ }
84
+
85
+ .numeric-control input[type="number"] {
86
+ width: 60px;
87
+ padding: 1px 1px;
88
+ border: 1px solid #ddd;
89
+ border-radius: 1px;
90
+ }
@@ -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)
@@ -36,13 +36,15 @@ class BaseWidget(Generic[T, W], ABC):
36
36
  def __init__(
37
37
  self,
38
38
  parameter: T,
39
- continuous: bool = False,
40
39
  width: str = "auto",
41
40
  margin: str = "3px 0px",
42
41
  description_width: str = "initial",
43
42
  ):
44
43
  self._widget = self._create_widget(
45
- parameter, continuous, width, margin, description_width
44
+ parameter,
45
+ width,
46
+ margin,
47
+ description_width,
46
48
  )
47
49
  self._updating = False # Flag to prevent circular updates
48
50
  # List of callbacks to remember for quick disabling/enabling
@@ -52,7 +54,6 @@ class BaseWidget(Generic[T, W], ABC):
52
54
  def _create_widget(
53
55
  self,
54
56
  parameter: T,
55
- continuous: bool,
56
57
  width: str = "auto",
57
58
  margin: str = "3px 0px",
58
59
  description_width: str = "initial",
@@ -123,7 +124,6 @@ class TextWidget(BaseWidget[TextParameter, widgets.Text]):
123
124
  def _create_widget(
124
125
  self,
125
126
  parameter: TextParameter,
126
- continuous: bool,
127
127
  width: str = "auto",
128
128
  margin: str = "3px 0px",
129
129
  description_width: str = "initial",
@@ -131,7 +131,7 @@ class TextWidget(BaseWidget[TextParameter, widgets.Text]):
131
131
  return widgets.Text(
132
132
  value=parameter.value,
133
133
  description=parameter.name,
134
- continuous=continuous,
134
+ continuous=False,
135
135
  layout=widgets.Layout(width=width, margin=margin),
136
136
  style={"description_width": description_width},
137
137
  )
@@ -143,7 +143,6 @@ class BooleanWidget(BaseWidget[BooleanParameter, widgets.ToggleButton]):
143
143
  def _create_widget(
144
144
  self,
145
145
  parameter: BooleanParameter,
146
- continuous: bool,
147
146
  width: str = "auto",
148
147
  margin: str = "3px 0px",
149
148
  description_width: str = "initial",
@@ -162,7 +161,6 @@ class SelectionWidget(BaseWidget[SelectionParameter, widgets.Dropdown]):
162
161
  def _create_widget(
163
162
  self,
164
163
  parameter: SelectionParameter,
165
- continuous: bool,
166
164
  width: str = "auto",
167
165
  margin: str = "3px 0px",
168
166
  description_width: str = "initial",
@@ -198,7 +196,6 @@ class MultipleSelectionWidget(
198
196
  def _create_widget(
199
197
  self,
200
198
  parameter: MultipleSelectionParameter,
201
- continuous: bool,
202
199
  width: str = "auto",
203
200
  margin: str = "3px 0px",
204
201
  description_width: str = "initial",
@@ -235,7 +232,6 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
235
232
  def _create_widget(
236
233
  self,
237
234
  parameter: IntegerParameter,
238
- continuous: bool,
239
235
  width: str = "auto",
240
236
  margin: str = "3px 0px",
241
237
  description_width: str = "initial",
@@ -247,7 +243,7 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
247
243
  max=parameter.max,
248
244
  step=1,
249
245
  description=parameter.name,
250
- continuous_update=continuous,
246
+ continuous_update=False,
251
247
  style={"description_width": description_width},
252
248
  layout=widgets.Layout(width=width, margin=margin),
253
249
  )
@@ -264,6 +260,10 @@ class IntegerWidget(BaseWidget[IntegerParameter, widgets.IntSlider]):
264
260
  def extra_updates_from_parameter(self, parameter: IntegerParameter) -> None:
265
261
  """Update the widget attributes from the parameter."""
266
262
  current_value = self._widget.value
263
+ if parameter.min > self._widget.max:
264
+ self._widget.max = parameter.min + 1
265
+ if parameter.max < self._widget.min:
266
+ self._widget.min = parameter.max - 1
267
267
  self._widget.min = parameter.min
268
268
  self._widget.max = parameter.max
269
269
  self.value = max(parameter.min, min(parameter.max, current_value))
@@ -275,7 +275,6 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
275
275
  def _create_widget(
276
276
  self,
277
277
  parameter: FloatParameter,
278
- continuous: bool,
279
278
  width: str = "auto",
280
279
  margin: str = "3px 0px",
281
280
  description_width: str = "initial",
@@ -287,7 +286,7 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
287
286
  max=parameter.max,
288
287
  step=parameter.step,
289
288
  description=parameter.name,
290
- continuous_update=continuous,
289
+ continuous_update=False,
291
290
  style={"description_width": description_width},
292
291
  layout=widgets.Layout(width=width, margin=margin),
293
292
  )
@@ -305,6 +304,10 @@ class FloatWidget(BaseWidget[FloatParameter, widgets.FloatSlider]):
305
304
  def extra_updates_from_parameter(self, parameter: FloatParameter) -> None:
306
305
  """Update the widget attributes from the parameter."""
307
306
  current_value = self._widget.value
307
+ if parameter.min > self._widget.max:
308
+ self._widget.max = parameter.min + 1
309
+ if parameter.max < self._widget.min:
310
+ self._widget.min = parameter.max - 1
308
311
  self._widget.min = parameter.min
309
312
  self._widget.max = parameter.max
310
313
  self._widget.step = parameter.step
@@ -317,7 +320,6 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
317
320
  def _create_widget(
318
321
  self,
319
322
  parameter: IntegerRangeParameter,
320
- continuous: bool,
321
323
  width: str = "auto",
322
324
  margin: str = "3px 0px",
323
325
  description_width: str = "initial",
@@ -330,7 +332,7 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
330
332
  max=parameter.max,
331
333
  step=1,
332
334
  description=parameter.name,
333
- continuous_update=continuous,
335
+ continuous_update=False,
334
336
  style={"description_width": description_width},
335
337
  layout=widgets.Layout(width=width, margin=margin),
336
338
  )
@@ -349,6 +351,10 @@ class IntegerRangeWidget(BaseWidget[IntegerRangeParameter, widgets.IntRangeSlide
349
351
  def extra_updates_from_parameter(self, parameter: IntegerRangeParameter) -> None:
350
352
  """Update the widget attributes from the parameter."""
351
353
  low, high = self._widget.value
354
+ if parameter.min > self._widget.max:
355
+ self._widget.max = parameter.min + 1
356
+ if parameter.max < self._widget.min:
357
+ self._widget.min = parameter.max - 1
352
358
  self._widget.min = parameter.min
353
359
  self._widget.max = parameter.max
354
360
  # Ensure values stay within bounds
@@ -363,7 +369,6 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
363
369
  def _create_widget(
364
370
  self,
365
371
  parameter: FloatRangeParameter,
366
- continuous: bool,
367
372
  width: str = "auto",
368
373
  margin: str = "3px 0px",
369
374
  description_width: str = "initial",
@@ -376,7 +381,7 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
376
381
  max=parameter.max,
377
382
  step=parameter.step,
378
383
  description=parameter.name,
379
- continuous_update=continuous,
384
+ continuous_update=False,
380
385
  style={"description_width": description_width},
381
386
  layout=widgets.Layout(width=width, margin=margin),
382
387
  )
@@ -396,6 +401,10 @@ class FloatRangeWidget(BaseWidget[FloatRangeParameter, widgets.FloatRangeSlider]
396
401
  def extra_updates_from_parameter(self, parameter: FloatRangeParameter) -> None:
397
402
  """Update the widget attributes from the parameter."""
398
403
  low, high = self._widget.value
404
+ if parameter.min > self._widget.max:
405
+ self._widget.max = parameter.min + 1
406
+ if parameter.max < self._widget.min:
407
+ self._widget.min = parameter.max - 1
399
408
  self._widget.min = parameter.min
400
409
  self._widget.max = parameter.max
401
410
  self._widget.step = parameter.step
@@ -411,7 +420,6 @@ class UnboundedIntegerWidget(BaseWidget[UnboundedIntegerParameter, widgets.IntTe
411
420
  def _create_widget(
412
421
  self,
413
422
  parameter: UnboundedIntegerParameter,
414
- continuous: bool,
415
423
  width: str = "auto",
416
424
  margin: str = "3px 0px",
417
425
  description_width: str = "initial",
@@ -440,7 +448,6 @@ class UnboundedFloatWidget(BaseWidget[UnboundedFloatParameter, widgets.FloatText
440
448
  def _create_widget(
441
449
  self,
442
450
  parameter: UnboundedFloatParameter,
443
- continuous: bool,
444
451
  width: str = "auto",
445
452
  margin: str = "3px 0px",
446
453
  description_width: str = "initial",
@@ -470,7 +477,6 @@ class ButtonWidget(BaseWidget[ButtonAction, widgets.Button]):
470
477
  def _create_widget(
471
478
  self,
472
479
  parameter: ButtonAction,
473
- continuous: bool,
474
480
  width: str = "auto",
475
481
  margin: str = "3px 0px",
476
482
  description_width: str = "initial",
@@ -514,19 +520,32 @@ class ButtonWidget(BaseWidget[ButtonAction, widgets.Button]):
514
520
 
515
521
  def create_widget(
516
522
  parameter: Union[Parameter[Any], ButtonAction],
517
- continuous: bool = False,
518
523
  width: str = "auto",
519
524
  margin: str = "3px 0px",
520
525
  description_width: str = "initial",
521
526
  ) -> BaseWidget[Union[Parameter[Any], ButtonAction], widgets.Widget]:
522
527
  """Create and return the appropriate widget for the given parameter.
523
528
 
524
- Args:
525
- parameter: The parameter to create a widget for
526
- continuous: Whether to update the widget value continuously during user interaction
527
- width: Width of the widget
528
- margin: Margin of the widget
529
- description_width: Width of the description label
529
+ Parameters
530
+ ----------
531
+ parameter : Union[Parameter[Any], ButtonAction]
532
+ The parameter to create a widget for.
533
+ width : str, optional
534
+ Width of the widget. Default is 'auto'.
535
+ margin : str, optional
536
+ Margin of the widget. Default is '3px 0px'.
537
+ description_width : str, optional
538
+ Width of the description label. Default is 'initial'.
539
+
540
+ Returns
541
+ -------
542
+ BaseWidget[Union[Parameter[Any], ButtonAction], widgets.Widget]
543
+ The appropriate widget instance for the given parameter type.
544
+
545
+ Raises
546
+ ------
547
+ ValueError
548
+ If no widget implementation exists for the given parameter type.
530
549
  """
531
550
  widget_map = {
532
551
  TextParameter: TextWidget,
@@ -562,7 +581,6 @@ def create_widget(
562
581
 
563
582
  return widget_class(
564
583
  parameter,
565
- continuous,
566
584
  width=width,
567
585
  margin=margin,
568
586
  description_width=description_width,
syd/viewer.py CHANGED
@@ -12,6 +12,36 @@ NO_UPDATE = NoUpdate()
12
12
  NO_INITIAL_VALUE = NoInitialValue()
13
13
 
14
14
 
15
+ def make_viewer(plot_func: Optional[Callable] = None) -> "Viewer":
16
+ """Create an empty viewer object.
17
+
18
+ Parameters
19
+ ----------
20
+ plot_func : Callable, optional
21
+ A function that takes a state dictionary and returns a matplotlib figure.
22
+
23
+ Returns
24
+ -------
25
+ viewer : Viewer
26
+ A new viewer object
27
+
28
+ Examples
29
+ --------
30
+ >>> from syd import make_viewer
31
+ >>> def plot(state):
32
+ >>> ... generate figure, plot stuff ...
33
+ >>> return fig
34
+ >>> viewer = make_viewer(plot)
35
+ >>> viewer.add_float('x', value=1.0, min=0, max=10)
36
+ >>> viewer.on_change('x', viewer.update_based_on_x)
37
+ >>> viewer.show()
38
+ """
39
+ viewer = Viewer()
40
+ if plot_func is not None:
41
+ viewer.set_plot(plot_func)
42
+ return viewer
43
+
44
+
15
45
  def validate_parameter_operation(
16
46
  operation: str,
17
47
  parameter_type: Union[ParameterType, ActionType],
@@ -216,7 +246,6 @@ class Viewer:
216
246
  self,
217
247
  controls_position: Literal["left", "top", "right", "bottom"] = "left",
218
248
  controls_width_percent: int = 20,
219
- continuous: bool = False,
220
249
  suppress_warnings: bool = True,
221
250
  update_threshold: float = 1.0,
222
251
  ):
@@ -228,7 +257,6 @@ class Viewer:
228
257
  env="notebook",
229
258
  controls_position=controls_position,
230
259
  controls_width_percent=controls_width_percent,
231
- continuous=continuous,
232
260
  suppress_warnings=suppress_warnings,
233
261
  update_threshold=update_threshold,
234
262
  )
@@ -427,9 +455,8 @@ class Viewer:
427
455
  try:
428
456
  self._in_callbacks = True
429
457
  if name in self.callbacks:
430
- state = self.state
431
458
  for callback in self.callbacks[name]:
432
- callback(state)
459
+ callback(self.state)
433
460
  finally:
434
461
  self._in_callbacks = False
435
462
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syd
3
- Version: 1.0.2
3
+ Version: 1.1.0
4
4
  Summary: A Python package for making GUIs for data science easy.
5
5
  Project-URL: Homepage, https://github.com/landoskape/syd
6
6
  Author-email: Andrew Landau <andrew+tyler+landau+getridofthisanddtheplusses@gmail.com>
@@ -47,7 +47,7 @@ Have you ever wanted to look through all your data really quickly interactively?
47
47
 
48
48
  Syd is a system for creating a data viewing GUI that you can view in a jupyter notebook or in a web browser. And guess what? Since it can open in a web browser, you can even open it on any other computer on your local network! For example, your PI's computer. Gone are the days of single random examples that they make infinitely stubborn conclusions about. Now, you can look at all the examples, quickly and easily, on their computer. And that's why Syd stands for share your data!
49
49
 
50
- Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about what you want to _**plot**_ and which _**parameters**_ you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to spend your time _**thinking**_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
50
+ Okay, so what is it? Syd is an automated system to convert some basic python plotting code into an interactive GUI. This means you only have to think about _**what you want to plot**_ and _**which parameters**_ you want to be interactive. Syd handles all the behind-the-scenes action required to make an interface. And guess what? That means you get to _**spend your time thinking**_ about your data, rather than writing code to look at it. And that's why Syd stands for Science, Yes! Dayummmm!
51
51
 
52
52
  ## Installation
53
53
  It's easy, just use pip install. The dependencies are light so it should work in most environments.
@@ -56,7 +56,7 @@ pip install syd
56
56
  ```
57
57
 
58
58
  ## Documentation
59
- The full documentation is available at [shareyourdata.readthedocs.io](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
59
+ The full documentation is available [here](https://shareyourdata.readthedocs.io/). It includes a quick start guide, a comprehensive tutorial, and an API reference for the different elements of Syd. If you have any questions or want to suggest improvements to the docs, please let us know on the [github issues page](https://github.com/landoskape/syd/issues)!
60
60
 
61
61
  ## Quick Start
62
62
  This is an example of a sine wave viewer which is about as simple as it gets. You can choose which env to use - if you use ``env="notebook"`` then the GUI will deploy as the output of a jupyter cell (this only works in jupyter!). If you use ``env="browser"`` then the GUI will open a page in your default web browser and you can interact with the data there (works in jupyter notebooks and also from python scripts!).
@@ -78,9 +78,8 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
78
78
  viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
79
79
  viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
80
80
 
81
- # env = "browser" # for viewing in a web browser
82
- env = "notebook" # for viewing within a jupyter notebook
83
- viewer.show()
81
+ viewer.show() # for viewing in a jupyter notebook
82
+ # viewer.share() # for viewing in a web browser
84
83
  ```
85
84
 
86
85
  ![Quick Start Viewer](./docs/assets/viewer_screenshots/readme_example_gif.gif)
@@ -220,7 +219,6 @@ black . # from the root directory of the repo
220
219
 
221
220
  ## To-Do List
222
221
  - Layout controls
223
- - [ ] Improve the display and make it look better
224
222
  - [ ] Add a "save" button that saves the current state of the viewer to a json file
225
223
  - [ ] Add a "load" button that loads the viewer state from a json file
226
224
  - [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
@@ -228,4 +226,7 @@ black . # from the root directory of the repo
228
226
  - [ ] Consider "app_deployed" context for each deployer...
229
227
  - Export options:
230
228
  - [ ] Export lite: export the viewer as a HTML/Java package that contains an incomplete set of renderings of figures -- using a certain set of parameters.
231
- - [ ] Export full: export the viewer in a way that contains the data to give full functionality.
229
+ - [ ] Export full: export the viewer in a way that contains the data to give full functionality.
230
+ - [ ] Idea for sharing: https://github.com/analyticalmonk/awesome-neuroscience, https://github.com/fasouto/awesome-dataviz
231
+ - [ ] The handling of value in Selection parameters is kind of weird.... I think we need to think more about what to do for fails!!!!
232
+ - [ ] Range parameters render poorly in browser mode.
@@ -0,0 +1,20 @@
1
+ syd/__init__.py,sha256=scju2nVDJzGslTNmtc0Mp_ftux6TzB4BAOCrgU9R19A,63
2
+ syd/parameters.py,sha256=dlnYOVsi1CDtC2toVECf0kNBRipVrtUjr6XVX86b5MA,42886
3
+ syd/support.py,sha256=7wztPMaL750opyBDnaYYRVyBR5QUJtVspDzQWpu50rk,6106
4
+ syd/viewer.py,sha256=t4NWOMrzsVHwKsnEJ8Um_wDGSEE9FcyZyb_fp_pVpko,51516
5
+ syd/flask_deployment/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
+ syd/flask_deployment/deployer.py,sha256=LnIWvFBxphFONABJCD6WoMu-a89YWIV0ZCuN_sgvB1A,25657
7
+ syd/flask_deployment/testing_principles.md,sha256=GyULM97sDeie8h3tSPoduOckdMNGyWuwm1RdHo5jzK0,10130
8
+ syd/flask_deployment/static/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
9
+ syd/flask_deployment/static/css/styles.css,sha256=Gec78sj73IJDHz5OzE68e6LqyQf4E_JX0tc5zmavT4A,5533
10
+ syd/flask_deployment/static/css/viewer.css,sha256=9HToyeDhTA1Vhawmvev7ceyxKAVZASSX_azj8Hh-OVI,1644
11
+ syd/flask_deployment/static/js/viewer.js,sha256=aIh0gxrwMWUYVge4taj0pucg2RiCcWVuV9ekBvUVsPU,40594
12
+ syd/flask_deployment/templates/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
13
+ syd/flask_deployment/templates/index.html,sha256=fr1g9IOwNttULhQCIcw_fo0sNpmdgznSGfPStQllR_E,1594
14
+ syd/notebook_deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ syd/notebook_deployment/deployer.py,sha256=ELtYiR6ac8Rg0I1Hh8_K-w5j_oZIAXf-nDazWvbD3Uc,12225
16
+ syd/notebook_deployment/widgets.py,sha256=ptys7exVA6NCF4eCRZMTPJblT0ZbtPdN4o2A0Yh5Cfc,20781
17
+ syd-1.1.0.dist-info/METADATA,sha256=tSSGUSgt_nNjzDYUGouMjas2UKFpZZa9oOfwgvMsHL0,13952
18
+ syd-1.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
19
+ syd-1.1.0.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
20
+ syd-1.1.0.dist-info/RECORD,,
@@ -1,19 +0,0 @@
1
- syd/__init__.py,sha256=X6Ij4JC9wWFTejHeTRAhKNze3H6WZNVHHRehRq2jmlQ,250
2
- syd/parameters.py,sha256=dlnYOVsi1CDtC2toVECf0kNBRipVrtUjr6XVX86b5MA,42886
3
- syd/support.py,sha256=7wztPMaL750opyBDnaYYRVyBR5QUJtVspDzQWpu50rk,6106
4
- syd/viewer.py,sha256=I2uWCup3AZkF-CZ4Ux_glTZWKrPcqERAsO3HqXziomg,50846
5
- syd/flask_deployment/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1
6
- syd/flask_deployment/deployer.py,sha256=w-zuXX1PCnWRa_VCevbQ7yqMTNH5x3Dri-sQQXOF1sM,25110
7
- syd/flask_deployment/testing_principles.md,sha256=GyULM97sDeie8h3tSPoduOckdMNGyWuwm1RdHo5jzK0,10130
8
- syd/flask_deployment/static/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
9
- syd/flask_deployment/static/css/styles.css,sha256=gA-Urdq9wmd-XpVcxILxPGlkiOtKtujG7Mw7dWftIVM,5210
10
- syd/flask_deployment/static/js/viewer.js,sha256=kSY24VGjlQLe-jtPOEU1nE7U0ALC_ZaVzGIBuEXZsTs,27441
11
- syd/flask_deployment/templates/__init__.py,sha256=ieWE8NKR-APw7h4Ge0ooZGk6wZrneSSs_1cMyTPbQSA,65
12
- syd/flask_deployment/templates/index.html,sha256=fr1g9IOwNttULhQCIcw_fo0sNpmdgznSGfPStQllR_E,1594
13
- syd/notebook_deployment/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- syd/notebook_deployment/deployer.py,sha256=sf3f9STRXpU1ac-OGENYv1g5yBpUaiIcREBKtKWvIlA,12324
15
- syd/notebook_deployment/widgets.py,sha256=UbkasRf8wY9beUwpwJYjv9X0Lus3DvgAEIORHwaC-zA,20058
16
- syd-1.0.2.dist-info/METADATA,sha256=NtVVyB_wKzL9lDzjVgOkz_D1bcSp_6MiJVv9uDBUi2k,13741
17
- syd-1.0.2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
18
- syd-1.0.2.dist-info/licenses/LICENSE,sha256=YF6QR6Vjxcg5b_sYIyqkME7FZYau5TfEUGTG-0JeRK0,35129
19
- syd-1.0.2.dist-info/RECORD,,
File without changes