SPB Git

spb/wp36_uqo

Public
3.8 KB · 73 lines python
1"""Shared figure style for WP36 (vector-first, print-safe, validated palette).23Categorical slots are assigned in FIXED order (never cycled): series-1 blue,4series-2 orange, series-3 aqua, series-4 yellow, series-5 magenta, series-6 green,5series-7 violet, series-8 red. Sequential = blue ramp; diverging = blue <-> red6with neutral grey midpoint. Validated with the dataviz palette validator7(light mode, adjacent pairs: worst CVD dE 9.1, normal-vision 19.6).8"""9from __future__ import annotations1011import matplotlib as mpl12import matplotlib.pyplot as plt13from matplotlib.colors import LinearSegmentedColormap, ListedColormap1415CAT = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#6250d6", "#e34948"]16SEQ = ["#cde2fb", "#b7d3f6", "#9ec5f4", "#86b6ef", "#6da7ec", "#5598e7", "#3987e5", "#2a78d6", "#256abf", "#1c5cab", "#184f95", "#104281", "#0d366b"]17DIV = ["#0d366b", "#2a78d6", "#86b6ef", "#f0efec", "#f0a3a2", "#e34948", "#8a1f1f"]18SURFACE = "#fcfcfb"19TEXT = "#0b0b0b"20TEXT2 = "#52514e"21GRID = "#e6e5e1"22NEUTRAL = "#9a9892"2324cmap_seq = LinearSegmentedColormap.from_list("ucml_seq", SEQ)25cmap_div = LinearSegmentedColormap.from_list("ucml_div", DIV)26cmap_cat = ListedColormap(CAT)2728# region fixed colour order (identity follows the entity, never its rank)29REGIONS = ["Montréal", "Laval", "Montérégie", "Laurentides", "Lanaudière", "Capitale-Nationale", "Chaudière-Appalaches",30           "Estrie", "Mauricie", "Centre-du-Québec", "Outaouais", "Bas-Saint-Laurent", "Saguenay–Lac-Saint-Jean",31           "Abitibi-Témiscamingue", "Côte-Nord", "Gaspésie–Îles-de-la-Madeleine", "Nord-du-Québec"]32POWERTRAIN_ORDER = ["ICE", "HEV", "PHEV", "BEV", "Diesel", "unknown"]33BODY_ORDER = ["SUV", "Sedan", "Pickup", "Hatchback", "Van", "Coupe", "Convertible", "Wagon"]34CLASS_ORDER = ["direct_dealer", "full_portal", "banner", "sampled_portal", "private_marketplace", "small_portal"]35CLASS_LABEL = {"direct_dealer": "Dealer websites", "full_portal": "Full-inventory portals", "banner": "Dealer groups",36               "sampled_portal": "Sampled portals (excluded)", "private_marketplace": "Private sellers (Kijiji)", "small_portal": "Small portals"}373839def color_for(order: list[str], key: str) -> str:40    """Fixed slot per entity; entities beyond slot 8 fold into neutral grey."""41    try:42        i = order.index(key)43    except ValueError:44        return NEUTRAL45    return CAT[i] if i < len(CAT) else NEUTRAL464748def setup() -> None:49    mpl.rcParams.update({50        "figure.facecolor": SURFACE, "axes.facecolor": SURFACE, "savefig.facecolor": SURFACE,51        "font.family": "serif", "font.serif": ["Times New Roman", "Times", "Nimbus Roman", "DejaVu Serif"], "font.size": 9,52        "axes.edgecolor": GRID, "axes.linewidth": 0.6, "axes.labelcolor": TEXT, "axes.titlesize": 9.5, "axes.titleweight": "bold",53        "axes.grid": True, "grid.color": GRID, "grid.linewidth": 0.5, "axes.axisbelow": True,54        "axes.spines.top": False, "axes.spines.right": False,55        "xtick.color": TEXT2, "ytick.color": TEXT2, "xtick.labelsize": 8, "ytick.labelsize": 8,56        "legend.frameon": False, "legend.fontsize": 8, "lines.linewidth": 1.6, "lines.markersize": 4,57        "patch.linewidth": 0.0, "pdf.fonttype": 42, "ps.fonttype": 42, "figure.dpi": 150, "savefig.dpi": 300,58        "figure.constrained_layout.use": True,59    })606162def save(fig, stem: str, figdir=None) -> None:63    """Save vector PDF (paper) + PNG (preview) under figures/ and paper/figures/."""64    from . import config as C65    import shutil66    figdir = figdir or C.FIGURES67    figdir.mkdir(parents=True, exist_ok=True)68    fig.savefig(figdir / f"{stem}.pdf")69    fig.savefig(figdir / f"{stem}.png")70    (C.ROOT / "paper" / "figures").mkdir(parents=True, exist_ok=True)71    shutil.copy(figdir / f"{stem}.pdf", C.ROOT / "paper" / "figures" / f"{stem}.pdf")72    plt.close(fig)73
Files · d0f6445591c09fe14b42fb37035f398825960706
Menu

Theme