PyMieSim 3.6.0__cp313-cp313-win_amd64.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.
Files changed (101) hide show
  1. PyMieSim/__init__.py +16 -0
  2. PyMieSim/__main__.py +9 -0
  3. PyMieSim/_version.py +21 -0
  4. PyMieSim/binary/__init__.py +0 -0
  5. PyMieSim/binary/interface_detector.cp310-win_amd64.pyd +0 -0
  6. PyMieSim/binary/interface_detector.cp311-win_amd64.pyd +0 -0
  7. PyMieSim/binary/interface_detector.cp312-win_amd64.pyd +0 -0
  8. PyMieSim/binary/interface_detector.cp313-win_amd64.pyd +0 -0
  9. PyMieSim/binary/interface_experiment.cp310-win_amd64.pyd +0 -0
  10. PyMieSim/binary/interface_experiment.cp311-win_amd64.pyd +0 -0
  11. PyMieSim/binary/interface_experiment.cp312-win_amd64.pyd +0 -0
  12. PyMieSim/binary/interface_experiment.cp313-win_amd64.pyd +0 -0
  13. PyMieSim/binary/interface_scatterer.cp310-win_amd64.pyd +0 -0
  14. PyMieSim/binary/interface_scatterer.cp311-win_amd64.pyd +0 -0
  15. PyMieSim/binary/interface_scatterer.cp312-win_amd64.pyd +0 -0
  16. PyMieSim/binary/interface_scatterer.cp313-win_amd64.pyd +0 -0
  17. PyMieSim/binary/interface_sets.cp310-win_amd64.pyd +0 -0
  18. PyMieSim/binary/interface_sets.cp311-win_amd64.pyd +0 -0
  19. PyMieSim/binary/interface_sets.cp312-win_amd64.pyd +0 -0
  20. PyMieSim/binary/interface_sets.cp313-win_amd64.pyd +0 -0
  21. PyMieSim/binary/interface_source.cp310-win_amd64.pyd +0 -0
  22. PyMieSim/binary/interface_source.cp311-win_amd64.pyd +0 -0
  23. PyMieSim/binary/interface_source.cp312-win_amd64.pyd +0 -0
  24. PyMieSim/binary/interface_source.cp313-win_amd64.pyd +0 -0
  25. PyMieSim/binary/libcpp_coordinates.a +0 -0
  26. PyMieSim/binary/libcpp_detector.a +0 -0
  27. PyMieSim/binary/libcpp_experiment.a +0 -0
  28. PyMieSim/binary/libcpp_fibonacci.a +0 -0
  29. PyMieSim/binary/libcpp_mode_field.a +0 -0
  30. PyMieSim/binary/libcpp_sets.a +0 -0
  31. PyMieSim/binary/libcpp_source.a +0 -0
  32. PyMieSim/directories.py +31 -0
  33. PyMieSim/experiment/__init__.py +1 -0
  34. PyMieSim/experiment/dataframe_subclass.py +220 -0
  35. PyMieSim/experiment/detector/__init__.py +2 -0
  36. PyMieSim/experiment/detector/base.py +169 -0
  37. PyMieSim/experiment/detector/coherent_mode.py +50 -0
  38. PyMieSim/experiment/detector/photodiode.py +52 -0
  39. PyMieSim/experiment/scatterer/__init__.py +4 -0
  40. PyMieSim/experiment/scatterer/base.py +98 -0
  41. PyMieSim/experiment/scatterer/core_shell.py +82 -0
  42. PyMieSim/experiment/scatterer/cylinder.py +63 -0
  43. PyMieSim/experiment/scatterer/sphere.py +66 -0
  44. PyMieSim/experiment/setup.py +356 -0
  45. PyMieSim/experiment/source/__init__.py +2 -0
  46. PyMieSim/experiment/source/base.py +85 -0
  47. PyMieSim/experiment/source/gaussian.py +60 -0
  48. PyMieSim/experiment/source/planewave.py +69 -0
  49. PyMieSim/experiment/utils.py +132 -0
  50. PyMieSim/gui/__init__.py +0 -0
  51. PyMieSim/gui/helper.py +60 -0
  52. PyMieSim/gui/interface.py +136 -0
  53. PyMieSim/gui/section.py +606 -0
  54. PyMieSim/mesh.py +368 -0
  55. PyMieSim/polarization.py +174 -0
  56. PyMieSim/single/__init__.py +48 -0
  57. PyMieSim/single/detector/__init__.py +2 -0
  58. PyMieSim/single/detector/base.py +271 -0
  59. PyMieSim/single/detector/coherent.py +99 -0
  60. PyMieSim/single/detector/uncoherent.py +105 -0
  61. PyMieSim/single/representations.py +734 -0
  62. PyMieSim/single/scatterer/__init__.py +4 -0
  63. PyMieSim/single/scatterer/base.py +405 -0
  64. PyMieSim/single/scatterer/core_shell.py +126 -0
  65. PyMieSim/single/scatterer/cylinder.py +113 -0
  66. PyMieSim/single/scatterer/sphere.py +108 -0
  67. PyMieSim/single/source/__init__.py +3 -0
  68. PyMieSim/single/source/base.py +7 -0
  69. PyMieSim/single/source/gaussian.py +137 -0
  70. PyMieSim/single/source/planewave.py +97 -0
  71. PyMieSim/special_functions.py +81 -0
  72. PyMieSim/units.py +130 -0
  73. PyMieSim/validation_data/bohren_huffman/figure_810.csv +245 -0
  74. PyMieSim/validation_data/bohren_huffman/figure_87.csv +2 -0
  75. PyMieSim/validation_data/bohren_huffman/figure_88.csv +2 -0
  76. PyMieSim/validation_data/pymiescatt/example_coreshell_0.csv +41 -0
  77. PyMieSim/validation_data/pymiescatt/example_coreshell_1.csv +401 -0
  78. PyMieSim/validation_data/pymiescatt/example_shpere_0.csv +51 -0
  79. PyMieSim/validation_data/pymiescatt/example_shpere_1.csv +801 -0
  80. PyMieSim/validation_data/pymiescatt/example_shpere_2.csv +41 -0
  81. PyMieSim/validation_data/pymiescatt/example_shpere_3.csv +401 -0
  82. PyMieSim/validation_data/pymiescatt/example_sphere_0.csv +51 -0
  83. PyMieSim/validation_data/pymiescatt/example_sphere_1.csv +801 -0
  84. PyMieSim/validation_data/pymiescatt/example_sphere_2.csv +41 -0
  85. PyMieSim/validation_data/pymiescatt/example_sphere_3.csv +401 -0
  86. PyMieSim/validation_data/pymiescatt/validation_Qsca.csv +800 -0
  87. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_1.csv +400 -0
  88. PyMieSim/validation_data/pymiescatt/validation_Qsca_coreshell_2.csv +400 -0
  89. PyMieSim/validation_data/pymiescatt/validation_Qsca_medium.csv +800 -0
  90. PyMieSim/validation_data/pymiescatt/validation_coreshell.csv +81 -0
  91. PyMieSim/validation_data/pymiescatt/validation_sphere.csv +801 -0
  92. lib/libZBessel.a +0 -0
  93. lib/lib_ZBessel.a +0 -0
  94. lib/libcpp_base_scatterer.a +0 -0
  95. lib/libcpp_coreshell.a +0 -0
  96. lib/libcpp_cylinder.a +0 -0
  97. lib/libcpp_sphere.a +0 -0
  98. pymiesim-3.6.0.dist-info/METADATA +246 -0
  99. pymiesim-3.6.0.dist-info/RECORD +101 -0
  100. pymiesim-3.6.0.dist-info/WHEEL +5 -0
  101. pymiesim-3.6.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,606 @@
1
+ from dash import html, dcc, State, Input, Output
2
+ from PyMieSim.units import nanometer, degree, milliwatt
3
+ from PyMieSim.gui.helper import parse_string_to_array_or_float
4
+ import numpy
5
+
6
+ length_units = nanometer
7
+ power_units = milliwatt
8
+ angle_units = degree
9
+
10
+ class Section:
11
+ """
12
+ A base class to define shared functionality for different sections in the optical simulation interface.
13
+
14
+ This class provides methods for creating dropdown menus, input fields, and setting up callbacks for
15
+ interaction with the Dash application.
16
+ """
17
+
18
+ def create_call_backs(self):
19
+ """
20
+ Create and set up callbacks for the section.
21
+
22
+ This method defines the `call_backs` attribute by combining the dropdown and input field callbacks
23
+ and calls the `update_callbacks` method to set up the necessary Dash callbacks.
24
+
25
+ Attributes
26
+ ----------
27
+ call_backs : list
28
+ A list of Dash `Input` objects corresponding to the dropdown and input fields of the section.
29
+ """
30
+ self.call_backs = [Input(f"{self.name}-dropdown", "value")] + [Input(id, "value") for id in self.input_id]
31
+ self.update_callbacks()
32
+
33
+ def create(self):
34
+ """
35
+ Create the layout for the section.
36
+
37
+ This method generates the HTML layout for the section, including a title, a dropdown menu to select
38
+ the section type, and input fields for relevant parameters.
39
+
40
+ Returns
41
+ -------
42
+ tuple
43
+ A tuple containing:
44
+ - html.Div : The main layout for the section.
45
+ - html.Div : A hidden placeholder for storing section-related data.
46
+ """
47
+ title = html.H2(self.title, style={'color': self.color})
48
+ type_dropdown = html.Div([
49
+ html.Label(f"Select {self.title} Type:"),
50
+ html.Div([dcc.Dropdown(id=self.dropdown_id, options=self.dropdown_options, value=self.default_value)])
51
+ ],
52
+ style={'margin-bottom': '20px'}
53
+ )
54
+
55
+ layout = [
56
+ title,
57
+ type_dropdown,
58
+ ]
59
+
60
+ for input in self.inputs.values():
61
+ user_option = html.Div([
62
+ html.Label(input['label'], style={'margin-right': '10px'}),
63
+ dcc.Input(id=input['id'], type='text', value=input['default'], style={'width': '200px'})
64
+ ],
65
+ style={'margin-bottom': '10px'}
66
+ )
67
+ layout.append(user_option)
68
+
69
+ return (
70
+ html.Div(layout, style={'padding': '10px', 'border': '1px solid black', 'margin': '10px'}),
71
+ html.Div(id=f"{self.name}-dropdown-data", style={"display": "none"}) # Hidden placeholder for data
72
+ )
73
+
74
+ def update_callbacks(self):
75
+ """
76
+ Set up callbacks to update the data dictionary and x-axis options.
77
+ """
78
+ @self.app.callback(Output(f"{self.dropdown_id}-data", "children"), *self.call_backs)
79
+ def update_data(object_type, *inputs):
80
+ """
81
+ Update the data dictionary and x-axis options based on user inputs.
82
+
83
+ Parameters
84
+ ----------
85
+ object_type : str
86
+ The type of the source selected in the dropdown.
87
+ inputs : list
88
+ The values of the inputs from the source section.
89
+
90
+ Returns
91
+ -------
92
+ str
93
+ A message indicating the data has been updated.
94
+ """
95
+ # Map the input values to their corresponding keys
96
+ input_values = dict(zip(self.inputs.keys(), inputs))
97
+
98
+ # Parse inputs and update _xaxis_options if input is an array
99
+ self._xaxis_options = []
100
+ for key, value in input_values.items():
101
+ try:
102
+ parsed_value = parse_string_to_array_or_float(value)
103
+ if isinstance(parsed_value, numpy.ndarray) and parsed_value.size > 1:
104
+ self._xaxis_options.append(f"{self.name}:{key}")
105
+ self._xaxis_options_length.append(len(parsed_value))
106
+ except ValueError:
107
+ pass # Ignore invalid inputs
108
+
109
+ # Update the data dictionary
110
+ self.data = {'type': object_type, **input_values}
111
+
112
+ return "Data Updated"
113
+
114
+
115
+ class SourceSection(Section):
116
+ """
117
+ A class to manage the Source section of the optical simulation interface.
118
+
119
+ This class handles the inputs and callbacks related to the light source, such as its type,
120
+ wavelength, optical power, numerical aperture, and polarization.
121
+
122
+ Parameters
123
+ ----------
124
+ app : Dash
125
+ The Dash application instance.
126
+ """
127
+
128
+ name = 'source'
129
+
130
+ def __init__(self, app):
131
+ """
132
+ Initialize the SourceSection class.
133
+
134
+ Parameters
135
+ ----------
136
+ app : Dash
137
+ The Dash application instance.
138
+ """
139
+ self.app = app
140
+ self.title = "Source"
141
+ self.color = "blue"
142
+ self.dropdown_options = [
143
+ {'label': 'Laser', 'value': 'laser'}
144
+ ]
145
+ self.dropdown_id = f'{self.name}-dropdown'
146
+ self.default_value = 'laser'
147
+
148
+ self.inputs = {
149
+ "wavelength": {
150
+ "id": f"{self.name}-wavelength",
151
+ "label": f"Wavelength [{length_units}]",
152
+ "default": "650"
153
+ },
154
+ "optical_power": {
155
+ "id": f"{self.name}-optical_power",
156
+ "label": f"Power [{power_units}]",
157
+ "default": "5"
158
+ },
159
+ "NA": {
160
+ "id": f"{self.name}-NA",
161
+ "label": "Numerical aperture [NA]",
162
+ "default": "0.2"
163
+ },
164
+ "polarization": {
165
+ "id": f"{self.name}-polarization",
166
+ "label": f"Polarization [{angle_units}]",
167
+ "default": "0"
168
+ }
169
+ }
170
+
171
+ # Initialize the x-axis options attribute
172
+ self._xaxis_options = []
173
+ self._xaxis_options_length = []
174
+
175
+ self.call_backs = [Input(f"{self.name}-dropdown", "value")] + [
176
+ Input(input_data["id"], "value") for input_data in self.inputs.values()
177
+ ]
178
+
179
+ self.update_callbacks()
180
+
181
+
182
+ class ScattererSection(Section):
183
+ """
184
+ A class to manage the Scatterer section of the optical simulation interface.
185
+
186
+ This class handles the inputs and callbacks related to the scatterer, such as its type,
187
+ diameter, refractive index property, and medium refractive index.
188
+
189
+ Parameters
190
+ ----------
191
+ app : Dash
192
+ The Dash application instance.
193
+ """
194
+
195
+ name = 'scatterer'
196
+
197
+ def __init__(self, app):
198
+ """
199
+ Initialize the ScattererSection class.
200
+
201
+ Parameters
202
+ ----------
203
+ app : Dash
204
+ The Dash application instance.
205
+ """
206
+ self.app = app
207
+ self.title = "Scatterer"
208
+ self.color = "green"
209
+ self.dropdown_options = [
210
+ {'label': 'Sphere', 'value': 'sphere'},
211
+ ]
212
+ self.dropdown_id = 'scatterer-dropdown'
213
+ self.default_value = 'sphere'
214
+
215
+ self.inputs = {
216
+ "diameter": {
217
+ "id": f"{self.name}-diameter",
218
+ "label": f"Diameter [{length_units}]",
219
+ "default": "100:20000:200"
220
+ },
221
+ "property": {
222
+ "id": f"{self.name}-property",
223
+ "label": "Scatterer Property",
224
+ "default": "1.5, 1.6"
225
+ },
226
+ "medium_property": {
227
+ "id": f"{self.name}-medium-property",
228
+ "label": "Medium Property",
229
+ "default": "1.33"
230
+ }
231
+ }
232
+
233
+ # Initialize x-axis options
234
+ self._xaxis_options = []
235
+ self._xaxis_options_length = []
236
+
237
+ self.call_backs = [Input(f"{self.name}-dropdown", "value")] + [
238
+ Input(input_data["id"], "value") for input_data in self.inputs.values()
239
+ ]
240
+
241
+ self.update_callbacks()
242
+
243
+
244
+ class DetectorSection(Section):
245
+ """
246
+ A class to manage the Detector section of the optical simulation interface.
247
+
248
+ This class handles the inputs and callbacks related to the detector, such as numerical aperture,
249
+ offsets, sampling, and polarization filter.
250
+
251
+ Parameters
252
+ ----------
253
+ app : Dash
254
+ The Dash application instance.
255
+ """
256
+
257
+ name = 'detector'
258
+
259
+ def __init__(self, app):
260
+ """
261
+ Initialize the DetectorSection class.
262
+
263
+ Parameters
264
+ ----------
265
+ app : Dash
266
+ The Dash application instance.
267
+ """
268
+ self.app = app
269
+ self.title = "Detector"
270
+ self.color = "red"
271
+ self.dropdown_options = [
272
+ {'label': 'Photodiode', 'value': 'photodiode'},
273
+ ]
274
+ self.dropdown_id = 'detector-dropdown'
275
+ self.default_value = 'photodiode'
276
+
277
+ self.inputs = {
278
+ "NA": {
279
+ "id": f"{self.name}-na",
280
+ "label": "Numerical aperture",
281
+ "default": "0.9"
282
+ },
283
+ "phi_offset": {
284
+ "id": f"{self.name}-phi-offset",
285
+ "label": f"Phi offset [{angle_units}]",
286
+ "default": "0"
287
+ },
288
+ "gamma_offset": {
289
+ "id": f"{self.name}-gamma-offset",
290
+ "label": f"Gamma offset [{angle_units}]",
291
+ "default": "0"
292
+ },
293
+ "sampling": {
294
+ "id": f"{self.name}-sampling",
295
+ "label": "Sampling",
296
+ "default": "1000"
297
+ },
298
+ "polarization_filter": {
299
+ "id": f"{self.name}-polarization_filter",
300
+ "label": f"Polarization Filter [{angle_units}]",
301
+ "default": "None"
302
+ }
303
+ }
304
+
305
+ # Initialize x-axis options
306
+ self._xaxis_options = []
307
+ self._xaxis_options_length = []
308
+
309
+ self.call_backs = [Input(f"{self.name}-dropdown", "value")] + [
310
+ Input(input_data["id"], "value") for input_data in self.inputs.values()
311
+ ]
312
+
313
+ self.update_callbacks()
314
+
315
+
316
+ class MeasureSection:
317
+ """
318
+ A class to manage the Measure section of the optical simulation interface.
319
+
320
+ Parameters
321
+ ----------
322
+ app : Dash
323
+ The Dash application instance.
324
+ scatterer_section : Section
325
+ The Scatterer section instance providing scatterer-related inputs.
326
+ source_section : Section
327
+ The Source section instance providing source-related inputs.
328
+ detector_section : Section
329
+ The Detector section instance providing detector-related inputs.
330
+ """
331
+
332
+ def __init__(self, app, scatterer_section, source_section, detector_section):
333
+ self.app = app
334
+ self.scatterer_section = scatterer_section
335
+ self.source_section = source_section
336
+ self.detector_section = detector_section
337
+ self.dropdown_id = "measure-input"
338
+ self.plot_button_id = "generate-plot"
339
+ self.xaxis_input_id = "xaxis-input"
340
+ self.filename_input_id = "filename-input"
341
+ self.save_button_id = "save-data"
342
+ self.plot_ready_store_id = "plot-ready"
343
+ self.data = "Qsca" # Default measure value
344
+ self.download_id = "download-data"
345
+
346
+ def get_measure_dropdown(self) -> dcc.Dropdown:
347
+ """
348
+ Creates a dropdown menu for selecting the measure type.
349
+
350
+ Returns
351
+ -------
352
+ dcc.Dropdown
353
+ A Dash dropdown component for selecting measures such as Qsca, Qext, etc.
354
+ """
355
+ return dcc.Dropdown(
356
+ id=self.dropdown_id,
357
+ options=[
358
+ {'label': 'Qsca', 'value': 'Qsca'},
359
+ {'label': 'Qext', 'value': 'Qext'},
360
+ {'label': 'Qabs', 'value': 'Qabs'},
361
+ {'label': 'Qpr', 'value': 'Qpr'},
362
+ {'label': 'Csca', 'value': 'Csca'},
363
+ {'label': 'Cext', 'value': 'Cext'},
364
+ {'label': 'Cabs', 'value': 'Cabs'},
365
+ {'label': 'Cpr', 'value': 'Cpr'},
366
+ {'label': 'g', 'value': 'g'},
367
+ {'label': 'Coupling', 'value': 'coupling'}
368
+ ],
369
+ value='Qsca',
370
+ style={'margin-right': '10px', 'margin-bottom': '0px', 'height': '36px', 'width': '200px'}
371
+ )
372
+
373
+ def get_xaxis_dropdown(self) -> dcc.Dropdown:
374
+ """
375
+ Creates a dropdown menu for selecting the x-axis parameter.
376
+
377
+ Returns
378
+ -------
379
+ dcc.Dropdown
380
+ A Dash dropdown component populated dynamically with x-axis options.
381
+ """
382
+ return dcc.Dropdown(
383
+ id=self.xaxis_input_id,
384
+ options=[],
385
+ value=None,
386
+ style={'margin-right': '10px', 'height': '36px', 'width': '300px'}
387
+ )
388
+
389
+ def get_filename_input(self) -> dcc.Input:
390
+ return dcc.Input(
391
+ id=self.filename_input_id,
392
+ type="text",
393
+ placeholder="Enter filename",
394
+ value="output.csv",
395
+ style={'margin-right': '10px', 'height': '36px', 'width': '200px'}
396
+ )
397
+
398
+ def get_plot_button(self) -> html.Button:
399
+ return html.Button("Generate Plot", id=self.plot_button_id, n_clicks=0, disabled=True, style={'height': '36px', 'background-color': 'grey', 'color': 'white'})
400
+
401
+ def get_save_button(self) -> html.Button:
402
+ return html.Button("Save Data", id=self.save_button_id, n_clicks=0, disabled=True, style={'height': '36px', 'background-color': 'grey', 'color': 'white'})
403
+
404
+ def create(self) -> html.Div:
405
+ """
406
+ Creates the layout for the Measure section, including dropdowns and buttons.
407
+
408
+ Returns
409
+ -------
410
+ html.Div
411
+ A Dash HTML Div containing the Measure section components.
412
+ """
413
+ return html.Div([
414
+ dcc.Store(id=self.plot_ready_store_id, data=False), # Track if plot is ready
415
+ dcc.Download(id=self.download_id),
416
+ html.Div(
417
+ [self.get_measure_dropdown(), self.get_xaxis_dropdown(), self.get_plot_button()],
418
+ style={'display': 'flex', 'align-items': 'center', 'justify-content': 'center'}
419
+ ),
420
+ html.Div(
421
+ [self.get_filename_input(), self.get_save_button()],
422
+ style={'display': 'flex', 'align-items': 'center', 'justify-content': 'center', 'margin-top': '20px'}
423
+ )
424
+ ])
425
+
426
+ def update_callbacks(self, callback_func, save_func):
427
+ """
428
+ Updates the callbacks for the Measure section components.
429
+
430
+ Parameters
431
+ ----------
432
+ callback_func : callable
433
+ A function to generate the plot based on the selected measure and x-axis.
434
+ save_func : callable
435
+ A function to save the data to a file when the "Save Data" button is clicked.
436
+ """
437
+ def button_style(enabled: bool) -> tuple:
438
+ """
439
+ Helper function to generate button styles and states.
440
+
441
+ Parameters
442
+ ----------
443
+ enabled : bool
444
+ Whether the button should be enabled.
445
+
446
+ Returns
447
+ -------
448
+ tuple
449
+ A tuple containing (disabled, style) for the button.
450
+ """
451
+ if enabled:
452
+ return False, {'height': '36px', 'background-color': '#28a745', 'color': 'white'} # Green
453
+ return True, {'height': '36px', 'background-color': 'grey', 'color': 'white'} # Grey
454
+
455
+ @self.app.callback(
456
+ [Output("plot-image", "src"), Output(self.plot_ready_store_id, "data")],
457
+ Input(self.plot_button_id, "n_clicks"),
458
+ State(self.dropdown_id, "value"),
459
+ State(self.xaxis_input_id, "value")
460
+ )
461
+ def trigger_callback(n_clicks: int, measure: str, xaxis: str) -> tuple:
462
+ """
463
+ Trigger the plot generation callback.
464
+
465
+ Parameters
466
+ ----------
467
+ n_clicks : int
468
+ Number of button clicks.
469
+ measure : str
470
+ Selected measure.
471
+ xaxis : str
472
+ Selected x-axis parameter.
473
+
474
+ Returns
475
+ -------
476
+ tuple
477
+ The plot source and a flag indicating plot readiness.
478
+ """
479
+ if n_clicks > 0:
480
+ plot_src = callback_func(measure, xaxis)
481
+ return plot_src, True
482
+ return None, False
483
+
484
+ @self.app.callback(
485
+ [Output(self.save_button_id, "disabled"), Output(self.save_button_id, "style")],
486
+ Input(self.plot_ready_store_id, "data")
487
+ )
488
+ def update_save_button_style(plot_ready: bool) -> tuple:
489
+ """
490
+ Update the save button's style based on plot readiness.
491
+
492
+ Parameters
493
+ ----------
494
+ plot_ready : bool
495
+ Whether the plot is ready.
496
+
497
+ Returns
498
+ -------
499
+ tuple
500
+ Disabled state and style for the save button.
501
+ """
502
+ return button_style(plot_ready)
503
+
504
+ @self.app.callback(
505
+ [Output(self.plot_button_id, "disabled"), Output(self.plot_button_id, "style")],
506
+ Input(self.xaxis_input_id, "value")
507
+ )
508
+ def update_plot_button_style(xaxis_value: str) -> tuple:
509
+ """
510
+ Update the plot button's style based on the x-axis selection.
511
+
512
+ Parameters
513
+ ----------
514
+ xaxis_value : str
515
+ The selected x-axis value.
516
+
517
+ Returns
518
+ -------
519
+ tuple
520
+ Disabled state and style for the plot button.
521
+ """
522
+ return button_style(bool(xaxis_value))
523
+
524
+ @self.app.callback(
525
+ Output(self.download_id, "data"),
526
+ Input(self.save_button_id, "n_clicks"),
527
+ State(self.plot_ready_store_id, "data"),
528
+ State(self.filename_input_id, "value"),
529
+ State(self.dropdown_id, "value")
530
+ )
531
+ def save_data(n_clicks: int, plot_ready: bool, filename: str, measure: str) -> dict:
532
+ """
533
+ Trigger file download when the save button is clicked.
534
+
535
+ Parameters
536
+ ----------
537
+ n_clicks : int
538
+ Number of times the save button has been clicked.
539
+ plot_ready : bool
540
+ Whether the plot is ready.
541
+ filename : str
542
+ The file name for the downloaded data.
543
+ measure : str
544
+ Selected measure.
545
+ xaxis : str
546
+ Selected x-axis parameter.
547
+
548
+ Returns
549
+ -------
550
+ dict or None
551
+ File content and metadata for download, or None if conditions are not met.
552
+ """
553
+ if n_clicks > 0 and plot_ready:
554
+ dataframe = save_func(filename=filename, measure=measure)
555
+ content = dataframe.to_csv(index=False)
556
+ return {"content": content, "filename": filename, "type": "text/csv"}
557
+ return None
558
+
559
+ @self.app.callback(
560
+ Output(self.xaxis_input_id, "options"),
561
+ Input(f"{self.scatterer_section.dropdown_id}-data", "children"),
562
+ Input(f"{self.source_section.dropdown_id}-data", "children"),
563
+ Input(f"{self.detector_section.dropdown_id}-data", "children")
564
+ )
565
+ def update_xaxis_options(scatterer_data, source_data, detector_data):
566
+ """
567
+ Dynamically update x-axis options based on Scatterer, Source, and Detector sections.
568
+
569
+ Parameters
570
+ ----------
571
+ scatterer_data : str
572
+ The data from the scatterer section.
573
+ source_data : str
574
+ The data from the source section.
575
+ detector_data : str
576
+ The data from the detector section.
577
+
578
+ Returns
579
+ -------
580
+ list
581
+ A list of dictionaries for the updated dropdown options.
582
+ """
583
+ options = []
584
+
585
+ # Recompute _xaxis_options for all sections dynamically
586
+ for section in [self.scatterer_section, self.source_section, self.detector_section]:
587
+ section._xaxis_options = [] # Reset options
588
+ section._xaxis_options_length = [] # Reset lengths
589
+
590
+ # Parse inputs and update _xaxis_options
591
+ for key, value in section.data.items():
592
+ try:
593
+ parsed_value = parse_string_to_array_or_float(value)
594
+ if isinstance(parsed_value, numpy.ndarray) and parsed_value.size > 1:
595
+ section._xaxis_options.append(f"{section.name}:{key}")
596
+ section._xaxis_options_length.append(parsed_value.size)
597
+ except ValueError:
598
+ pass # Ignore invalid inputs
599
+
600
+ # Add the recomputed options to the x-axis dropdown
601
+ options.extend(
602
+ [{"label": f"{opt} ({size})", "value": opt} for opt, size in zip(section._xaxis_options, section._xaxis_options_length)]
603
+ )
604
+
605
+ return options
606
+