eagleplot 3.0.0__tar.gz → 3.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.
- {eagleplot-3.0.0 → eagleplot-3.1.0}/LICENSE +21 -21
- {eagleplot-3.0.0 → eagleplot-3.1.0}/PKG-INFO +6 -1
- {eagleplot-3.0.0 → eagleplot-3.1.0}/README.md +4 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/Home.py +91 -29
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/__init__.py +1 -1
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/io.py +21 -8
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/10_Molecular_Viz.py +40 -14
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/11_Batch_Analysis.py +52 -17
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/13_Settings.py +85 -77
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/1_Data_Upload.py +10 -3
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/2_Quick_Plot.py +415 -387
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/3_Analysis_Tools.py +1274 -1162
- eagleplot-3.1.0/eagleplot/pages/4_Multi_Plot_Dashboard.py +803 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/5_Advanced_Plotting.py +73 -28
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/6_Export_Results.py +130 -53
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/7_Gallery.py +3 -1
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/9_Spectroscopy.py +20 -11
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/access.py +16 -2
- eagleplot-3.1.0/eagleplot/utils/figures.py +198 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/plot_config.py +1 -1
- eagleplot-3.1.0/eagleplot/utils/runtime.py +128 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/theming.py +3 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/user_settings.py +11 -2
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/PKG-INFO +6 -1
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/SOURCES.txt +11 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/requires.txt +1 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/pyproject.toml +1 -0
- eagleplot-3.1.0/tests/test_advanced_plotting_page.py +179 -0
- eagleplot-3.1.0/tests/test_analysis_tools_page.py +229 -0
- eagleplot-3.1.0/tests/test_batch_page.py +64 -0
- eagleplot-3.1.0/tests/test_dashboard_page.py +267 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_docs_site.py +15 -0
- eagleplot-3.1.0/tests/test_export_page.py +115 -0
- eagleplot-3.1.0/tests/test_figures.py +238 -0
- eagleplot-3.1.0/tests/test_home_page.py +50 -0
- eagleplot-3.1.0/tests/test_quick_plot.py +97 -0
- eagleplot-3.1.0/tests/test_runtime.py +251 -0
- eagleplot-3.0.0/eagleplot/pages/4_Multi_Plot_Dashboard.py +0 -791
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/annotations.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/assets/logo.ico +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/assets/logo.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/assets/nccu-horiz-logo.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/assets/nccu-wings.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/batch.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/cli.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/data_tools.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/bimodal_distribution.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/correlation_matrix.npy +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/gaussian_surface.npy +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/grouped_categorical.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/ir_spectrum.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/molecular_properties.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/normal_distribution.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/reaction_kinetics.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/scatter_correlation.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/example-data/Artificial/sine_cosine_waves.csv +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/box_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/contour_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/distribution_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/heatmap.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/histogram.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/interactive_3d_surface.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/interactive_heatmap.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/interactive_histogram.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/interactive_line.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/interactive_scatter.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/line_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/manifest.json +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/regression_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/gallery/scatter_plot.png +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/math.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/molecular/__init__.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/molecular/atom_data.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/molecular/parsers.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/nist.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/12_Help.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/8_Data_Tools.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/pages/__init__.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/peaks.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/plotting.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/plugin_loader.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/report.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/signal.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/spectroscopy.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/__init__.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/data_preview.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot/utils/session_state.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/dependency_links.txt +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/entry_points.txt +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/eagleplot.egg-info/top_level.txt +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/setup.cfg +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_annotations.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_batch.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_cli.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_data_tools.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_docs_consistency.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_gui_pages.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_gui_paths.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_gui_safety.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_integration.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_io.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_math.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_molecular_parsers.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_nist.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_peaks.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_plotting.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_plotting_advanced.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_plugin_loader.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_rename_compat.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_repo_hygiene.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_report.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_signal.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_spectroscopy.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_theming.py +0 -0
- {eagleplot-3.0.0 → eagleplot-3.1.0}/tests/test_utils.py +0 -0
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Jonathan D. Schultz, North Carolina Central University
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jonathan D. Schultz, North Carolina Central University
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: eagleplot
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.1.0
|
|
4
4
|
Summary: A general-purpose Python toolkit for scientific data visualization and analysis
|
|
5
5
|
Author-email: "Jonathan D. Schultz" <jschult3@nccu.edu>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,6 +37,7 @@ Provides-Extra: formats
|
|
|
37
37
|
Requires-Dist: h5py>=3.0.0; extra == "formats"
|
|
38
38
|
Requires-Dist: xarray>=0.20.0; extra == "formats"
|
|
39
39
|
Requires-Dist: netcdf4>=1.6.0; extra == "formats"
|
|
40
|
+
Requires-Dist: jcamp>=1.2; extra == "formats"
|
|
40
41
|
Provides-Extra: nist
|
|
41
42
|
Requires-Dist: requests>=2.28.0; extra == "nist"
|
|
42
43
|
Provides-Extra: docs
|
|
@@ -62,6 +63,10 @@ Dynamic: license-file
|
|
|
62
63
|
[](https://pypi.org/project/eagleplot/)
|
|
63
64
|
[](https://mybinder.org/v2/gh/The-Schultz-Lab/eagleplot/main)
|
|
64
65
|
|
|
66
|
+
**[Open EaglePlot in your browser](https://the-schultz-lab.github.io/eagleplot/app/)**: nothing
|
|
67
|
+
to install, and your data never leaves your computer. Good for laptops where you can't
|
|
68
|
+
install software. To install it instead, see [Quick Start](#quick-start).
|
|
69
|
+
|
|
65
70
|
A general-purpose Python toolkit for scientific data visualization and analysis, developed for research and teaching in computational science at North Carolina Central University.
|
|
66
71
|
|
|
67
72
|
> **EaglePlot was called Plottle until version 3.0.** If you used Plottle, change
|
|
@@ -4,6 +4,10 @@
|
|
|
4
4
|
[](https://pypi.org/project/eagleplot/)
|
|
5
5
|
[](https://mybinder.org/v2/gh/The-Schultz-Lab/eagleplot/main)
|
|
6
6
|
|
|
7
|
+
**[Open EaglePlot in your browser](https://the-schultz-lab.github.io/eagleplot/app/)**: nothing
|
|
8
|
+
to install, and your data never leaves your computer. Good for laptops where you can't
|
|
9
|
+
install software. To install it instead, see [Quick Start](#quick-start).
|
|
10
|
+
|
|
7
11
|
A general-purpose Python toolkit for scientific data visualization and analysis, developed for research and teaching in computational science at North Carolina Central University.
|
|
8
12
|
|
|
9
13
|
> **EaglePlot was called Plottle until version 3.0.** If you used Plottle, change
|
|
@@ -9,15 +9,15 @@ Navigation Structure
|
|
|
9
9
|
Home
|
|
10
10
|
Data Upload
|
|
11
11
|
Plot
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
Quick Plot — 27 plot types, Matplotlib/Seaborn/Plotly
|
|
13
|
+
Dashboard — Multi-Plot Dashboard (grid layouts, axis sharing)
|
|
14
14
|
Advanced
|
|
15
15
|
↳ Advanced Plotting — Seaborn statistical + Plotly interactive
|
|
16
16
|
↳ Spectroscopy — IR/Raman, NMR, UV-Vis, Mass Spec + NIST
|
|
17
17
|
↳ Molecular Viz — 3D structure and vibrational modes
|
|
18
18
|
Analyze
|
|
19
|
-
|
|
20
|
-
Batch
|
|
19
|
+
Analysis Tools — stats, curve fit, signal, peaks, …
|
|
20
|
+
Batch Analysis — the same across several datasets, + workflow presets
|
|
21
21
|
Export
|
|
22
22
|
Gallery
|
|
23
23
|
Help
|
|
@@ -47,6 +47,7 @@ _NCCU_HORIZ = _ASSETS_DIR / "nccu-horiz-logo.png"
|
|
|
47
47
|
_NCCU_WINGS = _ASSETS_DIR / "nccu-wings.png"
|
|
48
48
|
|
|
49
49
|
from eagleplot import __version__ # noqa: E402
|
|
50
|
+
from eagleplot.utils import runtime # noqa: E402
|
|
50
51
|
from eagleplot.utils import initialize_session_state, get_session_summary # noqa: E402
|
|
51
52
|
from eagleplot.utils.theming import app_css # noqa: E402
|
|
52
53
|
|
|
@@ -62,7 +63,8 @@ st.set_page_config(
|
|
|
62
63
|
page_title="EaglePlot",
|
|
63
64
|
page_icon=_page_icon,
|
|
64
65
|
layout="wide",
|
|
65
|
-
|
|
66
|
+
# "auto": open on wide screens, closed on phones (where it would cover the page).
|
|
67
|
+
initial_sidebar_state="auto",
|
|
66
68
|
menu_items={
|
|
67
69
|
"About": (
|
|
68
70
|
f"**EaglePlot** v{__version__}\n\n"
|
|
@@ -87,6 +89,56 @@ initialize_session_state()
|
|
|
87
89
|
# ── Home page content ─────────────────────────────────────────────────────────
|
|
88
90
|
|
|
89
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
|
+
|
|
90
142
|
def _home_page() -> None:
|
|
91
143
|
"""Render the Dashboard / Help landing page."""
|
|
92
144
|
tab_dash, tab_help = st.tabs(["Dashboard", "Help"])
|
|
@@ -117,29 +169,32 @@ def _home_page() -> None:
|
|
|
117
169
|
|
|
118
170
|
st.divider()
|
|
119
171
|
|
|
120
|
-
# ── Session metrics ───────────────────────────────────────────────────
|
|
121
172
|
summary = get_session_summary()
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
col2.metric("Plots", summary["num_plots"])
|
|
125
|
-
col3.metric("Analyses", summary["num_analyses"])
|
|
126
|
-
|
|
127
|
-
if summary["current_dataset"]:
|
|
128
|
-
st.success(f"Active dataset: **{summary['current_dataset']}**")
|
|
129
|
-
st.page_link(basic_pg, label="Go to Quick Plot →")
|
|
173
|
+
if summary["num_datasets"] == 0:
|
|
174
|
+
_get_started()
|
|
130
175
|
else:
|
|
131
|
-
|
|
132
|
-
|
|
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 →")
|
|
133
188
|
|
|
134
189
|
with tab_help:
|
|
135
190
|
st.markdown("## Quick Start")
|
|
136
191
|
st.markdown("""
|
|
137
192
|
1. **Upload Data** — Go to *Data Upload* to load a CSV, Excel, NumPy, or other file.
|
|
138
193
|
Or load a built-in example dataset.
|
|
139
|
-
2. **Plot** — *Plot →
|
|
140
|
-
interactively.
|
|
141
|
-
3. **Analyse** — *Analyze →
|
|
142
|
-
peak analysis, and more.
|
|
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.
|
|
143
198
|
4. **Export** — *Export* saves plots as PNG/PDF/SVG and data in multiple formats.
|
|
144
199
|
""")
|
|
145
200
|
|
|
@@ -207,12 +262,12 @@ data_pg = st.Page(
|
|
|
207
262
|
# Plot group
|
|
208
263
|
basic_pg = st.Page(
|
|
209
264
|
str(_PAGES_DIR / "2_Quick_Plot.py"),
|
|
210
|
-
title="
|
|
265
|
+
title="Quick Plot",
|
|
211
266
|
url_path="plot-basic",
|
|
212
267
|
)
|
|
213
268
|
multi_pg = st.Page(
|
|
214
269
|
str(_PAGES_DIR / "4_Multi_Plot_Dashboard.py"),
|
|
215
|
-
title="
|
|
270
|
+
title="Dashboard",
|
|
216
271
|
url_path="plot-multiplot",
|
|
217
272
|
)
|
|
218
273
|
adv_plt_pg = st.Page(
|
|
@@ -234,12 +289,12 @@ molvis_pg = st.Page(
|
|
|
234
289
|
# Analyze group
|
|
235
290
|
single_pg = st.Page(
|
|
236
291
|
str(_PAGES_DIR / "3_Analysis_Tools.py"),
|
|
237
|
-
title="
|
|
292
|
+
title="Analysis Tools",
|
|
238
293
|
url_path="analyze-single",
|
|
239
294
|
)
|
|
240
295
|
batch_pg = st.Page(
|
|
241
296
|
str(_PAGES_DIR / "11_Batch_Analysis.py"),
|
|
242
|
-
title="Batch",
|
|
297
|
+
title="Batch Analysis",
|
|
243
298
|
url_path="analyze-batch",
|
|
244
299
|
)
|
|
245
300
|
|
|
@@ -313,16 +368,16 @@ with st.sidebar:
|
|
|
313
368
|
st.page_link(data_pg, label="Data Upload")
|
|
314
369
|
|
|
315
370
|
with st.expander("**Plot**", expanded=pg in _PLOT_PAGES):
|
|
316
|
-
st.page_link(basic_pg, label="
|
|
317
|
-
st.page_link(multi_pg, label="
|
|
371
|
+
st.page_link(basic_pg, label="Quick Plot")
|
|
372
|
+
st.page_link(multi_pg, label="Dashboard")
|
|
318
373
|
st.markdown(_ADV_HEADER, unsafe_allow_html=True)
|
|
319
374
|
st.page_link(adv_plt_pg, label="↳ Advanced Plotting")
|
|
320
375
|
st.page_link(spec_pg, label="↳ Spectroscopy")
|
|
321
376
|
st.page_link(molvis_pg, label="↳ Molecular Viz")
|
|
322
377
|
|
|
323
378
|
with st.expander("**Analyze**", expanded=pg in _ANALYZE_PAGES):
|
|
324
|
-
st.page_link(single_pg, label="
|
|
325
|
-
st.page_link(batch_pg, label="Batch")
|
|
379
|
+
st.page_link(single_pg, label="Analysis Tools")
|
|
380
|
+
st.page_link(batch_pg, label="Batch Analysis")
|
|
326
381
|
st.page_link(datatools_pg, label="Data Tools")
|
|
327
382
|
|
|
328
383
|
st.divider()
|
|
@@ -332,7 +387,7 @@ with st.sidebar:
|
|
|
332
387
|
st.page_link(settings_pg, label="Settings")
|
|
333
388
|
|
|
334
389
|
st.divider()
|
|
335
|
-
with st.expander("About", expanded=
|
|
390
|
+
with st.expander("About", expanded=False):
|
|
336
391
|
if _NCCU_HORIZ.exists():
|
|
337
392
|
_horiz_b64 = base64.b64encode(_NCCU_HORIZ.read_bytes()).decode()
|
|
338
393
|
st.markdown(
|
|
@@ -355,6 +410,13 @@ with st.sidebar:
|
|
|
355
410
|
"science at North Carolina Central University."
|
|
356
411
|
)
|
|
357
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
|
+
)
|
|
358
420
|
|
|
359
421
|
|
|
360
422
|
# ── Execute the active page ───────────────────────────────────────────────────
|
|
@@ -14,6 +14,6 @@ gui : Interactive GUI components
|
|
|
14
14
|
# `[tool.setuptools.dynamic]`, and cli.py / Home.py / docs/conf.py import it, so
|
|
15
15
|
# the number cannot drift between the package, the CLI, and the GUI.
|
|
16
16
|
# See audit A-26.
|
|
17
|
-
__version__ = "3.
|
|
17
|
+
__version__ = "3.1.0"
|
|
18
18
|
|
|
19
19
|
__all__ = ["io", "math", "plotting", "gui", "__version__"]
|
|
@@ -389,15 +389,28 @@ def load_jcamp(filepath: Union[str, Path]) -> pd.DataFrame:
|
|
|
389
389
|
1/CM
|
|
390
390
|
"""
|
|
391
391
|
try:
|
|
392
|
-
|
|
392
|
+
import jcamp as _jcamp
|
|
393
393
|
except ImportError:
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
394
|
+
raise ImportError(
|
|
395
|
+
"The `jcamp` package is required to load JCAMP-DX files. "
|
|
396
|
+
"Install it with: pip install jcamp"
|
|
397
|
+
)
|
|
398
|
+
# The reader has been renamed over jcamp's history: readfile (1.3+),
|
|
399
|
+
# jcamp_readfile (1.2), JCAMP_reader (older). Only looking for the old names
|
|
400
|
+
# made every .jdx upload fail with jcamp 1.3 installed.
|
|
401
|
+
_jcamp_read = next(
|
|
402
|
+
(
|
|
403
|
+
getattr(_jcamp, name)
|
|
404
|
+
for name in ("readfile", "jcamp_readfile", "JCAMP_reader")
|
|
405
|
+
if hasattr(_jcamp, name)
|
|
406
|
+
),
|
|
407
|
+
None,
|
|
408
|
+
)
|
|
409
|
+
if _jcamp_read is None:
|
|
410
|
+
raise ImportError(
|
|
411
|
+
f"Unsupported jcamp version {getattr(_jcamp, '__version__', '?')}: no "
|
|
412
|
+
"JCAMP-DX file reader found. Install it with: pip install --upgrade jcamp"
|
|
413
|
+
)
|
|
401
414
|
|
|
402
415
|
filepath = Path(filepath)
|
|
403
416
|
if not filepath.exists():
|
|
@@ -17,6 +17,7 @@ import streamlit as st
|
|
|
17
17
|
|
|
18
18
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
19
19
|
|
|
20
|
+
from eagleplot.utils.figures import LAZY_DOWNLOAD # noqa: E402
|
|
20
21
|
from eagleplot.utils.session_state import initialize_session_state
|
|
21
22
|
from eagleplot.molecular import (
|
|
22
23
|
VibrationalData,
|
|
@@ -172,8 +173,37 @@ st.subheader("Export")
|
|
|
172
173
|
|
|
173
174
|
col1, col2 = st.columns(2)
|
|
174
175
|
|
|
176
|
+
|
|
177
|
+
def _freq_table_csv(modes) -> bytes:
|
|
178
|
+
return (
|
|
179
|
+
pd.DataFrame(
|
|
180
|
+
{
|
|
181
|
+
"mode": [m.mode_number for m in modes],
|
|
182
|
+
"frequency_cm-1": [m.frequency for m in modes],
|
|
183
|
+
"ir_intensity_km_per_mol": [
|
|
184
|
+
(m.ir_intensity if m.ir_intensity is not None else float("nan")) for m in modes
|
|
185
|
+
],
|
|
186
|
+
"is_imaginary": [m.is_imaginary for m in modes],
|
|
187
|
+
}
|
|
188
|
+
)
|
|
189
|
+
.to_csv(index=False)
|
|
190
|
+
.encode()
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# On Streamlit >= 1.52 each export is one click: the file is built when Download is
|
|
195
|
+
# pressed. Older Streamlit keeps the two-step "prepare, then download" flow, so no
|
|
196
|
+
# export is built on every rerun there either (roadmap D1.2).
|
|
175
197
|
with col1:
|
|
176
|
-
if
|
|
198
|
+
if LAZY_DOWNLOAD:
|
|
199
|
+
st.download_button(
|
|
200
|
+
"Download interactive HTML",
|
|
201
|
+
data=lambda f=fig: f.to_html(full_html=True, include_plotlyjs="cdn"),
|
|
202
|
+
file_name=f"{Path(uploaded.name).stem}_mol.html",
|
|
203
|
+
mime="text/html",
|
|
204
|
+
key="mol_dl_html",
|
|
205
|
+
)
|
|
206
|
+
elif st.button("Export as interactive HTML", key="mol_export_html"):
|
|
177
207
|
try:
|
|
178
208
|
html_str = fig.to_html(full_html=True, include_plotlyjs="cdn")
|
|
179
209
|
st.download_button(
|
|
@@ -187,22 +217,18 @@ with col1:
|
|
|
187
217
|
st.error(str(exc))
|
|
188
218
|
|
|
189
219
|
with col2:
|
|
190
|
-
if vib_data.modes and
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
for m in vib_data.modes
|
|
198
|
-
],
|
|
199
|
-
"is_imaginary": [m.is_imaginary for m in vib_data.modes],
|
|
200
|
-
}
|
|
220
|
+
if vib_data.modes and LAZY_DOWNLOAD:
|
|
221
|
+
st.download_button(
|
|
222
|
+
"Download frequency table (CSV)",
|
|
223
|
+
data=lambda modes=list(vib_data.modes): _freq_table_csv(modes),
|
|
224
|
+
file_name=f"{Path(uploaded.name).stem}_frequencies.csv",
|
|
225
|
+
mime="text/csv",
|
|
226
|
+
key="mol_dl_csv_btn",
|
|
201
227
|
)
|
|
202
|
-
|
|
228
|
+
elif vib_data.modes and st.button("Download frequency table (CSV)", key="mol_dl_csv"):
|
|
203
229
|
st.download_button(
|
|
204
230
|
"Download CSV",
|
|
205
|
-
data=
|
|
231
|
+
data=_freq_table_csv(vib_data.modes),
|
|
206
232
|
file_name=f"{Path(uploaded.name).stem}_frequencies.csv",
|
|
207
233
|
mime="text/csv",
|
|
208
234
|
key="mol_dl_csv_btn",
|
|
@@ -16,7 +16,9 @@ import pandas as pd
|
|
|
16
16
|
|
|
17
17
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
18
18
|
|
|
19
|
+
from eagleplot.utils.figures import download_payload # noqa: E402
|
|
19
20
|
from eagleplot.utils.session_state import initialize_session_state
|
|
21
|
+
from eagleplot.utils import runtime
|
|
20
22
|
from eagleplot.batch import (
|
|
21
23
|
batch_statistics,
|
|
22
24
|
batch_curve_fit,
|
|
@@ -55,10 +57,31 @@ _RESULT_OUTPUT_COLS = [
|
|
|
55
57
|
]
|
|
56
58
|
|
|
57
59
|
|
|
58
|
-
def
|
|
59
|
-
"""
|
|
60
|
-
|
|
61
|
-
|
|
60
|
+
def _show_result(state_key: str, filename: str) -> None:
|
|
61
|
+
"""Show the last result of a batch tool, with a CSV download.
|
|
62
|
+
|
|
63
|
+
Results live in session state, so they stay on screen after any later interaction,
|
|
64
|
+
including clicking Download (which reruns the page). They used to exist only on the
|
|
65
|
+
rerun right after Run. The CSV is built on click, or once per result (roadmap D1.2).
|
|
66
|
+
"""
|
|
67
|
+
saved = st.session_state.get(state_key)
|
|
68
|
+
if not saved:
|
|
69
|
+
return
|
|
70
|
+
result_df, names = saved["df"], saved["datasets"]
|
|
71
|
+
st.success(f"Results for {len(names)} dataset(s): {', '.join(names)}")
|
|
72
|
+
st.dataframe(result_df, width="stretch")
|
|
73
|
+
st.download_button(
|
|
74
|
+
"Download CSV",
|
|
75
|
+
data=download_payload(
|
|
76
|
+
lambda df=result_df: df.to_csv(index=False).encode("utf-8"),
|
|
77
|
+
cache_name=f"_{state_key}_csv",
|
|
78
|
+
version=id(result_df),
|
|
79
|
+
key="csv",
|
|
80
|
+
),
|
|
81
|
+
file_name=filename,
|
|
82
|
+
mime="text/csv",
|
|
83
|
+
key=f"{state_key}_dl",
|
|
84
|
+
)
|
|
62
85
|
|
|
63
86
|
|
|
64
87
|
# ── top-level tabs ─────────────────────────────────────────────────────────────
|
|
@@ -120,11 +143,13 @@ with tab_analysis:
|
|
|
120
143
|
cols_arg = col_filter if col_filter else None
|
|
121
144
|
try:
|
|
122
145
|
result_df = batch_statistics(selected_datasets, columns=cols_arg)
|
|
123
|
-
st.
|
|
124
|
-
|
|
125
|
-
|
|
146
|
+
st.session_state["ba_stats_result"] = {
|
|
147
|
+
"df": result_df,
|
|
148
|
+
"datasets": list(selected_names),
|
|
149
|
+
}
|
|
126
150
|
except Exception as exc:
|
|
127
151
|
st.error(f"Error: {exc}")
|
|
152
|
+
_show_result("ba_stats_result", "batch_statistics.csv")
|
|
128
153
|
|
|
129
154
|
# ── Curve Fit ─────────────────────────────────────────────────────────────
|
|
130
155
|
with sub_fit:
|
|
@@ -164,11 +189,13 @@ with tab_analysis:
|
|
|
164
189
|
fit_type=fit_type,
|
|
165
190
|
degree=fit_degree,
|
|
166
191
|
)
|
|
167
|
-
st.
|
|
168
|
-
|
|
169
|
-
|
|
192
|
+
st.session_state["ba_fit_result"] = {
|
|
193
|
+
"df": result_df,
|
|
194
|
+
"datasets": list(selected_names),
|
|
195
|
+
}
|
|
170
196
|
except Exception as exc:
|
|
171
197
|
st.error(f"Error: {exc}")
|
|
198
|
+
_show_result("ba_fit_result", "batch_curve_fit.csv")
|
|
172
199
|
|
|
173
200
|
# ── Peak Analysis ─────────────────────────────────────────────────────────
|
|
174
201
|
with sub_peaks:
|
|
@@ -229,11 +256,13 @@ with tab_analysis:
|
|
|
229
256
|
prominence=pa_prominence,
|
|
230
257
|
distance=pa_distance,
|
|
231
258
|
)
|
|
232
|
-
st.
|
|
233
|
-
|
|
234
|
-
|
|
259
|
+
st.session_state["ba_pa_result"] = {
|
|
260
|
+
"df": result_df,
|
|
261
|
+
"datasets": list(selected_names),
|
|
262
|
+
}
|
|
235
263
|
except Exception as exc:
|
|
236
264
|
st.error(f"Error: {exc}")
|
|
265
|
+
_show_result("ba_pa_result", "batch_peak_analysis.csv")
|
|
237
266
|
|
|
238
267
|
|
|
239
268
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
@@ -241,10 +270,16 @@ with tab_analysis:
|
|
|
241
270
|
# ══════════════════════════════════════════════════════════════════════════════
|
|
242
271
|
with tab_workflows:
|
|
243
272
|
st.subheader("Workflow Presets")
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
273
|
+
if runtime.capabilities().persistent_storage:
|
|
274
|
+
st.info(
|
|
275
|
+
"Workflows are saved in **config.json** and persist across app restarts. "
|
|
276
|
+
"A workflow is a named list of analysis steps stored as JSON."
|
|
277
|
+
)
|
|
278
|
+
else:
|
|
279
|
+
st.info(
|
|
280
|
+
"A workflow is a named list of analysis steps stored as JSON. In this version "
|
|
281
|
+
"of EaglePlot, saved workflows last only until you close or reload this tab."
|
|
282
|
+
)
|
|
248
283
|
|
|
249
284
|
_TEMPLATE_STEPS = [
|
|
250
285
|
{"operation": "batch_statistics", "params": {"columns": []}},
|