Files
cs249r_book/book/tools/figures/style.py
Vijay Janapa Reddi e4a2e40a82 Remove research-questions callouts, fix ml_ops cascade figure, standardize figure font
Builds on the PR #1957 merge (Zeljko's Vol1 layout), otherwise byte-for-byte his version:

- Remove the end-of-chapter research-questions callouts from all chapters.
- ml_ops correction-cascade figure: keep Zeljko's Model A-D chain TikZ drawing,
  and rewrite the caption, alt-text, and lead-in to describe the chain (they
  carried a stale 'timeline' description that no longer matched the figure),
  plus an operations-impact line.
- Standardize the matplotlib figure font on a bundled TeX Gyre Heros (a free,
  redistributable Helvetica-metric clone) registered at runtime in
  book/tools/figures/style.py, so figures render with a real bold identically
  on macOS, Linux, and CI instead of falling back to DejaVu Sans or an
  unreachable macOS .ttc bold face.
2026-07-12 15:29:07 +02:00

145 lines
4.6 KiB
Python

"""Publication plotting style for MLSysBook figures.
This module owns book-level matplotlib policy: palette, font stack, grid style,
and the small setup helper used by generated figure blocks. Simulator-aware
plots remain in ``mlsysim.viz``; book production style belongs here.
"""
from __future__ import annotations
import os
try:
import matplotlib
if "MPLBACKEND" not in os.environ:
matplotlib.use("Agg", force=True)
import matplotlib.pyplot as plt
_MATPLOTLIB_AVAILABLE = True
except ImportError:
plt = None
_MATPLOTLIB_AVAILABLE = False
COLORS = {
"crimson": "#A51C30", # Harvard Crimson
"primary": "#333333", # Dark gray text
"grid": "#CCCCCC", # Light gray
"BlueLine": "#006395",
"BlueL": "#D1E6F3",
"BlueFill": "#D6EAF8",
"RedLine": "#CB202D",
"RedL": "#F5D2D5",
"RedFill": "#F2D7D5",
"GreenLine": "#008F45",
"GreenL": "#D4EFDF",
"GreenFill": "#D5F5E3",
"OrangeLine": "#E67817",
"OrangeL": "#FCE4CC",
"VioletLine": "#7E317B",
"VioletL": "#E6D4E5",
"BrownLine": "#78492A",
"BrownL": "#E3D3C8",
"YellowFill": "#FEF9E0",
}
WEB_FIG_DPI = 120
# Bundled Helvetica-metric font, registered at runtime so figures render in a
# consistent Helvetica clone WITH A REAL BOLD on every platform (local macOS,
# local Linux, CI container) regardless of installed system fonts. Without this,
# matplotlib falls back per-platform (macOS Helvetica.ttc has no reachable bold
# face; a bare Linux/CI box drops all the way to DejaVu Sans), so bold weight
# and text metrics drift between local previews and the published build.
_FONT_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "fonts")
_BUNDLED_FONTS_REGISTERED = False
def _register_bundled_fonts() -> None:
"""Register the bundled TeX Gyre Heros faces with matplotlib (idempotent)."""
global _BUNDLED_FONTS_REGISTERED
if _BUNDLED_FONTS_REGISTERED or not _MATPLOTLIB_AVAILABLE:
return
try:
import matplotlib.font_manager as fm
if os.path.isdir(_FONT_DIR):
known = {getattr(f, "fname", None) for f in fm.fontManager.ttflist}
for name in sorted(os.listdir(_FONT_DIR)):
if name.lower().endswith((".otf", ".ttf")):
path = os.path.join(_FONT_DIR, name)
if path not in known:
fm.fontManager.addfont(path)
except Exception:
# Never let a font-registration hiccup break figure generation;
# matplotlib will fall through the sans-serif stack below.
pass
_BUNDLED_FONTS_REGISTERED = True
def set_book_style() -> None:
"""Apply the global MLSysBook matplotlib style."""
if not _MATPLOTLIB_AVAILABLE:
raise ImportError("matplotlib is required for plot generation.")
_register_bundled_fonts()
plt.rcParams.update(
{
"font.family": "sans-serif",
"font.sans-serif": [
"TeX Gyre Heros", # bundled Helvetica-metric clone (has bold)
"Helvetica",
"Helvetica Neue",
"Arial",
"DejaVu Sans",
],
"font.size": 10,
"text.color": COLORS["primary"],
"axes.labelsize": 11,
"axes.titlesize": 12,
"axes.titleweight": "bold",
"axes.grid": True,
"grid.color": COLORS["grid"],
"grid.alpha": 0.4,
"grid.linestyle": "--",
"figure.dpi": 300,
"savefig.bbox": "tight",
}
)
def finalize_web_figure(fig):
"""Match Quarto web defaults while publication figures keep print DPI."""
fig.set_dpi(WEB_FIG_DPI)
return fig
def setup_plot(figsize=(8, 5)):
"""Create a matplotlib figure/axis using the MLSysBook style."""
set_book_style()
fig, ax = plt.subplots(figsize=figsize)
return fig, ax, COLORS, plt
def bar_compare(labels, values, *, title=None, ylabel=None, figsize=(8, 4), color=None):
"""Render a compact bar comparison with the MLSysBook style."""
fig, ax, colors, _ = setup_plot(figsize=figsize)
bar_color = color or colors["BlueLine"]
bars = ax.bar(labels, values, color=bar_color, alpha=0.8)
if title:
ax.set_title(title)
if ylabel:
ax.set_ylabel(ylabel)
ax.tick_params(axis="x", rotation=0)
for bar, value in zip(bars, values):
ax.text(
bar.get_x() + bar.get_width() / 2,
bar.get_height(),
f"{value:g}",
ha="center",
va="bottom",
fontsize=9,
color=colors["primary"],
)
return finalize_web_figure(fig), ax