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
This commit is contained in:
@@ -27,12 +27,18 @@ def _(mo):
|
|||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _():
|
def _():
|
||||||
|
import sys
|
||||||
|
sys.path.insert(0, "../styles")
|
||||||
|
|
||||||
import altair as alt
|
import altair as alt
|
||||||
import duckdb
|
import duckdb
|
||||||
import polars as pl
|
import polars as pl
|
||||||
|
from nature import PALETTE, altair_theme
|
||||||
|
|
||||||
from conf import path as _conf_path
|
from conf import path as _conf_path
|
||||||
|
|
||||||
|
altair_theme()
|
||||||
|
|
||||||
DB_PATH = str(_conf_path("db.aco"))
|
DB_PATH = str(_conf_path("db.aco"))
|
||||||
con = duckdb.connect(DB_PATH, read_only=True)
|
con = duckdb.connect(DB_PATH, read_only=True)
|
||||||
|
|
||||||
@@ -40,7 +46,7 @@ def _():
|
|||||||
"""Run a query and return a Polars DataFrame."""
|
"""Run a query and return a Polars DataFrame."""
|
||||||
return con.execute(sql).pl()
|
return con.execute(sql).pl()
|
||||||
|
|
||||||
return alt, pl, q
|
return PALETTE, alt, pl, q
|
||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
@@ -93,7 +99,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
patients = q(
|
patients = q(
|
||||||
"SELECT sex, race, state, age, age_group FROM core.patient WHERE state IS NOT NULL"
|
"SELECT sex, race, state, age, age_group FROM core.patient WHERE state IS NOT NULL"
|
||||||
)
|
)
|
||||||
@@ -103,7 +109,7 @@ def _(alt, mo, q):
|
|||||||
.mark_arc(innerRadius=50)
|
.mark_arc(innerRadius=50)
|
||||||
.encode(
|
.encode(
|
||||||
theta=alt.Theta("count():Q"),
|
theta=alt.Theta("count():Q"),
|
||||||
color=alt.Color("sex:N", scale=alt.Scale(scheme="set2")),
|
color=alt.Color("sex:N", scale=alt.Scale(range=PALETTE)),
|
||||||
tooltip=["sex:N", "count():Q"],
|
tooltip=["sex:N", "count():Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Sex Distribution", width=250, height=250)
|
.properties(title="Sex Distribution", width=250, height=250)
|
||||||
@@ -115,7 +121,7 @@ def _(alt, mo, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("count():Q", title="Patients"),
|
x=alt.X("count():Q", title="Patients"),
|
||||||
y=alt.Y("race:N", sort="-x", title=None),
|
y=alt.Y("race:N", sort="-x", title=None),
|
||||||
color=alt.Color("race:N", scale=alt.Scale(scheme="set2"), legend=None),
|
color=alt.Color("race:N", scale=alt.Scale(range=PALETTE), legend=None),
|
||||||
tooltip=["race:N", "count():Q"],
|
tooltip=["race:N", "count():Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Race Distribution", width=350, height=250)
|
.properties(title="Race Distribution", width=350, height=250)
|
||||||
@@ -126,14 +132,14 @@ def _(alt, mo, q):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, patients):
|
def _(PALETTE, alt, mo, patients):
|
||||||
age_chart = (
|
age_chart = (
|
||||||
alt.Chart(patients.to_pandas())
|
alt.Chart(patients.to_pandas())
|
||||||
.mark_bar()
|
.mark_bar()
|
||||||
.encode(
|
.encode(
|
||||||
x=alt.X("age:Q", bin=alt.Bin(step=5), title="Age"),
|
x=alt.X("age:Q", bin=alt.Bin(step=5), title="Age"),
|
||||||
y=alt.Y("count():Q", title="Patients"),
|
y=alt.Y("count():Q", title="Patients"),
|
||||||
color=alt.Color("sex:N", scale=alt.Scale(scheme="set2")),
|
color=alt.Color("sex:N", scale=alt.Scale(range=PALETTE)),
|
||||||
tooltip=["count():Q"],
|
tooltip=["count():Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Age Distribution by Sex", width=600, height=300)
|
.properties(title="Age Distribution by Sex", width=600, height=300)
|
||||||
@@ -151,7 +157,7 @@ def _(alt, mo, patients):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("len:Q", title="Patients"),
|
x=alt.X("len:Q", title="Patients"),
|
||||||
y=alt.Y("state:N", sort="-x", title=None),
|
y=alt.Y("state:N", sort="-x", title=None),
|
||||||
color=alt.value("#66c2a5"),
|
color=alt.value(PALETTE[2]),
|
||||||
tooltip=["state:N", "len:Q"],
|
tooltip=["state:N", "len:Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Top 15 States", width=350, height=300)
|
.properties(title="Top 15 States", width=350, height=300)
|
||||||
@@ -170,7 +176,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
enc_types = q("""
|
enc_types = q("""
|
||||||
SELECT encounter_type, count(*) AS encounters
|
SELECT encounter_type, count(*) AS encounters
|
||||||
FROM core.encounter
|
FROM core.encounter
|
||||||
@@ -184,7 +190,7 @@ def _(alt, mo, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("encounters:Q", title="Count"),
|
x=alt.X("encounters:Q", title="Count"),
|
||||||
y=alt.Y("encounter_type:N", sort="-x", title=None),
|
y=alt.Y("encounter_type:N", sort="-x", title=None),
|
||||||
color=alt.value("#fc8d62"),
|
color=alt.value(PALETTE[7]),
|
||||||
tooltip=["encounter_type:N", "encounters:Q"],
|
tooltip=["encounter_type:N", "encounters:Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Encounters by Type", width=600, height=450)
|
.properties(title="Encounters by Type", width=600, height=450)
|
||||||
@@ -195,7 +201,7 @@ def _(alt, mo, q):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
enc_monthly = q("""
|
enc_monthly = q("""
|
||||||
SELECT strftime(encounter_start_date, '%Y-%m') AS month,
|
SELECT strftime(encounter_start_date, '%Y-%m') AS month,
|
||||||
encounter_type,
|
encounter_type,
|
||||||
@@ -216,7 +222,7 @@ def _(alt, mo, q):
|
|||||||
x=alt.X("month:T", title="Month"),
|
x=alt.X("month:T", title="Month"),
|
||||||
y=alt.Y("encounters:Q", title="Encounters"),
|
y=alt.Y("encounters:Q", title="Encounters"),
|
||||||
color=alt.Color(
|
color=alt.Color(
|
||||||
"encounter_type:N", title="Type", scale=alt.Scale(scheme="set2")
|
"encounter_type:N", title="Type", scale=alt.Scale(range=PALETTE)
|
||||||
),
|
),
|
||||||
tooltip=["month:T", "encounter_type:N", "encounters:Q"],
|
tooltip=["month:T", "encounter_type:N", "encounters:Q"],
|
||||||
)
|
)
|
||||||
@@ -236,7 +242,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
chronic = q("""
|
chronic = q("""
|
||||||
SELECT condition, count(DISTINCT person_id) AS patients
|
SELECT condition, count(DISTINCT person_id) AS patients
|
||||||
FROM chronic_conditions.chronic_conditions_long
|
FROM chronic_conditions.chronic_conditions_long
|
||||||
@@ -251,7 +257,7 @@ def _(alt, mo, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("patients:Q", title="Patients"),
|
x=alt.X("patients:Q", title="Patients"),
|
||||||
y=alt.Y("condition:N", sort="-x", title=None),
|
y=alt.Y("condition:N", sort="-x", title=None),
|
||||||
color=alt.Color("patients:Q", scale=alt.Scale(scheme="reds"), legend=None),
|
color=alt.Color("patients:Q", scale=alt.Scale(range=[PALETTE[1] + "33", PALETTE[1]]), legend=None),
|
||||||
tooltip=["condition:N", "patients:Q"],
|
tooltip=["condition:N", "patients:Q"],
|
||||||
)
|
)
|
||||||
.properties(
|
.properties(
|
||||||
@@ -285,7 +291,7 @@ def _(mo, q):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, comorbidity):
|
def _(PALETTE, alt, comorbidity):
|
||||||
burden_chart = (
|
burden_chart = (
|
||||||
alt.Chart(comorbidity.to_pandas())
|
alt.Chart(comorbidity.to_pandas())
|
||||||
.mark_bar()
|
.mark_bar()
|
||||||
@@ -296,7 +302,7 @@ def _(alt, comorbidity):
|
|||||||
title="Number of Chronic Conditions",
|
title="Number of Chronic Conditions",
|
||||||
),
|
),
|
||||||
y=alt.Y("count():Q", title="Patients"),
|
y=alt.Y("count():Q", title="Patients"),
|
||||||
color=alt.value("#e78ac3"),
|
color=alt.value(PALETTE[5]),
|
||||||
tooltip=["count():Q"],
|
tooltip=["count():Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Distribution of Comorbidity Burden", width=600, height=300)
|
.properties(title="Distribution of Comorbidity Burden", width=600, height=300)
|
||||||
@@ -315,7 +321,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, pl, q):
|
def _(PALETTE, alt, mo, pl, q):
|
||||||
risk = q("""
|
risk = q("""
|
||||||
SELECT r.person_id, r.payment_risk_score, r.v24_risk_score,
|
SELECT r.person_id, r.payment_risk_score, r.v24_risk_score,
|
||||||
r.normalized_risk_score, r.member_months,
|
r.normalized_risk_score, r.member_months,
|
||||||
@@ -345,7 +351,7 @@ def _(alt, mo, pl, q):
|
|||||||
title="Payment Risk Score",
|
title="Payment Risk Score",
|
||||||
),
|
),
|
||||||
y=alt.Y("count():Q", title="Patients"),
|
y=alt.Y("count():Q", title="Patients"),
|
||||||
color=alt.Color("sex:N", scale=alt.Scale(scheme="set2")),
|
color=alt.Color("sex:N", scale=alt.Scale(range=PALETTE)),
|
||||||
tooltip=["count():Q"],
|
tooltip=["count():Q"],
|
||||||
)
|
)
|
||||||
.properties(title="HCC Payment Risk Score Distribution", width=600, height=300)
|
.properties(title="HCC Payment Risk Score Distribution", width=600, height=300)
|
||||||
@@ -357,7 +363,7 @@ def _(alt, mo, pl, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("age:Q", title="Age"),
|
x=alt.X("age:Q", title="Age"),
|
||||||
y=alt.Y("payment_risk_score:Q", title="Payment Risk Score"),
|
y=alt.Y("payment_risk_score:Q", title="Payment Risk Score"),
|
||||||
color=alt.Color("sex:N", scale=alt.Scale(scheme="set2")),
|
color=alt.Color("sex:N", scale=alt.Scale(range=PALETTE)),
|
||||||
size=alt.Size(
|
size=alt.Size(
|
||||||
"member_months:Q", scale=alt.Scale(range=[20, 200]), legend=None
|
"member_months:Q", scale=alt.Scale(range=[20, 200]), legend=None
|
||||||
),
|
),
|
||||||
@@ -396,7 +402,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, pl, q):
|
def _(PALETTE, alt, mo, pl, q):
|
||||||
pmpm = q("""
|
pmpm = q("""
|
||||||
SELECT year_month,
|
SELECT year_month,
|
||||||
member_months,
|
member_months,
|
||||||
@@ -436,7 +442,7 @@ def _(alt, mo, pl, q):
|
|||||||
x=alt.X("year_month:T", title="Month"),
|
x=alt.X("year_month:T", title="Month"),
|
||||||
y=alt.Y("pmpm:Q", stack=True, title="PMPM ($)"),
|
y=alt.Y("pmpm:Q", stack=True, title="PMPM ($)"),
|
||||||
color=alt.Color(
|
color=alt.Color(
|
||||||
"category:N", title="Category", scale=alt.Scale(scheme="set2")
|
"category:N", title="Category", scale=alt.Scale(range=PALETTE)
|
||||||
),
|
),
|
||||||
tooltip=[
|
tooltip=[
|
||||||
"year_month:T",
|
"year_month:T",
|
||||||
@@ -449,7 +455,7 @@ def _(alt, mo, pl, q):
|
|||||||
|
|
||||||
total_line = (
|
total_line = (
|
||||||
alt.Chart(pmpm.to_pandas())
|
alt.Chart(pmpm.to_pandas())
|
||||||
.mark_line(color="#e41a1c", strokeWidth=2, point=True)
|
.mark_line(color=PALETTE[1], strokeWidth=2, point=True)
|
||||||
.encode(
|
.encode(
|
||||||
x=alt.X("year_month:T", title="Month"),
|
x=alt.X("year_month:T", title="Month"),
|
||||||
y=alt.Y("total_pmpm:Q", title="Total PMPM ($)"),
|
y=alt.Y("total_pmpm:Q", title="Total PMPM ($)"),
|
||||||
@@ -471,7 +477,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, pl, q):
|
def _(PALETTE, alt, mo, pl, q):
|
||||||
qm = q("""
|
qm = q("""
|
||||||
SELECT measure_id, measure_name, denominator_sum, numerator_sum,
|
SELECT measure_id, measure_name, denominator_sum, numerator_sum,
|
||||||
exclusion_sum, performance_rate
|
exclusion_sum, performance_rate
|
||||||
@@ -501,8 +507,8 @@ def _(alt, mo, pl, q):
|
|||||||
y=alt.Y("measure_name:N", sort="-x", title=None),
|
y=alt.Y("measure_name:N", sort="-x", title=None),
|
||||||
color=alt.condition(
|
color=alt.condition(
|
||||||
alt.datum.performance_rate > 50,
|
alt.datum.performance_rate > 50,
|
||||||
alt.value("#66c2a5"),
|
alt.value(PALETTE[2]),
|
||||||
alt.value("#fc8d62"),
|
alt.value(PALETTE[1]),
|
||||||
),
|
),
|
||||||
tooltip=[
|
tooltip=[
|
||||||
"measure_id:N",
|
"measure_id:N",
|
||||||
@@ -533,7 +539,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
ed = q("""
|
ed = q("""
|
||||||
SELECT ed_classification_description AS ed_classification,
|
SELECT ed_classification_description AS ed_classification,
|
||||||
count(*) AS visits,
|
count(*) AS visits,
|
||||||
@@ -552,7 +558,7 @@ def _(alt, mo, q):
|
|||||||
color=alt.Color(
|
color=alt.Color(
|
||||||
"ed_classification:N",
|
"ed_classification:N",
|
||||||
title="Classification",
|
title="Classification",
|
||||||
scale=alt.Scale(scheme="set2"),
|
scale=alt.Scale(range=PALETTE),
|
||||||
),
|
),
|
||||||
tooltip=[
|
tooltip=[
|
||||||
"ed_classification:N",
|
"ed_classification:N",
|
||||||
@@ -576,7 +582,7 @@ def _(alt, mo, q):
|
|||||||
),
|
),
|
||||||
y=alt.Y("avg_paid:Q", title="Avg Paid ($)"),
|
y=alt.Y("avg_paid:Q", title="Avg Paid ($)"),
|
||||||
color=alt.Color(
|
color=alt.Color(
|
||||||
"ed_classification:N", scale=alt.Scale(scheme="set2"), legend=None
|
"ed_classification:N", scale=alt.Scale(range=PALETTE), legend=None
|
||||||
),
|
),
|
||||||
tooltip=["ed_classification:N", alt.Tooltip("avg_paid:Q", format="$,.0f")],
|
tooltip=["ed_classification:N", alt.Tooltip("avg_paid:Q", format="$,.0f")],
|
||||||
)
|
)
|
||||||
@@ -590,7 +596,7 @@ def _(alt, mo, q):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
ed_dx = q("""
|
ed_dx = q("""
|
||||||
SELECT primary_diagnosis_description AS diagnosis, count(*) AS visits
|
SELECT primary_diagnosis_description AS diagnosis, count(*) AS visits
|
||||||
FROM ed_classification.summary
|
FROM ed_classification.summary
|
||||||
@@ -605,7 +611,7 @@ def _(alt, mo, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("visits:Q", title="ED Visits"),
|
x=alt.X("visits:Q", title="ED Visits"),
|
||||||
y=alt.Y("diagnosis:N", sort="-x", title=None),
|
y=alt.Y("diagnosis:N", sort="-x", title=None),
|
||||||
color=alt.value("#8da0cb"),
|
color=alt.value(PALETTE[0]),
|
||||||
tooltip=["diagnosis:N", "visits:Q"],
|
tooltip=["diagnosis:N", "visits:Q"],
|
||||||
)
|
)
|
||||||
.properties(title="Top 15 ED Diagnoses", width=700, height=400)
|
.properties(title="Top 15 ED Diagnoses", width=700, height=400)
|
||||||
@@ -650,7 +656,7 @@ def _(mo, q):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
readmit_cohort = q("""
|
readmit_cohort = q("""
|
||||||
SELECT specialty_cohort AS cohort,
|
SELECT specialty_cohort AS cohort,
|
||||||
count(*) AS admissions,
|
count(*) AS admissions,
|
||||||
@@ -670,8 +676,8 @@ def _(alt, mo, q):
|
|||||||
y=alt.Y("cohort:N", sort="-x", title=None),
|
y=alt.Y("cohort:N", sort="-x", title=None),
|
||||||
color=alt.condition(
|
color=alt.condition(
|
||||||
alt.datum.rate > 20,
|
alt.datum.rate > 20,
|
||||||
alt.value("#e41a1c"),
|
alt.value(PALETTE[1]),
|
||||||
alt.value("#66c2a5"),
|
alt.value(PALETTE[2]),
|
||||||
),
|
),
|
||||||
tooltip=[
|
tooltip=[
|
||||||
"cohort:N",
|
"cohort:N",
|
||||||
@@ -696,7 +702,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, pl, q):
|
def _(PALETTE, alt, mo, pl, q):
|
||||||
brand_gen = q("""
|
brand_gen = q("""
|
||||||
SELECT brand_vs_generic, count(*) AS claims,
|
SELECT brand_vs_generic, count(*) AS claims,
|
||||||
round(sum(paid_amount), 2) AS total_paid
|
round(sum(paid_amount), 2) AS total_paid
|
||||||
@@ -722,7 +728,7 @@ def _(alt, mo, pl, q):
|
|||||||
color=alt.Color(
|
color=alt.Color(
|
||||||
"brand_vs_generic:N",
|
"brand_vs_generic:N",
|
||||||
scale=alt.Scale(
|
scale=alt.Scale(
|
||||||
domain=["brand", "generic"], range=["#fc8d62", "#66c2a5"]
|
domain=["brand", "generic"], range=[PALETTE[7], PALETTE[2]]
|
||||||
),
|
),
|
||||||
legend=None,
|
legend=None,
|
||||||
),
|
),
|
||||||
@@ -774,7 +780,7 @@ def _(mo):
|
|||||||
|
|
||||||
|
|
||||||
@app.cell(hide_code=True)
|
@app.cell(hide_code=True)
|
||||||
def _(alt, mo, q):
|
def _(PALETTE, alt, mo, q):
|
||||||
pqi = q("""
|
pqi = q("""
|
||||||
SELECT r.pqi_number, m.pqi_name AS pqi_description,
|
SELECT r.pqi_number, m.pqi_name AS pqi_description,
|
||||||
r.rate_per_100_thousand, r.num_count, r.denom_count
|
r.rate_per_100_thousand, r.num_count, r.denom_count
|
||||||
@@ -790,7 +796,7 @@ def _(alt, mo, q):
|
|||||||
.encode(
|
.encode(
|
||||||
x=alt.X("rate_per_100_thousand:Q", title="Rate per 100,000"),
|
x=alt.X("rate_per_100_thousand:Q", title="Rate per 100,000"),
|
||||||
y=alt.Y("pqi_description:N", sort="-x", title=None),
|
y=alt.Y("pqi_description:N", sort="-x", title=None),
|
||||||
color=alt.value("#8da0cb"),
|
color=alt.value(PALETTE[0]),
|
||||||
tooltip=[
|
tooltip=[
|
||||||
"pqi_number:N",
|
"pqi_number:N",
|
||||||
"pqi_description:N",
|
"pqi_description:N",
|
||||||
|
|||||||
@@ -26,31 +26,20 @@ from __future__ import annotations
|
|||||||
|
|
||||||
import html as html_mod
|
import html as html_mod
|
||||||
import json
|
import json
|
||||||
|
import sys as _sys
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
|
from pathlib import Path as _Path
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
from aco.pipe.base import Pipeline
|
_styles_dir = str(_Path(__file__).resolve().parents[2] / "styles")
|
||||||
|
if _styles_dir not in _sys.path:
|
||||||
|
_sys.path.insert(0, _styles_dir)
|
||||||
|
|
||||||
# Tableau 10 palette for pipeline schemas, gray for externals.
|
from nature import EXTERNAL_COLOR as _EXTERNAL_COLOR # noqa: E402, I001
|
||||||
_SCHEMA_COLORS: dict[str, str] = {
|
from nature import SCHEMA_COLORS as _SCHEMA_COLORS # noqa: E402, F401
|
||||||
"core": "#e15759",
|
from nature import schema_color as _color # noqa: E402
|
||||||
"input_layer": "#76b7b2",
|
|
||||||
"cclf": "#4e79a7",
|
|
||||||
"claims_preprocessing": "#f28e2b",
|
|
||||||
"readmissions": "#59a14f",
|
|
||||||
"ahrq_measures": "#edc948",
|
|
||||||
"pharmacy": "#b07aa1",
|
|
||||||
"quality_measures": "#ff9da7",
|
|
||||||
"hcc_suspecting": "#9c755f",
|
|
||||||
"provider_attribution": "#bab0ac",
|
|
||||||
"main": "#86bcb6",
|
|
||||||
"data_quality": "#4e79a7",
|
|
||||||
}
|
|
||||||
_EXTERNAL_COLOR = "#888888"
|
|
||||||
|
|
||||||
|
from aco.pipe.base import Pipeline # noqa: E402
|
||||||
def _color(schema: str) -> str:
|
|
||||||
return _SCHEMA_COLORS.get(schema, _EXTERNAL_COLOR)
|
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -271,7 +260,7 @@ def to_html(graph: Graph, *, title: str = "ACO Pipeline DAG") -> str:
|
|||||||
<script src="https://unpkg.com/cytoscape-dagre@2/cytoscape-dagre.js"></script>
|
<script src="https://unpkg.com/cytoscape-dagre@2/cytoscape-dagre.js"></script>
|
||||||
<style>
|
<style>
|
||||||
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
* {{ margin: 0; padding: 0; box-sizing: border-box; }}
|
||||||
body {{ background: #1a1a2e; color: #e0e0e0; font-family: system-ui, sans-serif; }}
|
body {{ background: #1a1a2e; color: #e0e0e0; font-family: Helvetica, Arial, sans-serif; }}
|
||||||
#cy {{ width: 100vw; height: 100vh; }}
|
#cy {{ width: 100vw; height: 100vh; }}
|
||||||
#controls {{
|
#controls {{
|
||||||
position: fixed; top: 12px; left: 12px; z-index: 10;
|
position: fixed; top: 12px; left: 12px; z-index: 10;
|
||||||
|
|||||||
209
styles/nature.py
Normal file
209
styles/nature.py
Normal file
@@ -0,0 +1,209 @@
|
|||||||
|
"""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",
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user