1"""WP44 figure style (matplotlib, vector PDF). Palette = validated reference palette of the dataviz skill2(categorical slots in fixed order, validated 2026-11-03 with scripts/validate_palette.js, light surface #fcfcfb).3Rules applied everywhere: one y-axis per panel (no dual axes), thin marks, recessive grid, direct labels for <= 4 series,4legend when >= 2 series, sequential = single blue hue, diverging = blue/orange with neutral midpoint, text in ink tokens."""5import matplotlib as mpl6import matplotlib.pyplot as plt78CATEGORICAL = ["#2a78d6", "#eb6834", "#1baf7a", "#eda100", "#e87ba4", "#008300", "#6250d6", "#e34948"]9SEQUENTIAL = ["#cde2fb", "#b7d3f6", "#9ec5f4", "#86b6ef", "#6da7ec", "#5598e7", "#3987e5", "#2a78d6", "#256abf", "#1c5cab", "#184f95", "#104281", "#0d366b"]10DIVERGING = ["#0d366b", "#2a78d6", "#86b6ef", "#f0efec", "#f7a97f", "#eb6834", "#8a3412"]11INK, INK2, MUTED, GRID, AXIS, SURFACE = "#0b0b0b", "#52514e", "#898781", "#e1e0d9", "#c3c2b7", "#fcfcfb"12EVENT = "#52514e" # reference-date marker (ChatGPT release), never a series colour131415def apply():16 mpl.rcParams.update({17 "figure.dpi": 120, "savefig.dpi": 300, "figure.facecolor": "white", "axes.facecolor": "white",18 "font.family": "serif", "font.size": 9, "axes.titlesize": 10, "axes.labelsize": 9, "legend.fontsize": 8, "xtick.labelsize": 8, "ytick.labelsize": 8,19 "axes.edgecolor": AXIS, "axes.linewidth": 0.6, "axes.spines.top": False, "axes.spines.right": False,20 "axes.grid": True, "grid.color": GRID, "grid.linewidth": 0.5, "axes.axisbelow": True,21 "xtick.color": MUTED, "ytick.color": MUTED, "axes.labelcolor": INK2, "text.color": INK,22 "lines.linewidth": 1.4, "lines.markersize": 4, "legend.frameon": False,23 "axes.prop_cycle": mpl.cycler(color=CATEGORICAL), "pdf.fonttype": 42, "ps.fonttype": 42,24 })252627def seq_cmap():28 return mpl.colors.LinearSegmentedColormap.from_list("wp44_seq", SEQUENTIAL)293031def div_cmap():32 return mpl.colors.LinearSegmentedColormap.from_list("wp44_div", DIVERGING)333435def event_line(ax, x, label="ChatGPT release (2022-11-30)"):36 ax.axvline(x, color=EVENT, linewidth=0.8, linestyle="--", zorder=1)37 ax.annotate(label, xy=(x, 1.0), xycoords=("data", "axes fraction"), xytext=(3, -2), textcoords="offset points", fontsize=7, color=INK2, va="top")383940def save(fig, path):41 fig.savefig(path, bbox_inches="tight")42 if path.endswith(".pdf"):43 fig.savefig(path[:-4] + ".png", bbox_inches="tight")44 plt.close(fig)45