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.
210 lines
7.4 KiB
Python
210 lines
7.4 KiB
Python
"""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",
|
||
}
|