eagleplot 3.0.0__py3-none-any.whl → 3.2.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.
- eagleplot/Home.py +448 -362
- eagleplot/__init__.py +19 -19
- eagleplot/annotations.py +215 -215
- eagleplot/assets/logo.ico +0 -0
- eagleplot/assets/logo.png +0 -0
- eagleplot/batch.py +446 -446
- eagleplot/cli.py +719 -719
- eagleplot/data_tools.py +849 -849
- eagleplot/example-data/Artificial/bimodal_distribution.csv +501 -501
- eagleplot/example-data/Artificial/grouped_categorical.csv +241 -241
- eagleplot/example-data/Artificial/ir_spectrum.csv +1802 -1802
- eagleplot/example-data/Artificial/molecular_properties.csv +121 -121
- eagleplot/example-data/Artificial/normal_distribution.csv +401 -401
- eagleplot/example-data/Artificial/reaction_kinetics.csv +121 -121
- eagleplot/example-data/Artificial/scatter_correlation.csv +201 -201
- eagleplot/example-data/Artificial/sine_cosine_waves.csv +301 -301
- eagleplot/gallery/manifest.json +195 -195
- eagleplot/io.py +1021 -1008
- eagleplot/math.py +1210 -1210
- eagleplot/molecular/__init__.py +21 -21
- eagleplot/molecular/atom_data.py +287 -287
- eagleplot/molecular/parsers.py +747 -747
- eagleplot/nist.py +241 -241
- eagleplot/pages/10_Molecular_Viz.py +251 -225
- eagleplot/pages/11_Batch_Analysis.py +360 -325
- eagleplot/pages/12_Help.py +199 -199
- eagleplot/pages/13_Settings.py +396 -388
- eagleplot/pages/1_Data_Upload.py +531 -521
- eagleplot/pages/2_Quick_Plot.py +1721 -1693
- eagleplot/pages/3_Analysis_Tools.py +2622 -2510
- eagleplot/pages/4_Multi_Plot_Dashboard.py +803 -791
- eagleplot/pages/5_Advanced_Plotting.py +736 -691
- eagleplot/pages/6_Export_Results.py +519 -442
- eagleplot/pages/7_Gallery.py +203 -201
- eagleplot/pages/8_Data_Tools.py +434 -434
- eagleplot/pages/9_Spectroscopy.py +830 -821
- eagleplot/pages/__init__.py +8 -8
- eagleplot/peaks.py +513 -513
- eagleplot/plotting.py +2648 -2630
- eagleplot/plugin_loader.py +208 -208
- eagleplot/report.py +448 -448
- eagleplot/signal.py +494 -494
- eagleplot/spectroscopy.py +830 -830
- eagleplot/utils/__init__.py +97 -76
- eagleplot/utils/access.py +96 -77
- eagleplot/utils/data_preview.py +352 -352
- eagleplot/utils/figures.py +200 -0
- eagleplot/utils/host_folders.py +123 -0
- eagleplot/utils/plot_config.py +2721 -2721
- eagleplot/utils/runtime.py +265 -0
- eagleplot/utils/session_state.py +550 -535
- eagleplot/utils/theming.py +315 -312
- eagleplot/utils/user_settings.py +305 -294
- {eagleplot-3.0.0.dist-info → eagleplot-3.2.0.dist-info}/METADATA +311 -304
- eagleplot-3.2.0.dist-info/RECORD +76 -0
- eagleplot-3.0.0.dist-info/RECORD +0 -73
- {eagleplot-3.0.0.dist-info → eagleplot-3.2.0.dist-info}/WHEEL +0 -0
- {eagleplot-3.0.0.dist-info → eagleplot-3.2.0.dist-info}/entry_points.txt +0 -0
- {eagleplot-3.0.0.dist-info → eagleplot-3.2.0.dist-info}/licenses/LICENSE +0 -0
- {eagleplot-3.0.0.dist-info → eagleplot-3.2.0.dist-info}/top_level.txt +0 -0
eagleplot/Home.py
CHANGED
|
@@ -1,362 +1,448 @@
|
|
|
1
|
-
"""Graphical User Interface Module - Main Entry Point.
|
|
2
|
-
|
|
3
|
-
This module provides the main entry point for the Streamlit-based GUI for
|
|
4
|
-
the EaglePlot toolkit. It provides an interactive interface for data
|
|
5
|
-
exploration, visualization, and analysis.
|
|
6
|
-
|
|
7
|
-
Navigation Structure
|
|
8
|
-
--------------------
|
|
9
|
-
Home
|
|
10
|
-
Data Upload
|
|
11
|
-
Plot
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
Advanced
|
|
15
|
-
↳ Advanced Plotting — Seaborn statistical + Plotly interactive
|
|
16
|
-
↳ Spectroscopy — IR/Raman, NMR, UV-Vis, Mass Spec + NIST
|
|
17
|
-
↳ Molecular Viz — 3D structure and vibrational modes
|
|
18
|
-
Analyze
|
|
19
|
-
|
|
20
|
-
Batch
|
|
21
|
-
Export
|
|
22
|
-
Gallery
|
|
23
|
-
Help
|
|
24
|
-
Settings
|
|
25
|
-
|
|
26
|
-
Usage
|
|
27
|
-
-----
|
|
28
|
-
streamlit run eagleplot/Home.py
|
|
29
|
-
"""
|
|
30
|
-
|
|
31
|
-
import base64
|
|
32
|
-
import sys
|
|
33
|
-
import streamlit as st
|
|
34
|
-
from pathlib import Path
|
|
35
|
-
|
|
36
|
-
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
37
|
-
sys.path.insert(0, str(_REPO_ROOT))
|
|
38
|
-
|
|
39
|
-
_PAGES_DIR = Path(__file__).resolve().parent / "pages"
|
|
40
|
-
|
|
41
|
-
# Assets are inside the package (not at the repo root) so that they survive
|
|
42
|
-
# `pip install` -- see audit A-09. Resolving them relative to the repo root meant
|
|
43
|
-
# a pip-installed GUI came up with no logo and no NCCU branding.
|
|
44
|
-
_ASSETS_DIR = Path(__file__).resolve().parent / "assets"
|
|
45
|
-
_LOGO_PNG = _ASSETS_DIR / "logo.png"
|
|
46
|
-
_NCCU_HORIZ = _ASSETS_DIR / "nccu-horiz-logo.png"
|
|
47
|
-
_NCCU_WINGS = _ASSETS_DIR / "nccu-wings.png"
|
|
48
|
-
|
|
49
|
-
from eagleplot import __version__ # noqa: E402
|
|
50
|
-
from eagleplot.utils import
|
|
51
|
-
from eagleplot.utils
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
"
|
|
71
|
-
"
|
|
72
|
-
"
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
)
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
#
|
|
80
|
-
#
|
|
81
|
-
#
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
"
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
st.
|
|
132
|
-
st.
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
-
|
|
164
|
-
|
|
165
|
-
""
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
""
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
st.markdown(
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
)
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
)
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
)
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
str(_PAGES_DIR / "
|
|
270
|
-
title="
|
|
271
|
-
url_path="
|
|
272
|
-
)
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
1
|
+
"""Graphical User Interface Module - Main Entry Point.
|
|
2
|
+
|
|
3
|
+
This module provides the main entry point for the Streamlit-based GUI for
|
|
4
|
+
the EaglePlot toolkit. It provides an interactive interface for data
|
|
5
|
+
exploration, visualization, and analysis.
|
|
6
|
+
|
|
7
|
+
Navigation Structure
|
|
8
|
+
--------------------
|
|
9
|
+
Home
|
|
10
|
+
Data Upload
|
|
11
|
+
Plot
|
|
12
|
+
Quick Plot — 27 plot types, Matplotlib/Seaborn/Plotly
|
|
13
|
+
Dashboard — Multi-Plot Dashboard (grid layouts, axis sharing)
|
|
14
|
+
Advanced
|
|
15
|
+
↳ Advanced Plotting — Seaborn statistical + Plotly interactive
|
|
16
|
+
↳ Spectroscopy — IR/Raman, NMR, UV-Vis, Mass Spec + NIST
|
|
17
|
+
↳ Molecular Viz — 3D structure and vibrational modes
|
|
18
|
+
Analyze
|
|
19
|
+
Analysis Tools — stats, curve fit, signal, peaks, …
|
|
20
|
+
Batch Analysis — the same across several datasets, + workflow presets
|
|
21
|
+
Export
|
|
22
|
+
Gallery
|
|
23
|
+
Help
|
|
24
|
+
Settings
|
|
25
|
+
|
|
26
|
+
Usage
|
|
27
|
+
-----
|
|
28
|
+
streamlit run eagleplot/Home.py
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
import base64
|
|
32
|
+
import sys
|
|
33
|
+
import streamlit as st
|
|
34
|
+
from pathlib import Path
|
|
35
|
+
|
|
36
|
+
_REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
37
|
+
sys.path.insert(0, str(_REPO_ROOT))
|
|
38
|
+
|
|
39
|
+
_PAGES_DIR = Path(__file__).resolve().parent / "pages"
|
|
40
|
+
|
|
41
|
+
# Assets are inside the package (not at the repo root) so that they survive
|
|
42
|
+
# `pip install` -- see audit A-09. Resolving them relative to the repo root meant
|
|
43
|
+
# a pip-installed GUI came up with no logo and no NCCU branding.
|
|
44
|
+
_ASSETS_DIR = Path(__file__).resolve().parent / "assets"
|
|
45
|
+
_LOGO_PNG = _ASSETS_DIR / "logo.png"
|
|
46
|
+
_NCCU_HORIZ = _ASSETS_DIR / "nccu-horiz-logo.png"
|
|
47
|
+
_NCCU_WINGS = _ASSETS_DIR / "nccu-wings.png"
|
|
48
|
+
|
|
49
|
+
from eagleplot import __version__ # noqa: E402
|
|
50
|
+
from eagleplot.utils import runtime # noqa: E402
|
|
51
|
+
from eagleplot.utils import initialize_session_state, get_session_summary # noqa: E402
|
|
52
|
+
from eagleplot.utils.theming import app_css # noqa: E402
|
|
53
|
+
|
|
54
|
+
try:
|
|
55
|
+
from PIL import Image as _PILImage
|
|
56
|
+
|
|
57
|
+
_page_icon = _PILImage.open(_LOGO_PNG) if _LOGO_PNG.exists() else "📊"
|
|
58
|
+
except ImportError:
|
|
59
|
+
_page_icon = "📊"
|
|
60
|
+
|
|
61
|
+
# ── Page configuration (must be the first Streamlit call) ─────────────────────
|
|
62
|
+
st.set_page_config(
|
|
63
|
+
page_title="EaglePlot",
|
|
64
|
+
page_icon=_page_icon,
|
|
65
|
+
layout="wide",
|
|
66
|
+
# "auto": open on wide screens, closed on phones (where it would cover the page).
|
|
67
|
+
initial_sidebar_state="auto",
|
|
68
|
+
menu_items={
|
|
69
|
+
"About": (
|
|
70
|
+
f"**EaglePlot** v{__version__}\n\n"
|
|
71
|
+
"Scientific data visualization and analysis toolkit for research "
|
|
72
|
+
"and teaching in computational science — developed at North Carolina "
|
|
73
|
+
"Central University.\n\n"
|
|
74
|
+
"**Institution:** NCCU Department of Chemistry and Biochemistry"
|
|
75
|
+
)
|
|
76
|
+
},
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# ── App-wide styling, derived from the active theme ───────────────────────────
|
|
80
|
+
# Colours come from eagleplot.utils.theming.app_css() rather than being hardcoded, so
|
|
81
|
+
# the stylesheet follows whatever the user selects in Settings. Previously the NCCU
|
|
82
|
+
# dark palette was baked in as literals, which left the hero subtitle
|
|
83
|
+
# white-on-white on a light theme (A-17).
|
|
84
|
+
st.markdown(app_css(), unsafe_allow_html=True)
|
|
85
|
+
|
|
86
|
+
initialize_session_state()
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# ── Home page content ─────────────────────────────────────────────────────────
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Examples offered on the first screen: one click loads the data and opens Quick Plot.
|
|
93
|
+
_EXAMPLE_DIR = Path(__file__).resolve().parent / "example-data" / "Artificial"
|
|
94
|
+
_STARTERS = [
|
|
95
|
+
("sine_cosine_waves.csv", "Sine & cosine waves", "Line plot of three series"),
|
|
96
|
+
("ir_spectrum.csv", "IR spectrum", "A spectrum from 4000 to 400 cm⁻¹"),
|
|
97
|
+
("scatter_correlation.csv", "Scatter correlation", "Yield vs reaction time"),
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _get_started() -> None:
|
|
102
|
+
"""First screen for a session without data: one-click examples, or upload."""
|
|
103
|
+
st.markdown("### Get started")
|
|
104
|
+
st.caption("Try EaglePlot with an example dataset, or bring your own data.")
|
|
105
|
+
cols = st.columns(len(_STARTERS))
|
|
106
|
+
for col, (fname, title, desc) in zip(cols, _STARTERS):
|
|
107
|
+
path = _EXAMPLE_DIR / fname
|
|
108
|
+
if not path.exists():
|
|
109
|
+
continue
|
|
110
|
+
with col, st.container(border=True):
|
|
111
|
+
st.markdown(f"**{title}**")
|
|
112
|
+
st.caption(desc)
|
|
113
|
+
if st.button(
|
|
114
|
+
"Plot this example",
|
|
115
|
+
key=f"home_try_{fname}",
|
|
116
|
+
type="primary" if fname == _STARTERS[0][0] else "secondary",
|
|
117
|
+
width="stretch",
|
|
118
|
+
):
|
|
119
|
+
from eagleplot.io import load_data
|
|
120
|
+
from eagleplot.utils import add_dataset
|
|
121
|
+
|
|
122
|
+
add_dataset(
|
|
123
|
+
fname,
|
|
124
|
+
load_data(path),
|
|
125
|
+
metadata={"source": "example", "file_type": path.suffix},
|
|
126
|
+
)
|
|
127
|
+
st.switch_page(basic_pg)
|
|
128
|
+
with st.container(border=True):
|
|
129
|
+
_up_col, _ex_col = st.columns(2)
|
|
130
|
+
with _up_col:
|
|
131
|
+
st.markdown("**Your own data**")
|
|
132
|
+
st.caption("CSV, Excel, JSON, NumPy, spectra (JCAMP-DX) and more.")
|
|
133
|
+
if st.button("Upload a file", key="home_upload", width="stretch"):
|
|
134
|
+
st.switch_page(data_pg)
|
|
135
|
+
with _ex_col:
|
|
136
|
+
st.markdown("**More examples**")
|
|
137
|
+
st.caption("Distributions, kinetics, molecular properties, 2-D arrays.")
|
|
138
|
+
if st.button("Browse all examples", key="home_examples", width="stretch"):
|
|
139
|
+
st.switch_page(data_pg)
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def _home_page() -> None:
|
|
143
|
+
"""Render the Dashboard / Help landing page."""
|
|
144
|
+
tab_dash, tab_help = st.tabs(["Dashboard", "Help"])
|
|
145
|
+
|
|
146
|
+
with tab_dash:
|
|
147
|
+
# ── Hero ──────────────────────────────────────────────────────────────
|
|
148
|
+
_logo_col, _title_col = st.columns([1, 5], vertical_alignment="center")
|
|
149
|
+
with _logo_col:
|
|
150
|
+
if _LOGO_PNG.exists():
|
|
151
|
+
_b64 = base64.b64encode(_LOGO_PNG.read_bytes()).decode()
|
|
152
|
+
st.markdown(
|
|
153
|
+
f'<img src="data:image/png;base64,{_b64}" alt="EaglePlot logo"'
|
|
154
|
+
' style="width:110px;height:auto;display:block;">',
|
|
155
|
+
unsafe_allow_html=True,
|
|
156
|
+
)
|
|
157
|
+
with _title_col:
|
|
158
|
+
st.markdown(
|
|
159
|
+
"<h1 style='margin-bottom: 0.1rem;'>EaglePlot</h1>",
|
|
160
|
+
unsafe_allow_html=True,
|
|
161
|
+
)
|
|
162
|
+
st.markdown(
|
|
163
|
+
"<p class='eagleplot-muted'>"
|
|
164
|
+
"Scientific data visualization and analysis "
|
|
165
|
+
f" · v{__version__}"
|
|
166
|
+
"</p>",
|
|
167
|
+
unsafe_allow_html=True,
|
|
168
|
+
)
|
|
169
|
+
|
|
170
|
+
st.divider()
|
|
171
|
+
|
|
172
|
+
summary = get_session_summary()
|
|
173
|
+
if summary["num_datasets"] == 0:
|
|
174
|
+
_get_started()
|
|
175
|
+
else:
|
|
176
|
+
# ── Session metrics ───────────────────────────────────────────────
|
|
177
|
+
col1, col2, col3 = st.columns(3)
|
|
178
|
+
col1.metric("Datasets", summary["num_datasets"])
|
|
179
|
+
col2.metric("Plots", summary["num_plots"])
|
|
180
|
+
col3.metric("Analyses", summary["num_analyses"])
|
|
181
|
+
if summary["current_dataset"]:
|
|
182
|
+
st.success(f"Active dataset: **{summary['current_dataset']}**")
|
|
183
|
+
_go_col, _more_col, _ = st.columns([1, 1, 2])
|
|
184
|
+
with _go_col:
|
|
185
|
+
st.page_link(basic_pg, label="Go to Quick Plot →")
|
|
186
|
+
with _more_col:
|
|
187
|
+
st.page_link(data_pg, label="Load more data →")
|
|
188
|
+
|
|
189
|
+
with tab_help:
|
|
190
|
+
st.markdown("## Quick Start")
|
|
191
|
+
st.markdown("""
|
|
192
|
+
1. **Upload Data** — Go to *Data Upload* to load a CSV, Excel, NumPy, or other file.
|
|
193
|
+
Or load a built-in example dataset.
|
|
194
|
+
2. **Plot** — *Plot → Quick Plot* lets you choose from 27 plot types and configure
|
|
195
|
+
them interactively.
|
|
196
|
+
3. **Analyse** — *Analyze → Analysis Tools* has curve fitting, statistics, signal
|
|
197
|
+
processing, peak analysis, and more.
|
|
198
|
+
4. **Export** — *Export* saves plots as PNG/PDF/SVG and data in multiple formats.
|
|
199
|
+
""")
|
|
200
|
+
|
|
201
|
+
st.markdown("## Supported File Formats")
|
|
202
|
+
st.markdown("""
|
|
203
|
+
| Format | Extensions |
|
|
204
|
+
|---|---|
|
|
205
|
+
| Tabular | CSV, TSV, Excel (.xlsx/.xls), JSON, Parquet |
|
|
206
|
+
| NumPy | .npy, .npz |
|
|
207
|
+
| Python | Pickle (.pkl) |
|
|
208
|
+
| Spectroscopy | JCAMP-DX (.jdx/.dx), SPC (.spc), ASC (.asc) |
|
|
209
|
+
| Scientific | HDF5 (.h5/.hdf5), NetCDF (.nc/.cdf), mzML (.mzml/.mzxml) |
|
|
210
|
+
""")
|
|
211
|
+
|
|
212
|
+
st.markdown("## Tips")
|
|
213
|
+
col1, col2 = st.columns(2)
|
|
214
|
+
with col1:
|
|
215
|
+
with st.expander("Session management"):
|
|
216
|
+
st.markdown("""
|
|
217
|
+
- Datasets persist across all pages during a session
|
|
218
|
+
- Use *Export → Save Session* to preserve your work between restarts
|
|
219
|
+
- Saved sessions can be reloaded from the same page
|
|
220
|
+
""")
|
|
221
|
+
with st.expander("Performance"):
|
|
222
|
+
st.markdown("""
|
|
223
|
+
- Files >50 MB or >10k rows get a downsampled preview automatically
|
|
224
|
+
- Interactive Plotly plots are slower than Matplotlib for large data
|
|
225
|
+
- Clear plot history periodically to free memory
|
|
226
|
+
""")
|
|
227
|
+
with col2:
|
|
228
|
+
with st.expander("Choosing a plot type"):
|
|
229
|
+
st.markdown("""
|
|
230
|
+
- **Histogram** — single-variable distributions
|
|
231
|
+
- **Scatter** — relationship between two variables
|
|
232
|
+
- **Line** — time series or ordered data
|
|
233
|
+
- **Box / Violin** — compare distributions across groups
|
|
234
|
+
- **Heatmap** — 2D matrix or correlation data
|
|
235
|
+
- **Ternary** — 3-component compositions
|
|
236
|
+
""")
|
|
237
|
+
with st.expander("Publication figures"):
|
|
238
|
+
st.markdown("""
|
|
239
|
+
- Apply *Publication Style* in the style sidebar for paper-ready figures
|
|
240
|
+
- Export at 300 DPI from *Export*
|
|
241
|
+
- Use SVG export for vector graphics in manuscripts
|
|
242
|
+
""")
|
|
243
|
+
|
|
244
|
+
st.markdown("## About")
|
|
245
|
+
st.markdown(f"""
|
|
246
|
+
**EaglePlot** is developed at North Carolina Central University for research and teaching
|
|
247
|
+
in computational science.
|
|
248
|
+
Built with Streamlit · NumPy · Pandas · Matplotlib · Seaborn · Plotly · SciPy.
|
|
249
|
+
Version {__version__}
|
|
250
|
+
""")
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
# ── Page objects ──────────────────────────────────────────────────────────────
|
|
254
|
+
|
|
255
|
+
home_pg = st.Page(_home_page, title="Home", default=True)
|
|
256
|
+
data_pg = st.Page(
|
|
257
|
+
str(_PAGES_DIR / "1_Data_Upload.py"),
|
|
258
|
+
title="Data Upload",
|
|
259
|
+
url_path="data-upload",
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
# Plot group
|
|
263
|
+
basic_pg = st.Page(
|
|
264
|
+
str(_PAGES_DIR / "2_Quick_Plot.py"),
|
|
265
|
+
title="Quick Plot",
|
|
266
|
+
url_path="plot-basic",
|
|
267
|
+
)
|
|
268
|
+
multi_pg = st.Page(
|
|
269
|
+
str(_PAGES_DIR / "4_Multi_Plot_Dashboard.py"),
|
|
270
|
+
title="Dashboard",
|
|
271
|
+
url_path="plot-multiplot",
|
|
272
|
+
)
|
|
273
|
+
adv_plt_pg = st.Page(
|
|
274
|
+
str(_PAGES_DIR / "5_Advanced_Plotting.py"),
|
|
275
|
+
title="Advanced Plotting",
|
|
276
|
+
url_path="plot-advanced",
|
|
277
|
+
)
|
|
278
|
+
spec_pg = st.Page(
|
|
279
|
+
str(_PAGES_DIR / "9_Spectroscopy.py"),
|
|
280
|
+
title="Spectroscopy",
|
|
281
|
+
url_path="plot-spectroscopy",
|
|
282
|
+
)
|
|
283
|
+
molvis_pg = st.Page(
|
|
284
|
+
str(_PAGES_DIR / "10_Molecular_Viz.py"),
|
|
285
|
+
title="Molecular Viz",
|
|
286
|
+
url_path="plot-molecular-viz",
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
# Analyze group
|
|
290
|
+
single_pg = st.Page(
|
|
291
|
+
str(_PAGES_DIR / "3_Analysis_Tools.py"),
|
|
292
|
+
title="Analysis Tools",
|
|
293
|
+
url_path="analyze-single",
|
|
294
|
+
)
|
|
295
|
+
batch_pg = st.Page(
|
|
296
|
+
str(_PAGES_DIR / "11_Batch_Analysis.py"),
|
|
297
|
+
title="Batch Analysis",
|
|
298
|
+
url_path="analyze-batch",
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
# Top-level pages
|
|
302
|
+
export_pg = st.Page(
|
|
303
|
+
str(_PAGES_DIR / "6_Export_Results.py"),
|
|
304
|
+
title="Export",
|
|
305
|
+
url_path="export",
|
|
306
|
+
)
|
|
307
|
+
gallery_pg = st.Page(
|
|
308
|
+
str(_PAGES_DIR / "7_Gallery.py"),
|
|
309
|
+
title="Gallery",
|
|
310
|
+
url_path="gallery",
|
|
311
|
+
)
|
|
312
|
+
help_pg = st.Page(
|
|
313
|
+
str(_PAGES_DIR / "12_Help.py"),
|
|
314
|
+
title="Help",
|
|
315
|
+
url_path="help",
|
|
316
|
+
)
|
|
317
|
+
settings_pg = st.Page(
|
|
318
|
+
str(_PAGES_DIR / "13_Settings.py"),
|
|
319
|
+
title="Settings",
|
|
320
|
+
url_path="settings",
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
datatools_pg = st.Page(
|
|
324
|
+
str(_PAGES_DIR / "8_Data_Tools.py"),
|
|
325
|
+
title="Data Tools",
|
|
326
|
+
url_path="analyze-data-tools",
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
# ── Navigation (position="hidden" — custom sidebar built below) ───────────────
|
|
330
|
+
|
|
331
|
+
_PLOT_PAGES = {basic_pg, multi_pg, adv_plt_pg, spec_pg, molvis_pg}
|
|
332
|
+
_ANALYZE_PAGES = {single_pg, batch_pg, datatools_pg}
|
|
333
|
+
|
|
334
|
+
pg = st.navigation(
|
|
335
|
+
[
|
|
336
|
+
home_pg,
|
|
337
|
+
data_pg,
|
|
338
|
+
basic_pg,
|
|
339
|
+
multi_pg,
|
|
340
|
+
adv_plt_pg,
|
|
341
|
+
spec_pg,
|
|
342
|
+
molvis_pg,
|
|
343
|
+
single_pg,
|
|
344
|
+
batch_pg,
|
|
345
|
+
datatools_pg,
|
|
346
|
+
export_pg,
|
|
347
|
+
gallery_pg,
|
|
348
|
+
help_pg,
|
|
349
|
+
settings_pg,
|
|
350
|
+
],
|
|
351
|
+
position="hidden",
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
# ── Custom sidebar navigation ─────────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
_ADV_HEADER = "<p class='eagleplot-section-label'>Advanced</p>"
|
|
358
|
+
|
|
359
|
+
with st.sidebar:
|
|
360
|
+
if _LOGO_PNG.exists():
|
|
361
|
+
_sb_logo_b64 = base64.b64encode(_LOGO_PNG.read_bytes()).decode()
|
|
362
|
+
st.markdown(
|
|
363
|
+
f'<img src="data:image/png;base64,{_sb_logo_b64}" alt="EaglePlot logo"'
|
|
364
|
+
' style="width:72px;height:auto;display:block;margin:0.5rem auto 1rem;">',
|
|
365
|
+
unsafe_allow_html=True,
|
|
366
|
+
)
|
|
367
|
+
st.page_link(home_pg, label="Home")
|
|
368
|
+
st.page_link(data_pg, label="Data Upload")
|
|
369
|
+
|
|
370
|
+
with st.expander("**Plot**", expanded=pg in _PLOT_PAGES):
|
|
371
|
+
st.page_link(basic_pg, label="Quick Plot")
|
|
372
|
+
st.page_link(multi_pg, label="Dashboard")
|
|
373
|
+
st.markdown(_ADV_HEADER, unsafe_allow_html=True)
|
|
374
|
+
st.page_link(adv_plt_pg, label="↳ Advanced Plotting")
|
|
375
|
+
st.page_link(spec_pg, label="↳ Spectroscopy")
|
|
376
|
+
st.page_link(molvis_pg, label="↳ Molecular Viz")
|
|
377
|
+
|
|
378
|
+
with st.expander("**Analyze**", expanded=pg in _ANALYZE_PAGES):
|
|
379
|
+
st.page_link(single_pg, label="Analysis Tools")
|
|
380
|
+
st.page_link(batch_pg, label="Batch Analysis")
|
|
381
|
+
st.page_link(datatools_pg, label="Data Tools")
|
|
382
|
+
|
|
383
|
+
st.divider()
|
|
384
|
+
st.page_link(export_pg, label="Export")
|
|
385
|
+
st.page_link(gallery_pg, label="Gallery")
|
|
386
|
+
st.page_link(help_pg, label="Help")
|
|
387
|
+
st.page_link(settings_pg, label="Settings")
|
|
388
|
+
|
|
389
|
+
st.divider()
|
|
390
|
+
with st.expander("About", expanded=False):
|
|
391
|
+
if _NCCU_HORIZ.exists():
|
|
392
|
+
_horiz_b64 = base64.b64encode(_NCCU_HORIZ.read_bytes()).decode()
|
|
393
|
+
st.markdown(
|
|
394
|
+
f'<img src="data:image/png;base64,{_horiz_b64}"'
|
|
395
|
+
' alt="North Carolina Central University"'
|
|
396
|
+
' style="width:100%;height:auto;margin-bottom:0.4rem;">',
|
|
397
|
+
unsafe_allow_html=True,
|
|
398
|
+
)
|
|
399
|
+
if _NCCU_WINGS.exists():
|
|
400
|
+
_wings_b64 = base64.b64encode(_NCCU_WINGS.read_bytes()).decode()
|
|
401
|
+
st.markdown(
|
|
402
|
+
f'<img src="data:image/png;base64,{_wings_b64}"'
|
|
403
|
+
' alt="NCCU Eagle Wings emblem"'
|
|
404
|
+
' style="width:60%;height:auto;display:block;margin:0 auto 0.4rem;">',
|
|
405
|
+
unsafe_allow_html=True,
|
|
406
|
+
)
|
|
407
|
+
st.markdown(
|
|
408
|
+
"**EaglePlot** is a scientific data visualization and analysis "
|
|
409
|
+
"toolkit developed for research and teaching in computational "
|
|
410
|
+
"science at North Carolina Central University."
|
|
411
|
+
)
|
|
412
|
+
st.markdown("[NCCU Schultz Lab on GitHub](https://github.com/The-Schultz-Lab)")
|
|
413
|
+
if runtime.IS_PYODIDE and not runtime.IS_DESKTOP:
|
|
414
|
+
st.caption(
|
|
415
|
+
f"EaglePlot v{__version__}, running in your browser: your data stays on "
|
|
416
|
+
"this computer. To load whole folders, fetch spectra from NIST or work "
|
|
417
|
+
"faster, [install EaglePlot]"
|
|
418
|
+
"(https://the-schultz-lab.github.io/eagleplot/#install)."
|
|
419
|
+
)
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
# ── Execute the active page ───────────────────────────────────────────────────
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _wait_for_deferred_packages() -> None:
|
|
426
|
+
"""Browser build: tell the user the page is waiting, and rerun once it can load."""
|
|
427
|
+
st.info("Loading the science libraries for this page (only once per visit)…")
|
|
428
|
+
|
|
429
|
+
@st.fragment(run_every=0.5)
|
|
430
|
+
def _poll() -> None:
|
|
431
|
+
if not runtime.missing_deferred_packages():
|
|
432
|
+
st.rerun(scope="app")
|
|
433
|
+
|
|
434
|
+
_poll()
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
if runtime.IS_PYODIDE:
|
|
438
|
+
# The browser build starts without scipy (roadmap D4.1): load it now, while the
|
|
439
|
+
# student looks at Home, and hold other pages until it is there.
|
|
440
|
+
runtime.start_deferred_loads()
|
|
441
|
+
# …and import pandas and the plotting stack while the student reads Home.
|
|
442
|
+
runtime.warm_imports_in_background()
|
|
443
|
+
# Home and Quick Plot don't import them; scipy-based plot types import it when used.
|
|
444
|
+
if pg not in (home_pg, basic_pg) and runtime.missing_deferred_packages():
|
|
445
|
+
_wait_for_deferred_packages()
|
|
446
|
+
st.stop()
|
|
447
|
+
|
|
448
|
+
pg.run()
|