Skip to content

[REFACTOR] Report uniformity: emit_css authority, slot-threaded figures, and a figure-sizing/labeling sweep - #76

Merged
eleanorfrajka merged 3 commits into
mainfrom
rep/04-uniform-fixes
Aug 15, 2026
Merged

[REFACTOR] Report uniformity: emit_css authority, slot-threaded figures, and a figure-sizing/labeling sweep#76
eleanorfrajka merged 3 commits into
mainfrom
rep/04-uniform-fixes

Conversation

@eleanorfrajka

Copy link
Copy Markdown
Collaborator

Summary

This branch makes more fixes to ensure uniform appearance of figures. (In some ways, it is prescribing figure sizes the way GMT would do it, specifying the axes sizes rather than figsizes, but does this by computing the sizes.) We make one slot decision travel from the template to the figure and back, put the stylesheet under a single generator, and route the recurring figure-layout patterns (square panels + matched colorbar, grid + spines, per-variable colours, axis labels) through shared helpers so they can't diverge again.

The slot pipeline (the systemic core)

emit_css() is the single stylesheet. base.html now injects {{ css }} from emit_css(PACKAGE_NAME) plus a small oceanarray-local block of classes/vars that reference the shared tokens. The 73 raw hex colours inside inline style="…" attributes were swept to var(--…) tokens across all five templates (chrome → shared tokens; QC-flag legend swatches + the stage-1 line colour → named local vars, since they mirror plot colormaps). A new test forbids raw hex in inline styles so it can't regress.

The slot travels with the figure. New package-local reports/_slots.py: render(draw, *, slot) resolves SLOTS[slot]→width_in, forwards it to the draw function, and records the slot under the returned PNG; the slot_for(b64) Jinja global reads it back so the template picks its .slot-* width class from the same slot the figure was rendered at. All ~34 report draw_* functions gained a keyword-only width_in (they used to hardcode figsize=(W_FULL/W_HALF/…)), so the width is now an argument, not a constant baked in the plotter. The vendored render_b64 is untouched — width is resolved in the package-local adapter before the call. Non-full figures (T-S grid, grid trajectory, speed profile) now render at their true display width (e.g. 675 px for a half slot) instead of rendering full and letting the browser downscale.

A CSS bug this exposed: page-level .fig { width:100% } blocks in the grid/stack/instrument page_styles were overriding the .slot-* width and forcing every figure full-width; removed (the border/radius/margin kept). This is why "T-S / speed profile / trajectory went full width" — one fix resolved all three.

Geometry regression guard. test_png_geometry_on_rendered_page asserts every class="fig slot-*" figure's PNG width equals round(SLOTS[slot]·FIG_DPI). Had this existed, it would have caught the width:100% bug above. The debug view (OCEANARRAY_REPORT_DEBUG=1) now shows each figure's recorded slot next to its rendered figsize, so a slot-vs-render mismatch is visible.

Figure uniformity

Grid + spines, uniformly. Every grid-on figure now goes through plotters.helpers.grid_despine(ax) (grid on + top/right spines off, appearance from the mplstyle) — ~20 sites across plotters/ and reports/ that previously hardcoded a dashed grid and left the frame closed. grid_despine gained an axis= argument for the one-directional (bar/profile) cases.

Square panels with matched colorbars. The mooring knockdown-displacement plot is rebuilt on the square_axes_grid primitive (deterministic inch-based layout, colorbar in its own reserved column) so both panels are equal squares and the colorbar height matches by construction — replacing a set_box_aspect(1) + inline-colorbar combination that shrank the colorbar-bearing panel. square_axes_grid gained optional wgap_in / cbar_txt_in overrides for the shared-y / long-label case.

Other figure fixes: the current-rose grid is now a fixed 4-wide, vary-rows, full-width layout (uniform rose size, no dynamic width); the wavelet scalogram/timeseries heights were retuned; the buoyancy-frequency (N²) colorbar is clipped to 2.5–97.5 %; current-direction uses the cyclic twilight colormap (was hsv); the stack Aquadopp-tilt suptitle no longer overprints the top panel's title; the data-value-distribution histograms colour their "kept" bars with the variable's own VAR_COLORS line colour (falling back to blue).

Registries, labels, and metadata

  • vlabel() now renders (1) (the CF/UDUNITS dimensionless unit, matching the NetCDF units="1") for known dimensionless variables such as practical salinity; unknown variables stay bare. The T-S salinity axis routes through it.
  • Correctness: magnetic declination's units attribute was "degrees_east" (the CF unit for longitude) — corrected to "degree", with a separate magnetic_declination_sign_convention="positive_east" attribute for the sign.
  • The report's global-attribute table rounds nanosecond-precision timestamps (e.g. deployment_time) to 0.1 s for display; the saved file is unchanged.
  • The masthead wordmark is now a link to github.com/ocean-uhh/oceanarray.

Testing

767 pass, 8 skipped, ruff + format clean. Golden HTML fixtures re-baselined for the intended structural/label changes (slot classes, wordmark, rounded timestamps). New enforcement tests: test_png_geometry_on_rendered_page (slot width == PNG width) and test_no_raw_hex_in_template_inline_styles.

Breaking changes

  • Output metadata: the magnetic_declination_units global attribute changes from "degrees_east" to "degree", and a new magnetic_declination_sign_convention="positive_east" attribute is written. Downstream code reading the old value/unit must update. (Existing NetCDF files are not rewritten; the change applies to newly processed data.)
  • vlabel() behaviour: returns "Label (1)" for a known dimensionless variable (previously "Label" with no unit). Callers that string-matched the old output must update.
  • plot_multi_aquadopp_trajectories: the unused hab_var parameter was removed (the trajectory labels were simplified to the bare serial). Callers passing hab_var= must drop it.
  • Report draw_* signatures (additive, non-breaking): every report draw function gained a keyword-only width_in with a default — existing positional calls are unaffected.

Reviewer notes

  • The vendored files (config/report_tokens.py, config/report.mplstyle, reports/_encode.py, reports/_css.py) are unchanged — the width resolution lives in the package-local _slots.py adapter so the encoder signature stays frozen.
  • Golden masks PNG payloads, so figure-pixel changes are reviewed by eye; the geometry test covers PNG width numerically.
  • A short list of follow-ups (the remaining skipped enforcement-test stubs, a quarter-width slot, a shared table style, suptitle-spacing generalisation, and a docs example report) is tracked for the next branch.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors the report rendering pipeline to enforce uniform figure sizing/layout and CSS styling across report templates by centralizing stylesheet generation and making figure rendering “slot-aware” (render at the same width the template will display).

Changes:

  • Centralizes report CSS via emit_css() injected through base.html, and replaces inline raw hex colors with CSS variables (plus tests to prevent regression).
  • Introduces a slot pipeline (reports/_slots.py) so figures render at the correct display width and templates choose matching .slot-* classes using recorded slot metadata.
  • Sweeps plotting helpers and primitives to unify grid/spine styling and standardize figure geometry (square panels, matched colorbars, consistent widths).

Reviewed changes

Copilot reviewed 28 out of 28 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/unit/test_report_tokens.py Adds enforcement test to forbid raw hex colors in inline style="..." attributes.
tests/unit/test_report_golden.py Adds rendered-page PNG geometry invariant test for slot-classed figures.
tests/fixtures/golden/dune2/instrument/dune2_1_2026_9920_report.html Re-baselines golden HTML for new CSS injection, slot classes, and inline-style token usage.
tests/fixtures/golden/dune2/instrument/dune2_1_2026_2941_report.html Re-baselines golden HTML for new CSS injection, slot classes, and inline-style token usage.
tests/fixtures/golden/dune2/dune2_1_2026_stack_report.html Re-baselines golden HTML for slot-driven figure sizing and tokenized inline styles.
tests/fixtures/golden/dune2/dune2_1_2026_report.html Re-baselines golden HTML for tokenized inline styles and timestamp display rounding.
tests/fixtures/golden/dune2/dune2_1_2026_grid_report.html Re-baselines golden HTML for slot-driven sizing and tokenized inline styles.
oceanarray/reports/templates/stack.html Removes width overrides and switches selected figures to slot-based sizing.
oceanarray/reports/templates/mooring.html Replaces inline raw hex colors with CSS variables; updates figure borders to token vars.
oceanarray/reports/templates/instrument.html Removes width:100% override so slot widths can take effect; tokenizes inline colors.
oceanarray/reports/templates/grid.html Removes width:100% overrides and switches certain figures to slot-based sizing.
oceanarray/reports/templates/base.html Injects shared generated CSS (`{{ css
oceanarray/reports/_stack.py Uses shared helper styling (grid_despine) and adjusts figure sizing/annotations.
oceanarray/reports/_slots.py Adds new slot adapter to render figures at slot width and expose slot metadata to templates.
oceanarray/reports/_plots.py Routes multiple figure render paths through the new slot-aware renderer.
oceanarray/reports/_html_helpers.py Rounds nanosecond-precision ISO timestamps for report display.
oceanarray/reports/_env.py Registers new Jinja globals (slot_for, css) for slot pipeline and CSS injection.
oceanarray/reports/_array.py Applies unified grid/spine styling via grid_despine.
oceanarray/processors/coordinate.py Corrects magnetic declination units metadata and adds sign convention attribute.
oceanarray/plotters/ts.py Threads width_in through T–S plotting functions to support slot-sized rendering.
oceanarray/plotters/timeseries.py Threads width_in through grid/time-series plotters; standardizes grid styling.
oceanarray/plotters/spectrum.py Threads width_in through spectrum/wavelet plotters and applies unified styling.
oceanarray/plotters/primitives.py Extends square_axes_grid controls and threads width_in into trajectory plotting.
oceanarray/plotters/hydrography.py Threads width_in into hydrography plots and standardizes grid styling.
oceanarray/plotters/helpers.py Enhances grid_despine (axis selection) and switches direction colormap to twilight.
oceanarray/plotters/diagnostic.py Rebuilds knockdown displacement layout using square_axes_grid and threads width_in.
oceanarray/plotters/current.py Threads width_in through current-related plotters and removes unused hab_var parameter.
oceanarray/config/parameters.py Updates vlabel() to emit (1) for known dimensionless variables and preserves unknowns.
Suppressed comments (1)

oceanarray/reports/_env.py:51

  • The slot/debug registries (_slots._SLOT_BY_B64 and _figdebug._COLLECTED) are process-global and never cleared, so building many report pages in one run can accumulate a large amount of base64-keyed state and potentially leak across pages. Consider clearing both registries after each template render (or in a per-page build boundary) once the HTML string has been produced.
_ENV.globals["css"] = emit_css(params.PACKAGE_NAME)


def render_template(name: str, /, **context: Any) -> str:
    """Render report template *name* with *context* and return the HTML string.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread oceanarray/reports/_html_helpers.py Outdated
@eleanorfrajka
eleanorfrajka merged commit 69d3ab6 into main Aug 15, 2026
5 checks passed
@eleanorfrajka
eleanorfrajka deleted the rep/04-uniform-fixes branch August 15, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants