syd 1.0.1__tar.gz → 1.1.0__tar.gz

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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: syd
3
- Version: 1.0.1
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,13 +56,9 @@ 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
- <!-- <div style="float: right; margin-left: 100px; margin-bottom: 10px;">
63
- <img src="./docs/assets/viewer_screenshots/readme_example_gif.gif" alt="Syd" width="300" align="right"/>
64
- </div> -->
65
-
66
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!).
67
63
 
68
64
  ```python
@@ -82,12 +78,12 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
82
78
  viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
83
79
  viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
84
80
 
85
- # env = "browser" # for viewing in a web browser (accessible via an IP address)
86
- env = "notebook" # for viewing within a jupyter notebook
87
- viewer = viewer.deploy(env=env)
81
+ viewer.show() # for viewing in a jupyter notebook
82
+ # viewer.share() # for viewing in a web browser
88
83
  ```
89
84
 
90
85
  ![Quick Start Viewer](./docs/assets/viewer_screenshots/readme_example_gif.gif)
86
+
91
87
  ### More Examples
92
88
  We have several examples of more complex viewers with detailed explanations in the comments. Here are the links and descriptions to each of them:
93
89
 
@@ -101,7 +97,7 @@ We have several examples of more complex viewers with detailed explanations in t
101
97
 
102
98
 
103
99
  ### Data loading
104
- Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick example, check this out:
100
+ Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick and fast example, check this one out:
105
101
  ```python
106
102
  import numpy as np
107
103
  from matplotlib import pyplot as plt
@@ -122,15 +118,15 @@ def plot(state):
122
118
  # Since plot "knows" about the data variable, all you need to do is pass the plot
123
119
  # function to the syd viewer and it'll be able to access the data once deployed!
124
120
  viewer = make_viewer(plot)
125
- viewer.deploy(env="browser")
121
+ viewer.show()
126
122
  ```
127
123
 
128
124
  ### Handling Hierarchical Callbacks
129
- Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration, to try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb).
125
+ Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration. To try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb) or open it in colab [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/4-hierarchical_callbacks.ipynb).
130
126
 
131
- For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to choose the mouse, then choose the session, and then view a particular neuron from within that session. But the viewer will break if you try to index to session 5 for mouse 2 but mouse 2 only has 4 sessions!
127
+ For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to view a particular neuron from a particular session from a particular mouse. But the viewer will break if you try to index to session 5 for mouse 2 when mouse 2 has less than 5 sessions!
132
128
 
133
- This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can call the next callback in the hierarchy. It looks like this:
129
+ This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can **update** the state and call the next callback in the hierarchy once it's finished. It looks like this:
134
130
  ```python
135
131
  import numpy as np
136
132
  from syd import Viewer # Much easier to build a Viewer class for hierarchical callbacks
@@ -141,19 +137,22 @@ class MouseViewer(Viewer):
141
137
 
142
138
  self.add_selection("mouse", options=list(mice_names))
143
139
 
144
- # We don't know how many sessions or neurons to pick from yet!
140
+ # We don't know how many sessions or neurons to pick from yet,
141
+ # so just set the max to 1 for now.
145
142
  self.add_integer("session", min=0, max=1)
146
143
  self.add_integer("neuron", min=0, max=1)
147
144
 
148
- # Any time the mouse changes, update the sessions to pick from
145
+ # Any time the mouse changes, update the sessions to pick from!
149
146
  self.on_change("mouse", self.update_mouse)
150
147
 
151
- # Any time the session changes, update the neurons to pick from
148
+ # Any time the session changes, update the neurons to pick from!
152
149
  self.on_change("session", self.update_session)
153
150
 
154
151
  # Since we built callbacks for setting the range of the session
155
- # and neuron parameters, we can use them here!
156
- # To get the state, we can use self.state, which is the current
152
+ # and neuron parameters, we can use them here so the viewer is
153
+ # fully ready and up to date.
154
+
155
+ # To get the state, use self.state, which is the current
157
156
  # state of the viewer (in the init function, it'll just be the
158
157
  # default value for each parameter you've added already).
159
158
  self.update_mouse(self.state)
@@ -166,9 +165,13 @@ class MouseViewer(Viewer):
166
165
  self.update_integer("session", max=num_sessions - 1)
167
166
 
168
167
  # Now we need to update the neurons to choose from ....
169
- # But! Updating the session parameter might trigger a change to the
170
- # session value. So, instead of using the state dictionary that was
171
- # passed into the function, we can get the ~NEW~ state dictionary like this:
168
+
169
+ # But! Updating the session parameter's max value might trigger a change
170
+ # to the current session value. This ~won't be reflected~ in the state
171
+ # dictionary that was passed to this function.
172
+
173
+ # So, we need to load the ~NEW~ state dictionary, which is always
174
+ # accessible as self.state (or viewer.state if you're not using a class).
172
175
  new_state = self.state
173
176
 
174
177
  # Then perform the session update callback!
@@ -189,7 +192,7 @@ class MouseViewer(Viewer):
189
192
 
190
193
  # Now we can create a viewer and deploy it
191
194
  viewer = MouseViewer(["Mouse 1", "Mouse 2", "Mouse 3"])
192
- viewer.deploy(env="browser")
195
+ viewer.show()
193
196
  ```
194
197
 
195
198
  ## License
@@ -216,7 +219,6 @@ black . # from the root directory of the repo
216
219
 
217
220
  ## To-Do List
218
221
  - Layout controls
219
- - [ ] Improve the display and make it look better
220
222
  - [ ] Add a "save" button that saves the current state of the viewer to a json file
221
223
  - [ ] Add a "load" button that loads the viewer state from a json file
222
224
  - [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
@@ -225,4 +227,6 @@ black . # from the root directory of the repo
225
227
  - Export options:
226
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.
227
229
  - [ ] Export full: export the viewer in a way that contains the data to give full functionality.
228
- - [ ] Keep deploy() for backwards compatibility, but deprecate it in favor of show() and share() (for notebook and browser, respectively)
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.
@@ -17,7 +17,7 @@ Have you ever wanted to look through all your data really quickly interactively?
17
17
 
18
18
  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!
19
19
 
20
- 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!
20
+ 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!
21
21
 
22
22
  ## Installation
23
23
  It's easy, just use pip install. The dependencies are light so it should work in most environments.
@@ -26,13 +26,9 @@ pip install syd
26
26
  ```
27
27
 
28
28
  ## Documentation
29
- 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)!
29
+ 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)!
30
30
 
31
31
  ## Quick Start
32
- <!-- <div style="float: right; margin-left: 100px; margin-bottom: 10px;">
33
- <img src="./docs/assets/viewer_screenshots/readme_example_gif.gif" alt="Syd" width="300" align="right"/>
34
- </div> -->
35
-
36
32
  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!).
37
33
 
38
34
  ```python
@@ -52,12 +48,12 @@ viewer.add_float("amplitude", value=1.0, min=0.1, max=2.0)
52
48
  viewer.add_float("frequency", value=1.0, min=0.1, max=5.0)
53
49
  viewer.add_selection("color", value="red", options=["red", "blue", "green", "black"])
54
50
 
55
- # env = "browser" # for viewing in a web browser (accessible via an IP address)
56
- env = "notebook" # for viewing within a jupyter notebook
57
- viewer = viewer.deploy(env=env)
51
+ viewer.show() # for viewing in a jupyter notebook
52
+ # viewer.share() # for viewing in a web browser
58
53
  ```
59
54
 
60
55
  ![Quick Start Viewer](./docs/assets/viewer_screenshots/readme_example_gif.gif)
56
+
61
57
  ### More Examples
62
58
  We have several examples of more complex viewers with detailed explanations in the comments. Here are the links and descriptions to each of them:
63
59
 
@@ -71,7 +67,7 @@ We have several examples of more complex viewers with detailed explanations in t
71
67
 
72
68
 
73
69
  ### Data loading
74
- Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick example, check this out:
70
+ Thinking about how to get data into a Syd viewer can be non-intuitive. For some examples that showcase different ways to get your data into a Syd viewer, check out the [data loading example](examples/3-data_loading.ipynb). Or, if you just want a quick and fast example, check this one out:
75
71
  ```python
76
72
  import numpy as np
77
73
  from matplotlib import pyplot as plt
@@ -92,15 +88,15 @@ def plot(state):
92
88
  # Since plot "knows" about the data variable, all you need to do is pass the plot
93
89
  # function to the syd viewer and it'll be able to access the data once deployed!
94
90
  viewer = make_viewer(plot)
95
- viewer.deploy(env="browser")
91
+ viewer.show()
96
92
  ```
97
93
 
98
94
  ### Handling Hierarchical Callbacks
99
- Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration, to try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb).
95
+ Syd dramatically reduces the amount of work you need to do to build a GUI for viewing your data. However, it can still be a bit complicated to think about callbacks. Below is a quick demonstration. To try it yourself, check out the full example [here](examples/4-hierarchical_callbacks.ipynb) or open it in colab [![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/landoskape/syd/blob/main/examples/4-hierarchical_callbacks.ipynb).
100
96
 
101
- For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to choose the mouse, then choose the session, and then view a particular neuron from within that session. But the viewer will break if you try to index to session 5 for mouse 2 but mouse 2 only has 4 sessions!
97
+ For example, suppose your dataset is composed of electrophysiology recordings from 3 mice, where each mouse has a different number of sesssions, and each session has a different number of neurons. You want to build a viewer to view a particular neuron from a particular session from a particular mouse. But the viewer will break if you try to index to session 5 for mouse 2 when mouse 2 has less than 5 sessions!
102
98
 
103
- This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can call the next callback in the hierarchy. It looks like this:
99
+ This is where hierarchical callbacks come in. There's a straightforward pattern to handling this kind of situation that you can follow. You can write a callback for each **level** of the hierarchy. Then, each callback can **update** the state and call the next callback in the hierarchy once it's finished. It looks like this:
104
100
  ```python
105
101
  import numpy as np
106
102
  from syd import Viewer # Much easier to build a Viewer class for hierarchical callbacks
@@ -111,19 +107,22 @@ class MouseViewer(Viewer):
111
107
 
112
108
  self.add_selection("mouse", options=list(mice_names))
113
109
 
114
- # We don't know how many sessions or neurons to pick from yet!
110
+ # We don't know how many sessions or neurons to pick from yet,
111
+ # so just set the max to 1 for now.
115
112
  self.add_integer("session", min=0, max=1)
116
113
  self.add_integer("neuron", min=0, max=1)
117
114
 
118
- # Any time the mouse changes, update the sessions to pick from
115
+ # Any time the mouse changes, update the sessions to pick from!
119
116
  self.on_change("mouse", self.update_mouse)
120
117
 
121
- # Any time the session changes, update the neurons to pick from
118
+ # Any time the session changes, update the neurons to pick from!
122
119
  self.on_change("session", self.update_session)
123
120
 
124
121
  # Since we built callbacks for setting the range of the session
125
- # and neuron parameters, we can use them here!
126
- # To get the state, we can use self.state, which is the current
122
+ # and neuron parameters, we can use them here so the viewer is
123
+ # fully ready and up to date.
124
+
125
+ # To get the state, use self.state, which is the current
127
126
  # state of the viewer (in the init function, it'll just be the
128
127
  # default value for each parameter you've added already).
129
128
  self.update_mouse(self.state)
@@ -136,9 +135,13 @@ class MouseViewer(Viewer):
136
135
  self.update_integer("session", max=num_sessions - 1)
137
136
 
138
137
  # Now we need to update the neurons to choose from ....
139
- # But! Updating the session parameter might trigger a change to the
140
- # session value. So, instead of using the state dictionary that was
141
- # passed into the function, we can get the ~NEW~ state dictionary like this:
138
+
139
+ # But! Updating the session parameter's max value might trigger a change
140
+ # to the current session value. This ~won't be reflected~ in the state
141
+ # dictionary that was passed to this function.
142
+
143
+ # So, we need to load the ~NEW~ state dictionary, which is always
144
+ # accessible as self.state (or viewer.state if you're not using a class).
142
145
  new_state = self.state
143
146
 
144
147
  # Then perform the session update callback!
@@ -159,7 +162,7 @@ class MouseViewer(Viewer):
159
162
 
160
163
  # Now we can create a viewer and deploy it
161
164
  viewer = MouseViewer(["Mouse 1", "Mouse 2", "Mouse 3"])
162
- viewer.deploy(env="browser")
165
+ viewer.show()
163
166
  ```
164
167
 
165
168
  ## License
@@ -186,7 +189,6 @@ black . # from the root directory of the repo
186
189
 
187
190
  ## To-Do List
188
191
  - Layout controls
189
- - [ ] Improve the display and make it look better
190
192
  - [ ] Add a "save" button that saves the current state of the viewer to a json file
191
193
  - [ ] Add a "load" button that loads the viewer state from a json file
192
194
  - [ ] Add a "freeze" button that allows the user to update state variables without updating the plot until unfreezing
@@ -195,4 +197,6 @@ black . # from the root directory of the repo
195
197
  - Export options:
196
198
  - [ ] 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.
197
199
  - [ ] Export full: export the viewer in a way that contains the data to give full functionality.
198
- - [ ] Keep deploy() for backwards compatibility, but deprecate it in favor of show() and share() (for notebook and browser, respectively)
200
+ - [ ] Idea for sharing: https://github.com/analyticalmonk/awesome-neuroscience, https://github.com/fasouto/awesome-dataviz
201
+ - [ ] The handling of value in Selection parameters is kind of weird.... I think we need to think more about what to do for fails!!!!
202
+ - [ ] Range parameters render poorly in browser mode.
@@ -0,0 +1,3 @@
1
+ __version__ = "1.1.0"
2
+
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
+ }