Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions oceanarray/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -829,13 +829,11 @@ def _stage_token(value: str) -> "int | str":
If *value* is not recognised by :func:`~oceanarray.processors.resolve_stage`.

"""
import argparse as _ap

coerced: int | str = int(value) if value.isdigit() else value
try:
resolve_stage(coerced)
except ValueError as exc:
raise _ap.ArgumentTypeError(str(exc)) from exc
raise argparse.ArgumentTypeError(str(exc)) from exc
return coerced


Expand Down
17 changes: 7 additions & 10 deletions oceanarray/config/report_tokens.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,14 +59,15 @@
# Invariant asserted by the slot-contract test: inches == W_FULL * fraction, so
# display_px / fig_in is identical for every figure and one font size renders at
# one on-screen size everywhere. Test 2 asserts each saved PNG is exactly
# round(inches * FIG_DPI) px wide (1350 / 900 / 810 / 675 / 540 / 450).
# round(inches * FIG_DPI) px wide (1350 / 900 / 810 / 675 / 540 / 450 / 338).
SLOTS: dict[str, tuple[float, float]] = {
"full": (1.0, 9.0),
"twothirds": (2 / 3, 6.0),
"three-fifths": (0.6, 5.4),
"half": (0.5, 4.5),
"two-fifths": (0.4, 3.6),
"third": (1 / 3, 3.0),
"quarter": (0.25, 2.25),
}

# Ergonomic width aliases (inches) for plotter call sites; derived from SLOTS.
Expand All @@ -75,6 +76,7 @@
W_HALF: float = SLOTS["half"][1]
W_TWO_FIFTHS: float = SLOTS["two-fifths"][1]
W_THIRD: float = SLOTS["third"][1]
W_QUARTER: float = SLOTS["quarter"][1]

# Aspect-locked figure constants (spec §14).
SECTION_STRETCH: float = (
Expand Down Expand Up @@ -205,15 +207,10 @@ def pen(name: str) -> float:
"map": "#ee3377",
}

# Package accent (spec §12.2): confined to two additive places so it restyles no
# existing pixel — a small masthead wordmark and the footer's border-top. h2
# underlines stay --seafoam and links stay --ocean.
PACKAGE_ACCENT: dict[str, str] = {
"oceanarray": "#1a3a5c",
"ctdcast": "#0e6e6e",
"caldip": "#7a4b8a", # reserved
"amocatlas": "#8a5a2b", # reserved
}
# The package accent (spec §12.2) — a small masthead wordmark, the table header
# and the footer's border-top — is NOT a value in this vendored file. Each
# package chooses it locally and passes it to ``emit_css(package_accent)`` (see
# each repo's ``reports/_report_css.py``), so no per-package colour is encoded here.

# Neutral gray scale — consolidates the ad-hoc grays that the per-page template
# <style> blocks used (text shades, hairlines, sunken fills). Emitted as
Expand Down
10 changes: 2 additions & 8 deletions oceanarray/plotters/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,8 @@
Provides colormap helpers and rose-diagram rendering used across multiple
Tier-2 plotter modules.

Post-OdB: still to migrate from report/_plots.py:
_instrument_panels, _CANONICAL_PANELS, _COMPACT_PANEL_VARS,
_ts_heatmap_panel, _add_sigma0_contours, _xyz_to_enu_2d.

Also migrate _instrument_label from plotter.py.

Note: _fig_to_base64 stays in report/_html_helpers.py (called only by
Tier-3 wrappers in report/_plots.py; plotters/ never serialises to base64).
Note: _fig_to_base64 stays in reports/_html_helpers.py (called only by
Tier-3 wrappers in reports/_plots.py; plotters/ never serialises to base64).
"""

from __future__ import annotations
Expand Down
2 changes: 1 addition & 1 deletion oceanarray/plotters/timeseries.py
Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ def draw_grid_velocity_stacked(

"""
import matplotlib.pyplot as plt
from ..reports._plots import _velocity_panel_style
from .helpers import _velocity_panel_style

vel_vars = [
"east_velocity",
Expand Down
96 changes: 77 additions & 19 deletions oceanarray/reports/_css.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
"""Shared CSS for the report HTML pages, generated from the design tokens.

``emit_css(package)`` builds the whole stylesheet string from
``emit_css(package_accent)`` builds the whole stylesheet string from
``..config.report_tokens`` — colours, typography, spacing, radii and the slot
table — so no value is restated here. Its output is concatenated into each page
template's ``<style>`` block (``{{ css | safe }}``).

This module is written to be **vendored** across the packages that share the
report design system: it names no package and reads shared values only through
``..config.report_tokens``. Which package's accent to render is an argument to
``emit_css``, supplied by the (package-specific) caller — so the file itself
carries no package-specific edit (Phase A; no cross-repo hash test yet, spec §9).
report design system: it names no package and holds no per-package colour table,
reading shared values only through ``..config.report_tokens``. The accent
colour to render is an argument to ``emit_css``, supplied by the
(package-specific) caller — so the file itself carries no package-specific edit
(Phase A; no cross-repo hash test yet, spec §9).
"""

from __future__ import annotations
Expand All @@ -20,7 +21,6 @@
FONT_MONO,
FONT_SANS,
GRAYS,
PACKAGE_ACCENT,
RADII,
ROLE_ACCENT,
SEMANTIC,
Expand Down Expand Up @@ -69,22 +69,26 @@ def _emit_slots() -> str:
return "\n".join(out)


def emit_css(package: str) -> str:
def emit_css(package_accent: str) -> str:
"""Return the full shared stylesheet as a string, generated from the tokens.

Reproduces the established page look from the design tokens (spec §12–15 v2.0
are the current values, not a redesign). The only additions over
the previous hand-written stylesheet are the package accent's two homes — a
masthead ``.wordmark`` and the footer's ``border-top`` — plus the ``78ch``
reading measure and the structural ``break-inside`` print rules.
reading measure and the ``@media print`` rules for browser printing. (The
PDF renderer additionally injects :func:`print_css`, which layers ``@page``
geometry on top for the WeasyPrint path.)

Parameters
----------
package : str
Which package's accent to render (a key into ``PACKAGE_ACCENT``). Passed
by the package-specific caller; the function itself names no package.
package_accent : str
The accent colour to render as ``--package-accent`` — any CSS colour
value, e.g. a hex literal or ``var(--ocean)``. Chosen by the
package-specific caller; the function itself names no package and holds
no per-package colour table.
"""
root = _emit_root(PACKAGE_ACCENT[package])
root = _emit_root(package_accent)
slots = _emit_slots()
return f"""\
{root}
Expand All @@ -98,15 +102,15 @@ def emit_css(package: str) -> str:
p, li {{ max-width: 78ch; }}
.masthead {{
background: var(--ocean); color: #fff; position: relative;
padding: 1.6rem 2rem; border-radius: 8px; margin-bottom: 2rem;
padding: 1.6rem 2rem; border-radius: var(--radius-card); margin-bottom: 2rem;
}}
.masthead h1 {{ margin: 0 0 0.3rem; font-size: var(--fs-h1); font-weight: 700; }}
.masthead .sub {{ font-size: var(--fs-meta); opacity: 0.85; margin: 0 0 0.15rem; }}
.masthead .sub {{ font-size: var(--fs-meta); opacity: 0.85; margin: 0 0 0.15rem; max-width: none; }}
.wordmark {{
position: absolute; right: 2rem; bottom: 1.2rem;
font-size: var(--fs-dt); font-weight: 700; letter-spacing: 0.04em;
color: var(--package-accent); background: #fff; opacity: 0.85;
padding: 0.1rem 0.5rem; border-radius: 999px; text-decoration: none;
padding: 0.1rem 0.5rem; border-radius: var(--radius-pill); text-decoration: none;
}}
.wordmark:hover {{ opacity: 1; }}
.meta-grid {{
Expand All @@ -130,10 +134,23 @@ def emit_css(package: str) -> str:
text-decoration: none; margin-left: auto;
}}
.top-link:hover {{ color: var(--ocean); text-decoration: underline; }}
.note {{
.caption {{
color: var(--gray-5); font-size: var(--fs-note);
margin-top: -0.5rem; margin-bottom: 0.75rem;
margin-top: 0.75rem; margin-bottom: 0.75rem;
}}
h2 + .caption {{ margin-top: -0.5rem; }}
.explainer {{
font-size: var(--fs-note); color: var(--text);
background: var(--bg-sunken); border-left: 3px solid var(--muted);
padding: var(--sp-3); margin: -0.25rem 0 0.75rem; border-radius: var(--radius-btn);
}}
.warn {{
font-size: var(--fs-note); color: var(--text);
background: var(--warn-bg); border-left: 3px solid var(--warn);
padding: var(--sp-3); margin-bottom: 0.5rem; border-radius: var(--radius-btn);
}}
.warn::before {{ content: "⚠ "; }}
.warn.error {{ border-left-color: var(--error); }}
.jump-nav {{
background: var(--seafoam); padding: 0.55rem 1rem;
border-radius: 6px; margin-bottom: 1.5rem;
Expand All @@ -152,10 +169,26 @@ def emit_css(package: str) -> str:
.fig-col {{ display: flex; flex-direction: column; gap: 0.75rem; }}
figure {{ margin: 0; }}
figure img {{
border: 1px solid var(--rule); border-radius: 4px;
border: 1px solid var(--rule); border-radius: var(--radius-btn);
display: block; width: 100%; height: auto;
}}
figcaption {{ font-size: var(--fs-cap); color: var(--gray-5); margin-top: 0.25rem; }}
table {{
width: 100%; border-collapse: collapse;
font-size: var(--fs-note); margin: 0.6rem 0 1.2rem;
}}
th {{
background: var(--package-accent); color: #fff; text-align: left;
font-weight: 600; padding: 0.4rem 0.65rem;
}}
th.num {{ text-align: right; }}
td {{
padding: 0.35rem 0.65rem; border-bottom: 1px solid var(--rule);
vertical-align: top;
}}
tr:nth-child(even) td {{ background: var(--bg-sunken); }}
td.num, .num {{ text-align: right; font-variant-numeric: tabular-nums; }}
td.mono {{ font-family: var(--font-mono); font-size: var(--fs-xs); }}
{slots}
.masthead-header {{
display: flex; justify-content: space-between; align-items: flex-start;
Expand All @@ -169,7 +202,7 @@ def emit_css(package: str) -> str:
.nav-btns {{ display: flex; gap: 0.5rem; }}
.btn-nav {{
background: var(--ocean); color: #fff; padding: 0.25rem 0.75rem;
border-radius: 999px; text-decoration: none; font-size: var(--fs-nav);
border-radius: var(--radius-pill); text-decoration: none; font-size: var(--fs-nav);
}}
.btn-nav:hover {{ opacity: 0.85; }}
footer {{
Expand All @@ -190,6 +223,31 @@ def emit_css(package: str) -> str:
"""


def print_css(*, terse: bool = False) -> str:
"""Return the print stylesheet, injected at PDF render time (spec §8.1).

These WeasyPrint-specific rules are kept out of the screen stylesheet so
screen output is byte-identical; a PDF renderer appends them at render time.
The shared stylesheet keeps only the structural ``@media print`` rules (page
breaks). With *terse* ``True`` the ``.explainer`` prose is hidden (the
``--pdf-terse`` variant); by default nothing is hidden — captions, explainers
and warnings all print.
"""
css = """\
@page { size: A4; margin: 18mm 16mm 20mm 16mm; }
body { max-width: 100%; padding: 0; }
p, li, .explainer { max-width: 78ch; }
img { max-width: 100%; height: auto; }
.masthead { -webkit-print-color-adjust: exact; print-color-adjust: exact; padding: 0.9rem 1.25rem; }
.masthead-header h1, .masthead h1 { font-size: var(--fs-h1); }
.meta-grid { grid-template-columns: repeat(4, 1fr); gap: 0.3rem 1rem; font-size: var(--fs-xs); }
.jump-nav, .nav-btns, .btn-nav, .top-link { display: none; }
"""
if terse:
css += ".explainer { display: none; }\n"
return css


_JS_TOP_LINKS: str = """\
<script>
document.querySelectorAll('h2').forEach(h => {
Expand Down
8 changes: 4 additions & 4 deletions oceanarray/reports/_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

from . import _figdebug
from . import _slots
from ._css import emit_css
from ._report_css import SHARED_CSS
from .. import parameters as params

#: Directory holding the report page templates.
Expand All @@ -42,9 +42,9 @@
#: Generated stylesheet — the single source of truth for report CSS (tokens,
#: type/spacing scale, slot classes, shared chrome). base.html injects it via
#: ``{{ css | safe }}``; page-specific rules live in a small local block that
#: references these token variables (U0.1). The vendored ``_css.py`` is called,
#: never edited.
_ENV.globals["css"] = emit_css(params.PACKAGE_NAME)
#: references these token variables (U0.1). The accent is applied in the local
#: ``_report_css`` wiring; the vendored ``_css.py`` names no package.
_ENV.globals["css"] = SHARED_CSS


def render_template(name: str, /, **context: Any) -> str:
Expand Down
13 changes: 6 additions & 7 deletions oceanarray/reports/_plots.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
)
from ..plotters.helpers import ( # noqa: F401
_rose_ax,
_velocity_panel_style,
grid_despine,
)
from ..plotters.timeseries import (
Expand Down Expand Up @@ -659,11 +658,11 @@ def _make_speed_boxplot(nc_path: str) -> Optional[str]:
import xarray as xr
from oceanarray.plotters.current import plot_speed_boxplot

def _draw() -> "plt.Figure":
def _draw(*, width_in: float = report_tokens.W_QUARTER) -> "plt.Figure":
with xr.open_dataset(nc_path) as ds:
return plot_speed_boxplot(ds)
return plot_speed_boxplot(ds, width_in=width_in)

return render_b64(_draw, optional=True)
return render_slot(_draw, slot="quarter", optional=True)


def _make_hodograph_b64(nc_path: str) -> Optional[str]:
Expand Down Expand Up @@ -866,7 +865,7 @@ def _make_knockdown_hab_b64(ds: "xr.Dataset") -> Optional[str]:
"""
from oceanarray.plotters.diagnostic import plot_knockdown_hab

return render_b64(plot_knockdown_hab, ds, optional=True)
return render_slot(plot_knockdown_hab, ds, slot="half", optional=True)


def _make_knockdown_displacement_b64(ds: "xr.Dataset") -> Optional[str]:
Expand All @@ -879,7 +878,7 @@ def _make_knockdown_displacement_b64(ds: "xr.Dataset") -> Optional[str]:
"""
from oceanarray.plotters.diagnostic import plot_knockdown_displacement

return render_b64(plot_knockdown_displacement, ds, optional=True)
return render_slot(plot_knockdown_displacement, ds, slot="full", optional=True)


def _make_knockdown_anomaly_b64(ds: "xr.Dataset") -> Optional[str]:
Expand All @@ -893,7 +892,7 @@ def _make_knockdown_anomaly_b64(ds: "xr.Dataset") -> Optional[str]:
"""
from oceanarray.plotters.diagnostic import plot_knockdown_anomaly

return render_b64(plot_knockdown_anomaly, ds, optional=True)
return render_slot(plot_knockdown_anomaly, ds, slot="half", optional=True)


def _make_clock_check_b64(
Expand Down
23 changes: 10 additions & 13 deletions oceanarray/reports/_recovery_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,7 @@ def generate_recovery_table(
"""
from datetime import datetime, timezone

from ..utilities import format_latlon, parse_latlon_with_source
from ._html_helpers import _duration_str

proc_dir = Path(proc_dir)
Expand All @@ -318,19 +319,15 @@ def generate_recovery_table(
deploy_dt = _parse_dt(cfg.get("deployment_time"))
recover_dt = _parse_dt(cfg.get("recovery_time"))

# Latitude/longitude for the canonical meta fields, if available.
lat = (
cfg.get("seabed_latitude")
or cfg.get("deployment_latitude")
or cfg.get("latitude")
)
lon = (
cfg.get("seabed_longitude")
or cfg.get("deployment_longitude")
or cfg.get("longitude")
)
latitude = f"{lat} N" if lat else ""
longitude = f"{lon} W" if lon else ""
# Latitude/longitude for the canonical meta fields, if available. Route through
# the shared parser (DMS-aware, pairs lat/lon from one key set, skips the (0, 0)
# placeholder) then format with the hemisphere derived from the sign — never a
# hardcoded suffix, which produced the malformed "-27.8 W" for signed inputs.
lat_v, lon_v, source = parse_latlon_with_source(cfg)
if source.startswith("unknown"):
latitude = longitude = ""
else:
latitude, longitude = format_latlon(lat_v, lon_v)

rows, comments = _build_rows(proc_dir, mooring_name, cfg)

Expand Down
23 changes: 23 additions & 0 deletions oceanarray/reports/_report_css.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"""oceanarray's concrete report stylesheet, from the package-neutral ``emit_css``.

This is the package-specific wiring the vendored :mod:`oceanarray.reports._css`
deliberately omits: the single place oceanarray's accent is applied to the shared
generator. Another package supplies its own accent the same way; the vendored
``_css.py`` carries no package-specific edit, so re-vendoring it from the sister
repo is a byte-identical copy.
"""

from __future__ import annotations

from ._css import _JS_TOP_LINKS, emit_css

#: oceanarray's package accent — chosen here, locally, not in the vendored tokens.
#: Points at ``--ocean`` (structural navy ``#1a3a5c``) so the accent (wordmark,
#: table header, footer rule) matches the heading colour; change this one line to
#: rebrand. Any CSS colour value works.
PACKAGE_ACCENT: str = "var(--ocean)"

#: The generated stylesheet, ready to concatenate into a page ``<style>`` block.
SHARED_CSS: str = emit_css(PACKAGE_ACCENT)

__all__ = ["SHARED_CSS", "_JS_TOP_LINKS"]
Loading
Loading