From 6fcf1bdb0202c7e770242f3f19308577d8b6eb7d Mon Sep 17 00:00:00 2001 From: Orchestrator Date: Thu, 4 Jun 2026 17:08:24 -0500 Subject: [PATCH] feat(utils): add shared styling and color palette --- src/utils/styling.py | 127 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 src/utils/styling.py diff --git a/src/utils/styling.py b/src/utils/styling.py new file mode 100644 index 0000000..38729e7 --- /dev/null +++ b/src/utils/styling.py @@ -0,0 +1,127 @@ +"""Shared styling utilities and color palette for all charts.""" + +from typing import Optional + +import matplotlib.figure +import matplotlib.axes +import matplotlib.pyplot as plt + + +# --------------------------------------------------------------------------- +# Export & sizing constants +# --------------------------------------------------------------------------- + +EXPORT_DPI = 300 +FIGURE_SIZE_DEFAULT = (12, 7) # width, height in inches +FIGURE_SIZE_SQUARE = (8, 8) +FIGURE_SIZE_WIDE = (16, 10) + + +# --------------------------------------------------------------------------- +# Zone colors (risk / status shading) +# --------------------------------------------------------------------------- + +BUBBLE_ZONE = "#e74c3c" # Red — danger/bubble +WARNING_ZONE = "#f39c12" # Orange — warning +NORMAL_ZONE = "#27ae60" # Green — normal/healthy + + +# --------------------------------------------------------------------------- +# Data series colors +# --------------------------------------------------------------------------- + +AI_SPEND = "#2980b9" # Blue — AI spending +REVENUE = "#27ae60" # Green — revenue +AGENT_GROWTH = "#8e44ad" # Purple — agent adoption +DEBT = "#c0392b" # Dark red — debt +PRODUCTIVITY = "#16a085" # Teal — productivity metrics + + +# --------------------------------------------------------------------------- +# Neutral colors +# --------------------------------------------------------------------------- + +GRAY_LIGHT = "#ecf0f1" +GRAY_MEDIUM = "#95a5a6" +GRAY_DARK = "#2c3e50" +BLACK = "#1a1a2e" +WHITE = "#ffffff" + + +# --------------------------------------------------------------------------- +# Theme +# --------------------------------------------------------------------------- + +def get_theme() -> dict: + """Return a matplotlib rcParams dict with a clean, professional look.""" + return { + "font.family": "DejaVu Sans", + "font.size": 12, + "figure.facecolor": WHITE, + "figure.dpi": EXPORT_DPI, + "axes.facecolor": "#fafafa", + "axes.edgecolor": "#dddddd", + "axes.grid": True, + "axes.axisbelow": True, + "grid.color": "#e0e0e0", + "grid.linestyle": "-", + "grid.linewidth": 0.5, + "grid.alpha": 0.7, + "xtick.labelsize": 10, + "ytick.labelsize": 10, + "axes.titlesize": 16, + "axes.titleweight": "bold", + "axes.labelsize": 12, + "legend.fontsize": 9, + "figure.titlesize": 16, + "figure.titleweight": "bold", + "savefig.dpi": EXPORT_DPI, + "savefig.facecolor": WHITE, + "savefig.bbox": "tight", + } + + +# --------------------------------------------------------------------------- +# Helpers +# --------------------------------------------------------------------------- + +def apply_theme(fig: matplotlib.figure.Figure, ax: Optional[matplotlib.axes.Axes] = None) -> None: + """Apply the professional theme to a figure and optionally its axes. + + Parameters + ---------- + fig : matplotlib Figure + The figure to theme. + ax : matplotlib Axes, optional + If provided, spine cleanup is applied to this specific axes. + If *None*, every axes in the figure is cleaned. + """ + plt.rcParams.update(get_theme()) + fig.set_facecolor(WHITE) + + targets = [ax] if ax is not None else fig.axes + for axes in targets: + axes.set_facecolor("#fafafa") + axes.spines["top"].set_visible(False) + axes.spines["right"].set_visible(False) + axes.spines["left"].set_color("#cccccc") + axes.spines["bottom"].set_color("#cccccc") + + +def get_bubble_zone_colors() -> dict: + """Return zone colours suitable for shaded risk regions.""" + return { + "bubble": BUBBLE_ZONE, + "warning": WARNING_ZONE, + "normal": NORMAL_ZONE, + } + + +def get_company_colors() -> dict: + """Return consistent brand-colour mapping for hyperscalers.""" + return { + "Microsoft": "#00a4ef", + "Alphabet": "#4285f4", + "Meta": "#1877f2", + "Amazon": "#ff9900", + }