Files
stack/styles/nature.py
kert 33701365be
Some checks failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/infra-ci Pipeline failed
ci/woodpecker/push/infra-ci Pipeline was successful
coverage 99% coverage
ci/woodpecker/pr/ci Pipeline was successful
add Nature figure standards: palette, Altair theme, DAG colors
Create styles/nature.py with accessible 8-colour palette (Paul Tol
bright scheme), Altair theme registration, matplotlib rcParams, and
Cytoscape style helpers — all conforming to Nature Research figure
specifications (bib citation 9ASETLJ4).

Retrofit acodb_explorer.py: replace all hardcoded set2/hex colors
with PALETTE references, enable the Nature Altair theme on import.

Update src/aco/dag.py to import schema colors from the shared
styles/nature.py module instead of maintaining a separate Tableau 10
palette.

fix #45, fix #46, refs #47
2026-03-21 13:55:53 -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",
}