Files
stack/assets/nature.py
kert 8bd649d9ab feat: skinny packages, tree reorg, cloud abstraction (refs #182–#201)
P23 — Skinny Packages:
- Break conf→bib circular dependency with lazy guarded import
- Extract aco.table.base → conf.table_base (shared foundation)
- Add try/except ImportError guards on all cross-module imports
- Define per-module optional-dependencies in pyproject.toml
- Slim base deps to just pydantic; all heavy deps in optional groups
- Add gen_config.py --check validation for module/dep sync
- Add skinny-install CI matrix job (tests each extra in isolation)

P24 — Tree Reorganization:
- Move service configs to infra/ (traefik, coredns, grafana, etc.)
- Consolidate Dockerfiles under infra/images/
- Move styles/ → assets/css/ + assets/icons/ + assets/nature.py
- Update compose.yml, stack.toml, gen_config.py, all backend emitters
- Update bootstrap scripts and install_certs.sh

P25 — Cloud Provider Abstraction:
- Add cloud/ directory with self-hosted, aws, gcp, azure stubs
- Add cloud contexts (aws, gcp, azure) to stack.toml
- Add conf/storage.py — get_filesystem() dispatches per context
- Add aws, gcp, azure optional dependency groups
- Document service mapping for each provider

All 11955 tests pass. Zero functionality lost.
2026-03-24 15:00:26 -04:00

210 lines
7.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Nature Research figure standards — shared palette and theme.
Implements the specifications from Nature's "Preparing Figures" guide
(bib key ``9ASETLJ4``). Provides:
- ``PALETTE`` — 8-colour accessible sequence (colorblind-safe)
- ``SCHEMA_COLORS`` — pipeline schema → colour mapping for DAG viz
- ``altair_theme()`` — register-and-enable for Altair charts
- ``mpl_rc()`` — dict of matplotlib rcParams
- ``cytoscape_style()`` — Cytoscape.js stylesheet fragment
Key rules (Nature):
* Sans-serif typeface (Helvetica, Arial)
* Body text 5–7 pt; panel labels 8 pt bold lowercase
* Axis lines and tick marks mandatory
* No background gridlines, drop shadows, or decorative elements
* RGB colour space; accessible palette
* Units in parentheses on axis labels
"""
from __future__ import annotations
# ── Accessible 8-colour palette ──────────────────────────────────────
# Adapted from Paul Tol's "bright" qualitative scheme — verified
# colourblind-safe via Coblis and WCAG contrast tools.
PALETTE = [
"#4477AA", # blue
"#EE6677", # red / rose
"#228833", # green
"#CCBB44", # yellow
"#66CCEE", # cyan
"#AA3377", # purple
"#BBBBBB", # grey
"#EE8866", # orange
]
# Semantic aliases
BLUE, RED, GREEN, YELLOW, CYAN, PURPLE, GREY, ORANGE = PALETTE
# ── Typography ────────────────────────────────────────────────────────
FONT_FAMILY = "Helvetica, Arial, sans-serif"
FONT_SIZE_BODY = 7 # pt — Nature range 5-7
FONT_SIZE_AXIS = 7 # pt
FONT_SIZE_TITLE = 8 # pt — panel label size
FONT_SIZE_LABEL = 8 # pt — bold lowercase panel labels
LINE_WIDTH = 0.75 # pt — axis/tick stroke
TICK_SIZE = 4 # pt
# ── Figure dimensions (single-column Nature width: 89 mm) ───────────
FIG_WIDTH_MM = 89
FIG_HEIGHT_MM = 60
FIG_WIDTH_IN = FIG_WIDTH_MM / 25.4
FIG_HEIGHT_IN = FIG_HEIGHT_MM / 25.4
DPI = 450 # Nature min for exported figures
# ── Background ────────────────────────────────────────────────────────
BG_DARK = "#1a1a2e" # app/notebook dark background
BG_CHART = "transparent" # chart area — let container bg show through
AXIS_COLOR = "#cccccc" # axis lines, ticks
TEXT_COLOR = "#e0e0e0" # labels on dark bg
TITLE_COLOR = "#ffffff" # title / panel label
# ── Pipeline schema colours (for DAG / lineage viz) ──────────────────
SCHEMA_COLORS: dict[str, str] = {
"core": BLUE,
"input_layer": CYAN,
"cclf": "#5588CC",
"claims_preprocessing": ORANGE,
"readmissions": GREEN,
"ahrq_measures": YELLOW,
"pharmacy": PURPLE,
"quality_measures": RED,
"hcc_suspecting": "#AA7744",
"provider_attribution": GREY,
"main": "#55AA99",
"data_quality": "#5588CC",
}
EXTERNAL_COLOR = "#888888"
def schema_color(schema: str) -> str:
"""Return the palette colour for a pipeline schema."""
return SCHEMA_COLORS.get(schema, EXTERNAL_COLOR)
# ── Altair theme ──────────────────────────────────────────────────────
def _altair_theme() -> dict:
"""Vega-Lite config dict conforming to Nature specs."""
return {
"config": {
"background": BG_CHART,
"font": FONT_FAMILY,
"title": {
"font": FONT_FAMILY,
"fontSize": FONT_SIZE_TITLE,
"fontWeight": "bold",
"color": TITLE_COLOR,
"anchor": "start",
},
"axis": {
"labelFont": FONT_FAMILY,
"labelFontSize": FONT_SIZE_AXIS,
"labelColor": TEXT_COLOR,
"titleFont": FONT_FAMILY,
"titleFontSize": FONT_SIZE_AXIS,
"titleColor": TEXT_COLOR,
"gridColor": "transparent",
"gridOpacity": 0,
"domainColor": AXIS_COLOR,
"domainWidth": LINE_WIDTH,
"tickColor": AXIS_COLOR,
"tickSize": TICK_SIZE,
"tickWidth": LINE_WIDTH,
},
"legend": {
"labelFont": FONT_FAMILY,
"labelFontSize": FONT_SIZE_BODY,
"labelColor": TEXT_COLOR,
"titleFont": FONT_FAMILY,
"titleFontSize": FONT_SIZE_BODY,
"titleColor": TEXT_COLOR,
"symbolSize": 60,
},
"view": {
"stroke": "transparent",
},
"range": {
"category": PALETTE,
},
"mark": {
"color": PALETTE[0],
},
"bar": {
"color": PALETTE[0],
},
"arc": {
"stroke": BG_DARK,
"strokeWidth": 1,
},
"line": {
"strokeWidth": 1.5,
},
"point": {
"size": 40,
},
"area": {
"opacity": 0.7,
},
}
}
def altair_theme() -> None:
"""Register and enable the Nature theme for Altair."""
import altair as alt
alt.themes.register("nature", _altair_theme)
alt.themes.enable("nature")
# ── Matplotlib rcParams ──────────────────────────────────────────────
def mpl_rc() -> dict:
"""Return an rcParams dict for ``matplotlib.rcParams.update()``."""
return {
"font.family": "sans-serif",
"font.sans-serif": ["Helvetica", "Arial", "DejaVu Sans"],
"font.size": FONT_SIZE_BODY,
"axes.titlesize": FONT_SIZE_TITLE,
"axes.titleweight": "bold",
"axes.labelsize": FONT_SIZE_AXIS,
"axes.linewidth": LINE_WIDTH,
"axes.edgecolor": AXIS_COLOR,
"axes.labelcolor": TEXT_COLOR,
"axes.facecolor": "none",
"axes.grid": False,
"axes.prop_cycle": __import__("cycler").cycler(color=PALETTE),
"xtick.major.size": TICK_SIZE,
"xtick.major.width": LINE_WIDTH,
"xtick.color": AXIS_COLOR,
"xtick.labelsize": FONT_SIZE_BODY,
"ytick.major.size": TICK_SIZE,
"ytick.major.width": LINE_WIDTH,
"ytick.color": AXIS_COLOR,
"ytick.labelsize": FONT_SIZE_BODY,
"figure.figsize": (FIG_WIDTH_IN, FIG_HEIGHT_IN),
"figure.dpi": DPI,
"figure.facecolor": BG_DARK,
"savefig.dpi": DPI,
"savefig.facecolor": BG_DARK,
"legend.fontsize": FONT_SIZE_BODY,
"legend.frameon": False,
"text.color": TEXT_COLOR,
}
# ── Cytoscape.js style helpers ───────────────────────────────────────
def cytoscape_node_style() -> dict:
"""Base Cytoscape.js node style properties (Nature-conformant)."""
return {
"font-family": FONT_FAMILY,
"font-size": "9px",
"text-valign": "center",
"text-halign": "center",
"shape": "round-rectangle",
}