diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 116407a..7a19269 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,6 +5,7 @@ on: branches: [main] paths: - "docs/**" + - "build_docs.py" - ".github/workflows/docs.yml" workflow_dispatch: @@ -25,13 +26,30 @@ jobs: steps: - uses: actions/checkout@v6 + - name: Set up Python 3.11 + uses: actions/setup-python@v6 + with: + python-version: "3.11" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Install docs build dependencies + run: pip install markdown + + # Previously this workflow uploaded ./docs/ raw, which published the markdown + # sources as downloads and the Sphinx sources as dead files, while the API + # reference was never built at all. build_docs.py renders each guide into a + # styled page and wires them together. See audit A-13. + - name: Build documentation site + run: python build_docs.py + - name: Configure Pages uses: actions/configure-pages@v6 - name: Upload Pages artifact uses: actions/upload-pages-artifact@v5 with: - path: ./docs/ + path: ./docs/_site/ deploy: name: Deploy to GitHub Pages diff --git a/.gitignore b/.gitignore index 38cdd99..787a9e9 100644 --- a/.gitignore +++ b/.gitignore @@ -190,3 +190,6 @@ CLAUDE.md # Plottle — generated test artifacts (should never be committed) tests/*.png tests/*.json + +# Generated documentation site (build_docs.py) +docs/_site/ diff --git a/README.md b/README.md index c91699c..7078311 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Plottle provides a unified interface for scientific data work: - **Data I/O** — 18 file formats (CSV, Excel, TSV, JSON, Parquet, NumPy, Pickle, JCAMP-DX, HDF5, NetCDF, SPC, ASC, mzML/mzXML, and more) - **Mathematical analysis** — statistics, curve fitting, signal processing, peak analysis, hypothesis testing, optimization, linear algebra -- **Multi-library plotting** — 26 plot types across Matplotlib (static), Seaborn (statistical), and Plotly (interactive) +- **Multi-library plotting** — 27 plot types across Matplotlib (static), Seaborn (statistical), and Plotly (interactive) - **14-page Streamlit GUI** — exploratory data analysis without writing code - **CLI** — batch processing and scripted workflows - **Plugin system** — drop `plugin_*.py` into `plugins/` for custom plot types and tools @@ -76,7 +76,7 @@ without installing, use `python -m plottle.cli` instead. | --- | --- | | **Home** | Dashboard overview and help tabs | | **1 — Data Upload** | Upload files in 18 formats; preview shape, column types, and summary statistics; batch folder import | -| **2 — Quick Plot** | 26 plot types with live style controls, annotation panel, and Convert to Plotly toggle | +| **2 — Quick Plot** | 27 plot types with live style controls, annotation panel, and Convert to Plotly toggle | | **3 — Analysis Tools** | 8 tabs: Statistics, Distribution, Curve Fit, Optimization, Linear Algebra, Signal Processing, Peak Analysis, Statistical Tests | | **4 — Multi-Plot Dashboard** | Up to 4×4 grid layouts with axis sharing and combined PNG/PDF export | | **5 — Advanced Plotting** | Seaborn statistical plots and Plotly interactive charts with HTML export | @@ -140,7 +140,7 @@ save_figure(fig, 'spectrum.png', dpi=300) | --- | --- | | `plottle.io` | `load_data()` / `save_data()` — auto-detects format from extension | | `plottle.math` | 25 functions — statistics, curve fitting, hypothesis tests, optimization, linear algebra | -| `plottle.plotting` | 26 plot types; Matplotlib → `(fig, ax, info)`, Plotly → `(fig, info)` | +| `plottle.plotting` | 27 plot types; Matplotlib → `(fig, ax, info)`, Plotly → `(fig, info)` | | `plottle.signal` | 16 functions — smoothing, filtering, FFT, derivatives, baseline correction, interpolation | | `plottle.peaks` | 5 functions — find, integrate, FWHM, fit (Gaussian/Lorentzian/Voigt/pseudo-Voigt) | | `plottle.data_tools` | 12 non-destructive DataFrame operations | diff --git a/build_docs.py b/build_docs.py new file mode 100644 index 0000000..95d7968 --- /dev/null +++ b/build_docs.py @@ -0,0 +1,334 @@ +#!/usr/bin/env python3 +"""Build the static documentation site into ``docs/_site/``. + +Plottle's docs used to contain two disconnected systems: a full Sphinx setup that +CI never built, and a hand-written ``docs/index.html`` that linked to none of the +markdown guides. The published site was therefore a single marketing page, with +seven markdown files and an entire API reference deployed but unreachable except +by guessing URLs — and ``.md`` served raw downloads rather than renders +(audit A-13). + +Sphinx was dropped in favour of this script, which renders each markdown guide +into a page styled to match ``index.html`` and wires them together with a shared +nav. Run it locally to preview, or let ``.github/workflows/docs.yml`` run it. + +Usage +----- + pip install markdown + python build_docs.py + # then open docs/_site/index.html + +Output +------ +``docs/_site/`` — ``index.html`` (copied verbatim), one HTML page per guide, and +``docs.css``. The directory is gitignored and is what the Pages workflow uploads. +""" + +from __future__ import annotations + +import re +import shutil +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import List + +_ROOT = Path(__file__).resolve().parent +_DOCS = _ROOT / "docs" +_SITE = _DOCS / "_site" + +REPO_URL = "https://github.com/The-Schultz-Lab/plottle" + + +@dataclass(frozen=True) +class Page: + """One markdown guide and where it lands in the built site.""" + + source: Path + slug: str + title: str + blurb: str + + @property + def output_name(self) -> str: + return f"{self.slug}.html" + + +# Ordered — this is also the nav order and the order of the cards on the landing +# page. `docs/archive/` is deliberately excluded: it is superseded internal notes, +# not user documentation. +PAGES: List[Page] = [ + Page( + _DOCS / "getting_started.md", + "getting-started", + "Getting Started", + "Install Plottle, load your first dataset, and make your first plot.", + ), + Page( + _DOCS / "tutorials" / "gui_guide.md", + "gui-guide", + "GUI Guide", + "A tour of all 14 pages of the Streamlit interface.", + ), + Page( + _DOCS / "tutorials" / "cli_guide.md", + "cli-guide", + "CLI Guide", + "The five `plottle` subcommands, with worked examples.", + ), + Page( + _DOCS / "cheatsheet.md", + "cheatsheet", + "Cheatsheet", + "Quick reference for the Python API — every module, at a glance.", + ), + Page( + _DOCS / "bug-reports.md", + "bug-reports", + "Reporting Bugs", + "What to include so a problem can be reproduced and fixed.", + ), + Page( + _DOCS / "feature_requests.md", + "feature-requests", + "Feature Requests", + "How to propose new functionality.", + ), +] + +_CSS = """\ +/* Documentation pages — palette matched to index.html. */ +*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } +html { font-size: 16px; scroll-behavior: smooth; } +body { + font-family: Inter, 'Segoe UI', -apple-system, BlinkMacSystemFont, + 'Helvetica Neue', Arial, sans-serif; + background: #ffffff; color: #334155; line-height: 1.7; + -webkit-font-smoothing: antialiased; +} +img, svg { display: block; max-width: 100%; } + +.nav { + position: sticky; top: 0; z-index: 100; + background: rgba(15, 23, 42, 0.92); + backdrop-filter: blur(14px); -webkit-backdrop-filter: blur(14px); + border-bottom: 1px solid rgba(255,255,255,0.06); +} +.nav__inner { + max-width: 1160px; margin: 0 auto; padding: 0.85rem 1.5rem; + display: flex; align-items: center; gap: 1.25rem; flex-wrap: wrap; +} +.nav__logo { + font-weight: 700; font-size: 1.0625rem; color: #f8fafc; + text-decoration: none; letter-spacing: -0.01em; +} +.nav__link { + color: #94a3b8; text-decoration: none; font-size: 0.9375rem; + transition: color 0.15s ease; +} +.nav__link:hover, .nav__link[aria-current="page"] { color: #f8fafc; } +.nav__link[aria-current="page"] { font-weight: 600; } +.nav__spacer { margin-left: auto; } +.btn--ghost { + color: #e2e8f0; text-decoration: none; font-size: 0.875rem; + padding: 0.4rem 0.85rem; border: 1px solid rgba(203,213,225,0.22); + border-radius: 7px; transition: background 0.15s ease; +} +.btn--ghost:hover { background: rgba(255,255,255,0.07); color: #f8fafc; } + +.wrap { max-width: 820px; margin: 0 auto; padding: 3rem 1.5rem 5rem; } +.crumb { font-size: 0.875rem; color: #64748b; margin-bottom: 1.5rem; } +.crumb a { color: #8B1A2B; text-decoration: none; } +.crumb a:hover { text-decoration: underline; } + +.wrap h1 { + font-size: clamp(1.9rem, 4vw, 2.4rem); font-weight: 700; color: #0f172a; + letter-spacing: -0.02em; line-height: 1.2; margin-bottom: 1.25rem; +} +.wrap h2 { + font-size: 1.5rem; font-weight: 700; color: #0f172a; + margin: 2.75rem 0 0.85rem; padding-bottom: 0.4rem; + border-bottom: 1px solid #e2e8f0; +} +.wrap h3 { font-size: 1.15rem; font-weight: 650; color: #0f172a; margin: 2rem 0 0.6rem; } +.wrap h4 { font-size: 1rem; font-weight: 650; color: #475569; margin: 1.5rem 0 0.5rem; } +.wrap p, .wrap li { font-size: 1rem; } +.wrap p { margin: 0.85rem 0; } +.wrap ul, .wrap ol { margin: 0.85rem 0 0.85rem 1.5rem; } +.wrap li { margin: 0.35rem 0; } +.wrap a { color: #8B1A2B; text-decoration: none; } +.wrap a:hover { text-decoration: underline; } +.wrap strong { color: #0f172a; font-weight: 650; } +.wrap hr { border: 0; border-top: 1px solid #e2e8f0; margin: 2.5rem 0; } +.wrap blockquote { + border-left: 3px solid #D94054; padding: 0.15rem 0 0.15rem 1.1rem; + margin: 1.25rem 0; color: #475569; +} + +.wrap code { + font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace; + font-size: 0.875em; background: #f1f5f9; color: #8B1A2B; + padding: 0.15em 0.4em; border-radius: 4px; +} +.wrap pre { + background: #0f172a; border-radius: 10px; padding: 1.15rem 1.35rem; + overflow-x: auto; margin: 1.25rem 0; +} +.wrap pre code { + background: none; color: #e2e8f0; padding: 0; font-size: 0.875rem; line-height: 1.65; +} + +/* Wide tables scroll inside their own container so the page never does. */ +.table-scroll { overflow-x: auto; margin: 1.25rem 0; } +.wrap table { border-collapse: collapse; width: 100%; font-size: 0.9375rem; } +.wrap th, .wrap td { + text-align: left; padding: 0.6rem 0.85rem; border-bottom: 1px solid #e2e8f0; + vertical-align: top; +} +.wrap th { color: #0f172a; font-weight: 650; background: #f8fafc; white-space: nowrap; } + +.footer { border-top: 1px solid #e2e8f0; background: #f8fafc; } +.footer__inner { + max-width: 1160px; margin: 0 auto; padding: 2rem 1.5rem; + display: flex; justify-content: space-between; gap: 1rem; flex-wrap: wrap; + font-size: 0.875rem; color: #64748b; +} +.footer a { color: #475569; text-decoration: none; } +.footer a:hover { color: #0f172a; } + +@media (max-width: 640px) { + .wrap { padding: 2rem 1.15rem 3.5rem; } + .nav__inner { gap: 0.85rem; } +} +""" + + +def _nav_html(current_slug: str = "") -> str: + # Built without backslashes inside f-string expressions: that is a syntax error + # before Python 3.12, and this script must run on the same floor as the package. + links = [] + for page in PAGES[:4]: + current = ' aria-current="page"' if page.slug == current_slug else "" + links.append( + '{page.title}' + ) + return f"""""" + + +def _footer_html() -> str: + return f"""""" + + +def _wrap_tables(html: str) -> str: + """Put each table in a horizontally scrollable container. + + Wide reference tables would otherwise force the whole page to scroll + sideways on a narrow screen. + """ + return re.sub( + r"(.*?
)", + r'
\1
', + html, + flags=re.DOTALL, + ) + + +def _rewrite_internal_links(html: str) -> str: + """Point cross-references at built pages instead of source markdown. + + The guides link to each other with paths like ``tutorials/cli_guide.md``, + which would 404 in the built site. + """ + by_source_name = {p.source.name: p.output_name for p in PAGES} + for source_name, output_name in by_source_name.items(): + html = re.sub( + rf'href="(?:\.\./)*(?:tutorials/)?{re.escape(source_name)}"', + f'href="{output_name}"', + html, + ) + return html + + +def _render(page: Page, body_html: str) -> str: + return f""" + + + + +{page.title} — Plottle + + + + + +{_nav_html(page.slug)} +
+

Plottle › {page.title}

+{body_html} +
+{_footer_html()} + + +""" + + +def main() -> int: + try: + import markdown + except ImportError: + print( + "error: the `markdown` package is required.\n" + ' pip install markdown (or: pip install -e ".[docs]")', + file=sys.stderr, + ) + return 1 + + missing = [p.source for p in PAGES if not p.source.is_file()] + if missing: + for path in missing: + print(f"error: missing source file {path}", file=sys.stderr) + return 1 + + if _SITE.exists(): + shutil.rmtree(_SITE) + _SITE.mkdir(parents=True) + + (_SITE / "docs.css").write_text(_CSS, encoding="utf-8") + + # The landing page is hand-written and copied verbatim. + shutil.copy2(_DOCS / "index.html", _SITE / "index.html") + print(" index.html <- docs/index.html") + + converter = markdown.Markdown( + extensions=["extra", "sane_lists", "toc"], + output_format="html5", + ) + for page in PAGES: + converter.reset() + body = converter.convert(page.source.read_text(encoding="utf-8")) + body = _wrap_tables(_rewrite_internal_links(body)) + (_SITE / page.output_name).write_text(_render(page, body), encoding="utf-8") + print(f" {page.output_name:<20}<- {page.source.relative_to(_ROOT)}") + + print(f"\nBuilt {len(PAGES) + 1} pages into {_SITE.relative_to(_ROOT)}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.nojekyll b/docs/.nojekyll similarity index 100% rename from .nojekyll rename to docs/.nojekyll diff --git a/docs/Makefile b/docs/Makefile deleted file mode 100644 index 995edf1..0000000 --- a/docs/Makefile +++ /dev/null @@ -1,18 +0,0 @@ -# Minimal Sphinx Makefile -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -.PHONY: help html clean - -help: - @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) - -html: - @$(SPHINXBUILD) -b html "$(SOURCEDIR)" "$(BUILDDIR)/html" $(SPHINXOPTS) $(O) - @echo "" - @echo "Build finished. Open _build/html/index.html" - -clean: - rm -rf $(BUILDDIR) diff --git a/docs/_static/.gitkeep b/docs/_static/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/docs/_static/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/_templates/.gitkeep b/docs/_templates/.gitkeep deleted file mode 100644 index 8b13789..0000000 --- a/docs/_templates/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/docs/api/io.rst b/docs/api/io.rst deleted file mode 100644 index 87143a6..0000000 --- a/docs/api/io.rst +++ /dev/null @@ -1,13 +0,0 @@ -I/O Module (``modules.io``) -============================ - -Auto-detect format from extension and load or save any supported data type. - -Supported extensions: ``.pkl``, ``.npy``, ``.npz``, ``.csv``, ``.xlsx``, ``.tsv``, -``.json``, ``.parquet`` - -.. automodule:: modules.io - :members: - :undoc-members: - :show-inheritance: - :member-order: bysource diff --git a/docs/api/math.rst b/docs/api/math.rst deleted file mode 100644 index b880388..0000000 --- a/docs/api/math.rst +++ /dev/null @@ -1,10 +0,0 @@ -Math Module (``modules.math``) -================================ - -Statistical analysis, curve fitting, optimization, and linear algebra. - -.. automodule:: modules.math - :members: - :undoc-members: - :show-inheritance: - :member-order: bysource diff --git a/docs/api/plotting.rst b/docs/api/plotting.rst deleted file mode 100644 index b87b2b8..0000000 --- a/docs/api/plotting.rst +++ /dev/null @@ -1,15 +0,0 @@ -Plotting Module (``modules.plotting``) -======================================= - -All 13 plot types across Matplotlib, Seaborn, and Plotly. - -Return conventions (DEC-003): - -* **Matplotlib / Seaborn** functions return ``(fig, ax, info)`` -* **Plotly** functions return ``(fig, info)`` - -.. automodule:: modules.plotting - :members: - :undoc-members: - :show-inheritance: - :member-order: bysource diff --git a/docs/cheatsheet.md b/docs/cheatsheet.md index 6960af5..82aa267 100644 --- a/docs/cheatsheet.md +++ b/docs/cheatsheet.md @@ -10,9 +10,9 @@ NCCU Department of Chemistry and Biochemistry import sys sys.path.insert(0, 'path/to/plottle') -from modules.io import load_data, save_data -from modules.math import calculate_statistics, fit_linear, fit_polynomial -from modules.plotting import line_plot, scatter_plot, histogram, save_figure +from plottle.io import load_data, save_data +from plottle.math import calculate_statistics, fit_linear, fit_polynomial +from plottle.plotting import line_plot, scatter_plot, histogram, save_figure ``` --- @@ -20,7 +20,7 @@ from modules.plotting import line_plot, scatter_plot, histogram, save_figure ## I/O — Loading Data ```python -from modules.io import load_data, load_dataframe, load_numpy, load_pickle +from plottle.io import load_data, load_dataframe, load_numpy, load_pickle df = load_data('data.csv') # auto-detect by extension df = load_dataframe('data.csv') # → DataFrame (.csv .xlsx .tsv .json .parquet) @@ -31,7 +31,7 @@ obj = load_pickle('data.pkl') # → any object (.pkl) ## I/O — Saving Data ```python -from modules.io import save_data, save_dataframe, save_numpy, save_pickle +from plottle.io import save_data, save_dataframe, save_numpy, save_pickle save_data(df, 'output.csv') save_dataframe(df, 'output.xlsx') @@ -44,7 +44,7 @@ save_pickle(obj, 'session.pkl') ## Statistics ```python -from modules.math import ( +from plottle.math import ( calculate_mean, calculate_median, calculate_std, calculate_statistics, check_normality, ) @@ -65,7 +65,7 @@ norm = check_normality(arr) ## Curve Fitting ```python -from modules.math import fit_linear, fit_polynomial, fit_exponential, fit_custom +from plottle.math import fit_linear, fit_polynomial, fit_exponential, fit_custom # Linear y = m·x + b r = fit_linear(x, y) @@ -92,7 +92,7 @@ r = fit_custom(x, y, func=my_func, p0=[1.0, 0.1, 0.0]) ## Matplotlib Plots ```python -from modules.plotting import histogram, line_plot, scatter_plot, heatmap, contour_plot +from plottle.plotting import histogram, line_plot, scatter_plot, heatmap, contour_plot # Histogram fig, ax, info = histogram(data, bins=20, xlabel='Value', ylabel='Count', title='Distribution') @@ -116,7 +116,7 @@ fig, ax = contour_plot(X, Y, Z, title='Potential Energy Surface') ## Seaborn Plots ```python -from modules.plotting import distribution_plot, box_plot, regression_plot +from plottle.plotting import distribution_plot, box_plot, regression_plot fig, ax = distribution_plot(data, kind='kde') # kind: hist | kde | ecdf fig, ax = box_plot(df) # or box_plot(df, kind='violin') @@ -126,7 +126,7 @@ fig, ax = regression_plot(x, y) # scatter + regression line + C ## Interactive (Plotly) Plots ```python -from modules.plotting import ( +from plottle.plotting import ( interactive_histogram, interactive_scatter, interactive_line, interactive_heatmap, interactive_3d_surface, ) @@ -146,7 +146,7 @@ fig.write_html('plot.html') # save as self-contained HTML ## Saving Figures ```python -from modules.plotting import save_figure +from plottle.plotting import save_figure save_figure(fig, 'plot.png', dpi=150) # screen quality save_figure(fig, 'plot.png', dpi=300) # print / publication quality @@ -249,7 +249,7 @@ fig, ax = heatmap(corr, title='Correlation Matrix') ```python # Available in the GUI Settings page and plot_config module -from modules.utils.plot_config import COLOR_PALETTES +from plottle.utils.plot_config import COLOR_PALETTES palettes = list(COLOR_PALETTES.keys()) # 'Default', 'Color-Blind Safe (Wong)', 'Color-Blind Safe (Okabe-Ito)', diff --git a/docs/conf.py b/docs/conf.py deleted file mode 100644 index 4ca9a77..0000000 --- a/docs/conf.py +++ /dev/null @@ -1,89 +0,0 @@ -"""Sphinx configuration for Plottle API documentation.""" - -import os -import sys - -# Point Sphinx to the repo root so autodoc can import plottle.* -sys.path.insert(0, os.path.abspath("..")) - -# -- Project information ------------------------------------------------------- - -project = "Plottle" -copyright = "2026, Jonathan D. Schultz, PhD — NCCU Department of Chemistry and Biochemistry" -author = "Jonathan D. Schultz, PhD" -# Read the version from the package so it cannot drift from pyproject.toml. -_version_ns: dict = {} -for _line in open(os.path.join("..", "plottle", "__init__.py"), encoding="utf-8"): - if _line.startswith("__version__"): - exec(_line, _version_ns) # noqa: S102 -- a single literal assignment - break -release = _version_ns["__version__"] -version = ".".join(release.split(".")[:2]) - -# -- General configuration ----------------------------------------------------- - -extensions = [ - "sphinx.ext.autodoc", # auto-generate docs from docstrings - "sphinx.ext.napoleon", # Google- and NumPy-style docstring support - "sphinx.ext.viewcode", # add [source] links next to each function - "sphinx.ext.intersphinx", # cross-reference NumPy / SciPy docs -] - -intersphinx_mapping = { - "python": ("https://docs.python.org/3", None), - "numpy": ("https://numpy.org/doc/stable", None), - "scipy": ("https://docs.scipy.org/doc/scipy", None), - "pandas": ("https://pandas.pydata.org/docs", None), - "matplotlib": ("https://matplotlib.org/stable", None), -} - -# Napoleon settings — match the Google-style docstrings used in this project -napoleon_google_docstring = True -napoleon_numpy_docstring = True -napoleon_include_init_with_doc = False -napoleon_include_private_with_doc = False - -# autodoc settings -autodoc_member_order = "bysource" -autodoc_typehints = "description" - -# Mock imports that are not available in the Sphinx build environment or that -# should not be executed during doc generation (GUI-only dependencies). -autodoc_mock_imports = ["streamlit"] - -# Exclude the Streamlit GUI entry point and page modules from autodoc. -# These files import streamlit at module level and are not part of the public API. -exclude_patterns = [ - "_build", - "**/_build/*", - "Thumbs.db", - ".DS_Store", -] - -templates_path = ["_templates"] - -# -- HTML output --------------------------------------------------------------- - -html_theme = "furo" - -html_theme_options = { - "light_css_variables": { - "color-brand-primary": "#5a0010", - "color-brand-content": "#5a0010", - }, - "dark_css_variables": { - "color-brand-primary": "#e0a3a3", - "color-brand-content": "#e0a3a3", - }, -} - -html_static_path = ["_static"] - -html_title = "Plottle Documentation" -html_short_title = "Plottle" - -# -- Source files -------------------------------------------------------------- - -source_suffix = ".rst" -master_doc = "index" -language = "en" diff --git a/docs/getting_started.md b/docs/getting_started.md index 3b69b63..933d5bc 100644 --- a/docs/getting_started.md +++ b/docs/getting_started.md @@ -46,12 +46,15 @@ python -m venv .venv source .venv/bin/activate ``` -### Step 4 — Install dependencies +### Step 4 — Install Plottle ```bash -pip install -r requirements.txt +pip install -e ".[formats,nist]" ``` +The `formats` extra adds HDF5 and NetCDF support; `nist` adds the NIST WebBook +lookup. The editable install is also what puts the `plottle` command on your PATH. + ### Step 5 — Verify ```bash @@ -71,7 +74,7 @@ launch.bat ### Any platform ```bash -streamlit run modules/Home.py +streamlit run plottle/Home.py ``` Open `http://localhost:8501` in your browser. Use the sidebar to navigate between pages. @@ -94,7 +97,7 @@ import numpy as np import sys sys.path.insert(0, 'path/to/plottle') -from modules.plotting import line_plot, save_figure +from plottle.plotting import line_plot, save_figure # Simulate absorbance vs. wavelength wavelength = np.linspace(400, 800, 200) @@ -116,10 +119,10 @@ save_figure(fig, 'spectrum.png', dpi=300) ## Loading Data -The `modules.io` module supports eight common formats. +The `plottle.io` module supports eight common formats. ```python -from modules.io import load_data, save_data +from plottle.io import load_data, save_data # Auto-detect format from extension df = load_data('experiment.csv') # → pandas DataFrame @@ -145,7 +148,7 @@ Supported extensions: ## Computing Statistics ```python -from modules.math import calculate_statistics +from plottle.math import calculate_statistics stats = calculate_statistics(arr) print(f"Mean: {stats['mean']:.4f}") @@ -160,7 +163,7 @@ Returns: `mean`, `median`, `std`, `var`, `min`, `max`, `q1`, `q3`, `iqr`, `range ## Curve Fitting ```python -from modules.math import fit_linear, fit_polynomial +from plottle.math import fit_linear, fit_polynomial # Beer-Lambert: A = ε·c·l → linear fit result = fit_linear(concentration, absorbance) @@ -197,7 +200,7 @@ y_fit = poly['predict'](x) # call the returned predict function ## Saving Figures ```python -from modules.plotting import save_figure +from plottle.plotting import save_figure save_figure(fig, 'plot.png', dpi=300) # high-res raster save_figure(fig, 'plot.svg') # vector (no dpi needed) @@ -210,12 +213,20 @@ save_figure(fig, 'plot.pdf') # vector, publication-ready | Page | Purpose | | --- | --- | -| **1 — Data Upload** | Load files; preview shape, types, and statistics | -| **2 — Quick Plot** | Generate any plot type with live configuration controls | -| **3 — Analysis Tools** | Statistics, curve fitting, smoothing, peak fitting | -| **4 — Multi-Plot Dashboard** | Side-by-side grid of up to 6 independent plots | -| **5 — Advanced Plotting** | Correlation heatmaps, overlaid distributions, 3D scatter | -| **6 — Settings** | Persistent defaults and named style presets | +| **Home** | Dashboard overview and help tabs | +| **Data Upload** | Load files in 18 formats; preview shape, types, and statistics; batch folder import | +| **Plot → Basic** | 27 plot types with live style controls and an annotation panel | +| **Plot → Multiplot** | Up to 4×4 grid layouts with axis sharing and combined export | +| **Plot → Advanced Plotting** | Seaborn statistical plots and Plotly interactive charts | +| **Plot → Spectroscopy** | IR/Raman, NMR, UV-Vis, Mass Spec; NIST WebBook lookup by CAS | +| **Plot → Molecular Viz** | Gaussian/ORCA/Molden output; 3D structure and vibrational modes | +| **Analyze → Single** | Statistics, distributions, curve fitting, signal processing, peaks | +| **Analyze → Batch** | Batch statistics, curve fitting, and peak analysis with presets | +| **Analyze → Data Tools** | 12 non-destructive DataFrame operations | +| **Export** | Export plots, data, and analyses; save/load sessions; PDF reports | +| **Gallery** | Pre-rendered examples with "Use this config" buttons | +| **Help** | Getting started, plot types, analysis tools, formats, tips | +| **Settings** | Theme, plot defaults, and named preset management | --- @@ -224,9 +235,9 @@ save_figure(fig, 'plot.pdf') # vector, publication-ready ### Beer-Lambert calibration curve ```python -from modules.io import load_dataframe -from modules.math import fit_linear -from modules.plotting import scatter_plot, save_figure +from plottle.io import load_dataframe +from plottle.math import fit_linear +from plottle.plotting import scatter_plot, save_figure import numpy as np df = load_dataframe('calibration.csv') # columns: concentration, absorbance @@ -250,7 +261,7 @@ save_figure(fig, 'calibration.png', dpi=300) ### Comparing multiple spectra ```python -from modules.plotting import line_plot +from plottle.plotting import line_plot fig, ax = line_plot( wavelength, @@ -266,7 +277,8 @@ fig, ax = line_plot( ## Next Steps -- Work through the **Jupyter notebooks** in `notebooks/` for guided tutorials. -- See `docs/cheatsheet.md` for a quick-reference card of all functions. -- See `DEPLOYMENT.md` for how to run the GUI locally or on Streamlit Cloud. -- Report issues or suggestions via the course discussion board. +- Read the [GUI Guide](tutorials/gui_guide.md) for a tour of every page. +- Read the [CLI Guide](tutorials/cli_guide.md) for scripted and batch workflows. +- Keep the [Cheatsheet](cheatsheet.md) handy as a quick reference to the Python API. +- Found a problem? See [Reporting Bugs](bug-reports.md). +- Want something added? See [Feature Requests](feature_requests.md). diff --git a/docs/index.html b/docs/index.html index 9030297..1418bc8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -4,7 +4,7 @@ Plottle — Scientific Data Visualization for Python - + @@ -222,6 +222,8 @@ background: linear-gradient(135deg, rgba(139,26,43,0.10), rgba(197,160,40,0.08)); border: 1px solid rgba(139,26,43,0.12); } + .feature-card__title a { color: inherit; text-decoration: none; } + .feature-card__title a:hover { text-decoration: underline; } .feature-card__title { font-size: 1.0625rem; font-weight: 700; color: #0f172a; margin-bottom: 0.5rem; @@ -390,6 +392,7 @@ Install API GUI + Docs GitHub → @@ -402,7 +405,7 @@
Schultz Lab · NCCU - 26 plot types + 27 plot types

Scientific data
visualization,
simplified

@@ -543,7 +546,7 @@

Everything in one toolkit

📊
-
26 Plot Types
+
27 Plot Types

Scatter, line, bar, histogram, box, violin, heatmap, contour, 3D surface, regression, and more — in Matplotlib, Seaborn, or interactive Plotly. @@ -791,7 +794,7 @@

Python API

plottle.plotting - 26 plot functions; Matplotlib returns (fig, ax, info), Plotly returns (fig, info) + 27 plot functions; Matplotlib returns (fig, ax, info), Plotly returns (fig, info) plottle.math @@ -862,7 +865,7 @@

14-page Streamlit GUI

2 Quick Plot - 26 plot types, live style controls, annotation panel, Plotly toggle + 27 plot types, live style controls, annotation panel, Plotly toggle 3 @@ -957,6 +960,79 @@

Command-line interface

+ +
+
+

Documentation

+

+ Guides for the GUI, the CLI, and the Python API. +

+
+ +
+
🚀
+ +

+ Install Plottle, load your first dataset, and make your first plot. +

+
+ +
+
🖱️
+
+ GUI Guide +
+

+ A tour of all 14 pages of the Streamlit interface. +

+
+ +
+
⌨️
+
+ CLI Guide +
+

+ The five plottle subcommands, with worked examples. +

+
+ +
+
📖
+ +

+ Quick reference for the Python API — every module, at a glance. +

+
+ +
+
🐛
+ +

+ What to include so a problem can be reproduced and fixed. +

+
+ +
+
💡
+ +

+ How to propose new functionality. +

+
+ +
+
+
+