Matplotlib is the standard Python plotting library. pip install matplotlib. Figure: import matplotlib.pyplot as plt; fig, ax = plt.subplots(figsize=(10,6)). plot: ax.plot(x, y, color="steelblue", linewidth=2, label="series"). scatter: ax.scatter(x, y, c=colors, s=sizes, alpha=0.7). bar: ax.bar(categories, values, color="steelblue"). barh: ax.barh(labels, values). hist: ax.hist(data, bins=30, edgecolor="white"). boxplot: ax.boxplot([data1, data2]). imshow: ax.imshow(matrix, cmap="viridis", aspect="auto"). labels: ax.set_xlabel("X"); ax.set_ylabel("Y"); ax.set_title("Title"). legend: ax.legend(loc="upper right"). xlim/ylim: ax.set_xlim(0, 10). grid: ax.grid(True, alpha=0.3). axhline/axvline: ax.axhline(y=0, color="gray", linestyle="--"). fill_between: ax.fill_between(x, y1, y2, alpha=0.3). errorbar: ax.errorbar(x, y, yerr=err, capsize=5). twinx: ax2 = ax.twinx(). subplots: fig, axes = plt.subplots(2, 3, figsize=(15,8)). tight_layout: fig.tight_layout(). colorbar: plt.colorbar(im, ax=ax). annotate: ax.annotate("peak", xy=(x,y), xytext=(x+1,y+5), arrowprops=dict(arrowstyle="->")). style: plt.style.use("seaborn-v0_8-whitegrid"). savefig: fig.savefig("plot.png", dpi=150, bbox_inches="tight"). show: plt.show(). close: plt.close(fig). Claude Code generates Matplotlib dashboards, statistical charts, heatmaps, and multi-panel figures.
CLAUDE.md for Matplotlib
## Matplotlib Stack
- Version: matplotlib >= 3.8 | pip install matplotlib
- Figure: fig, ax = plt.subplots(figsize=(10, 6)) | fig, axes = plt.subplots(r, c)
- Plot: ax.plot/scatter/bar/hist/boxplot/imshow(data, **style_kwargs)
- Style: ax.set_xlabel/ylabel/title | ax.legend() | ax.grid(True, alpha=0.3)
- Export: fig.savefig("out.png", dpi=150, bbox_inches="tight") | plt.close(fig)
- Style preset: plt.style.use("seaborn-v0_8-whitegrid")
Matplotlib Visualization Pipeline
# app/charts.py — Matplotlib Figure, Axes, plot types, subplots, export, style
from __future__ import annotations
from pathlib import Path
from typing import Any, Sequence
import matplotlib
matplotlib.use("Agg") # non-interactive backend — safe for servers and CI
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np
# ─── Global style ────────────────────────────────────────────────────────────
plt.style.use("seaborn-v0_8-whitegrid")
COLORS = plt.rcParams["axes.prop_cycle"].by_key()["color"]
# ─────────────────────────────────────────────────────────────────────────────
# 1. Figure helpers
# ─────────────────────────────────────────────────────────────────────────────
def make_figure(
nrows: int = 1,
ncols: int = 1,
figsize: tuple[float, float] | None = None,
dpi: int = 100,
) -> tuple[plt.Figure, Any]:
"""
Create a Figure and Axes (or array of Axes).
Example:
fig, ax = make_figure()
fig, axes = make_figure(2, 3, figsize=(15, 8))
"""
if figsize is None:
figsize = (6 * ncols, 4 * nrows)
return plt.subplots(nrows, ncols, figsize=figsize, dpi=dpi)
def apply_style(
ax: plt.Axes,
title: str = "",
xlabel: str = "",
ylabel: str = "",
legend: bool = True,
grid_alpha: float = 0.3,
tight: bool = True,
) -> plt.Axes:
"""
Apply common axis styling.
Example:
apply_style(ax, title="Monthly Revenue", xlabel="Month", ylabel="$USD")
"""
if title: ax.set_title(title, pad=10, fontsize=13, fontweight="bold")
if xlabel: ax.set_xlabel(xlabel, fontsize=11)
if ylabel: ax.set_ylabel(ylabel, fontsize=11)
if legend: ax.legend(framealpha=0.9)
ax.grid(True, alpha=grid_alpha, linestyle="--")
ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
return ax
def save(
fig: plt.Figure,
path: str | Path,
dpi: int = 150,
close: bool = True,
) -> Path:
"""
Save figure to disk and optionally close it.
Example:
save(fig, "reports/revenue.png")
save(fig, "docs/chart.pdf") # PDF vector
"""
p = Path(path)
p.parent.mkdir(parents=True, exist_ok=True)
fig.tight_layout()
fig.savefig(p, dpi=dpi, bbox_inches="tight")
if close:
plt.close(fig)
return p
def to_bytes(fig: plt.Figure, fmt: str = "png", dpi: int = 150) -> bytes:
"""
Render figure to bytes (for HTTP responses or embeddings).
Example:
img_bytes = to_bytes(fig, fmt="png")
return Response(img_bytes, content_type="image/png")
"""
import io
buf = io.BytesIO()
fig.tight_layout()
fig.savefig(buf, format=fmt, dpi=dpi, bbox_inches="tight")
plt.close(fig)
buf.seek(0)
return buf.read()
# ─────────────────────────────────────────────────────────────────────────────
# 2. Line and area charts
# ─────────────────────────────────────────────────────────────────────────────
def line_chart(
x: Sequence,
y: Sequence | list[Sequence],
labels: list[str] | None = None,
title: str = "",
xlabel: str = "",
ylabel: str = "",
markers: bool = False,
figsize: tuple = (10, 5),
) -> plt.Figure:
"""
Line chart supporting single or multiple series.
Example:
fig = line_chart(months, revenue, title="Monthly Revenue", ylabel="$")
fig = line_chart(months, [revenue_a, revenue_b], labels=["A", "B"])
"""
fig, ax = make_figure(figsize=figsize)
series_list = [y] if not (isinstance(y[0], (list, np.ndarray)) and hasattr(y[0], "__iter__")) else y
for i, series in enumerate(series_list):
label = (labels or [])[i] if labels and i < len(labels) else f"series {i+1}"
marker = "o" if markers else None
ax.plot(x, series, label=label, color=COLORS[i % len(COLORS)],
linewidth=2, marker=marker, markersize=4)
apply_style(ax, title=title, xlabel=xlabel, ylabel=ylabel)
return fig
def area_chart(
x: Sequence,
y_lower: Sequence,
y_upper: Sequence,
y_mid: Sequence | None = None,
label: str = "",
title: str = "",
color: str = "steelblue",
figsize: tuple = (10, 5),
) -> plt.Figure:
"""
Area / confidence-band chart.
Example:
fig = area_chart(dates, lower_bound, upper_bound, y_mid=forecast,
label="95% CI", title="Forecast")
"""
fig, ax = make_figure(figsize=figsize)
ax.fill_between(x, y_lower, y_upper, color=color, alpha=0.2, label=label)
if y_mid is not None:
ax.plot(x, y_mid, color=color, linewidth=2, label="mean")
apply_style(ax, title=title)
return fig
# ─────────────────────────────────────────────────────────────────────────────
# 3. Bar charts
# ─────────────────────────────────────────────────────────────────────────────
def bar_chart(
categories: Sequence[str],
values: Sequence[float],
title: str = "",
xlabel: str = "",
ylabel: str = "",
color: str = "steelblue",
horizontal: bool = False,
annotate: bool = True,
figsize: tuple = (10, 5),
) -> plt.Figure:
"""
Vertical or horizontal bar chart with optional value labels.
Example:
fig = bar_chart(countries, revenue, title="Revenue by Country", horizontal=True)
"""
fig, ax = make_figure(figsize=figsize)
cats = list(categories)
vals = list(values)
if horizontal:
bars = ax.barh(cats, vals, color=color, edgecolor="white")
if annotate:
for bar in bars:
w = bar.get_width()
ax.text(w * 1.01, bar.get_y() + bar.get_height() / 2,
f"{w:,.0f}", va="center", fontsize=9)
else:
bars = ax.bar(cats, vals, color=color, edgecolor="white")
ax.tick_params(axis="x", rotation=45)
if annotate:
for bar in bars:
h = bar.get_height()
ax.text(bar.get_x() + bar.get_width() / 2, h * 1.01,
f"{h:,.0f}", ha="center", va="bottom", fontsize=9)
apply_style(ax, title=title, xlabel=xlabel, ylabel=ylabel, legend=False)
return fig
def grouped_bar_chart(
categories: Sequence[str],
groups: dict[str, Sequence[float]],
title: str = "",
xlabel: str = "",
ylabel: str = "",
figsize: tuple = (12, 5),
) -> plt.Figure:
"""
Grouped bar chart.
Example:
fig = grouped_bar_chart(["Q1","Q2","Q3","Q4"],
{"Product A": [120,140,130,160],
"Product B": [90,110,95,130]})
"""
fig, ax = make_figure(figsize=figsize)
n_groups = len(categories)
n_bars = len(groups)
width = 0.8 / n_bars
x = np.arange(n_groups)
for i, (name, vals) in enumerate(groups.items()):
offset = (i - n_bars / 2 + 0.5) * width
ax.bar(x + offset, vals, width, label=name,
color=COLORS[i % len(COLORS)], edgecolor="white")
ax.set_xticks(x)
ax.set_xticklabels(categories)
apply_style(ax, title=title, xlabel=xlabel, ylabel=ylabel)
return fig
# ─────────────────────────────────────────────────────────────────────────────
# 4. Distribution charts
# ─────────────────────────────────────────────────────────────────────────────
def histogram(
data: np.ndarray,
bins: int = 30,
title: str = "",
xlabel: str = "",
color: str = "steelblue",
density: bool = False,
kde: bool = False,
figsize: tuple = (8, 5),
) -> plt.Figure:
"""
Histogram with optional KDE overlay.
Example:
fig = histogram(scores, bins=20, title="Score Distribution", kde=True)
"""
fig, ax = make_figure(figsize=figsize)
ax.hist(data, bins=bins, density=density, color=color, edgecolor="white",
alpha=0.75, label="data")
if kde:
from scipy.stats import gaussian_kde # type: ignore[import]
xr = np.linspace(data.min(), data.max(), 300)
kde_fn = gaussian_kde(data)
ax2 = ax.twinx()
ax2.plot(xr, kde_fn(xr), color="darkorange", linewidth=2, label="KDE")
ax2.set_ylabel("Density")
apply_style(ax, title=title, xlabel=xlabel, ylabel="Frequency")
return fig
def scatter_plot(
x: np.ndarray,
y: np.ndarray,
c: np.ndarray | None = None,
labels: np.ndarray | None = None,
title: str = "",
xlabel: str = "",
ylabel: str = "",
cmap: str = "viridis",
alpha: float = 0.7,
figsize: tuple = (8, 6),
) -> plt.Figure:
"""
Scatter plot with optional color mapping and point labels.
Example:
fig = scatter_plot(X_2d[:, 0], X_2d[:, 1], c=label_ids,
title="PCA projection", cmap="tab10")
"""
fig, ax = make_figure(figsize=figsize)
sc = ax.scatter(x, y, c=c, cmap=cmap, alpha=alpha, edgecolors="none", s=30)
if c is not None:
plt.colorbar(sc, ax=ax)
if labels is not None:
for xi, yi, lab in zip(x, y, labels):
ax.annotate(str(lab), (xi, yi), fontsize=7, alpha=0.8)
apply_style(ax, title=title, xlabel=xlabel, ylabel=ylabel, legend=False)
return fig
# ─────────────────────────────────────────────────────────────────────────────
# 5. Heatmap
# ─────────────────────────────────────────────────────────────────────────────
def heatmap(
matrix: np.ndarray,
row_labels: list[str] | None = None,
col_labels: list[str] | None = None,
title: str = "",
cmap: str = "Blues",
annotate: bool = True,
fmt: str = ".2f",
figsize: tuple | None = None,
) -> plt.Figure:
"""
Annotated heatmap (confusion matrix, correlation matrix, etc.).
Example:
fig = heatmap(corr_matrix, row_labels=features, col_labels=features,
title="Correlation Matrix", fmt=".2f")
"""
n, m = matrix.shape
if figsize is None:
figsize = (max(6, m * 0.8), max(5, n * 0.6))
fig, ax = make_figure(figsize=figsize)
im = ax.imshow(matrix, cmap=cmap, aspect="auto")
plt.colorbar(im, ax=ax)
if row_labels:
ax.set_yticks(range(len(row_labels)))
ax.set_yticklabels(row_labels, fontsize=9)
if col_labels:
ax.set_xticks(range(len(col_labels)))
ax.set_xticklabels(col_labels, rotation=45, ha="right", fontsize=9)
if annotate:
vmin, vmax = matrix.min(), matrix.max()
threshold = (vmin + vmax) / 2
for i in range(n):
for j in range(m):
color = "white" if matrix[i, j] > threshold else "black"
ax.text(j, i, format(matrix[i, j], fmt),
ha="center", va="center", color=color, fontsize=8)
if title:
ax.set_title(title, pad=10, fontsize=13, fontweight="bold")
fig.tight_layout()
return fig
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
import tempfile, os
rng = np.random.default_rng(42)
print("=== Matplotlib demo (saving to temp PNG files) ===")
# Line chart
months = [f"2024-{m:02d}" for m in range(1, 13)]
rev_a = rng.normal(100, 15, 12).cumsum()
rev_b = rng.normal(80, 10, 12).cumsum()
fig = line_chart(months, [rev_a, rev_b], labels=["Product A","Product B"],
title="Monthly Revenue", ylabel="$K")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
p = save(fig, tf.name)
print(f" line_chart saved: {os.path.getsize(tf.name):,} bytes")
os.unlink(tf.name)
# Bar chart
countries = ["US","UK","DE","FR","JP","AU"]
revenue = [450, 280, 310, 190, 260, 140]
fig = bar_chart(countries, revenue, title="Revenue by Country",
ylabel="$K", horizontal=True)
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
p = save(fig, tf.name)
print(f" bar_chart saved: {os.path.getsize(tf.name):,} bytes")
os.unlink(tf.name)
# Heatmap
corr = np.corrcoef(rng.normal(0, 1, (6, 50)))
feats = ["age","income","score","tenure","clicks","orders"]
fig = heatmap(corr, feats, feats, title="Feature Correlation")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
p = save(fig, tf.name)
print(f" heatmap saved: {os.path.getsize(tf.name):,} bytes")
os.unlink(tf.name)
# Scatter
X = rng.normal(0, 1, (200, 2))
labels = (X[:, 0] + X[:, 1] > 0).astype(int)
fig = scatter_plot(X[:, 0], X[:, 1], c=labels, title="2-Class Scatter", cmap="bwr")
with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tf:
p = save(fig, tf.name)
print(f" scatter saved: {os.path.getsize(tf.name):,} bytes")
os.unlink(tf.name)
print("=== done ===")
For the plotly alternative — plotly generates interactive HTML charts with zoom, pan, hover tooltips, and can be embedded in Dash apps or Jupyter notebooks; matplotlib generates static images (PNG, SVG, PDF) suitable for reports, papers, and server-side rendering without a browser — use plotly for dashboards and exploratory analysis where interactivity adds value, matplotlib for publication-quality static figures, batch report generation, and CI-generated images. For the seaborn alternative — seaborn is a statistical visualization library built on top of matplotlib, providing high-level functions for distributions, regressions, facet grids, and categorical plots with sensible defaults; matplotlib is lower-level with complete control over every visual element — use seaborn for statistical charts (pairplot, boxplot, violin, lmplot) with minimal code, matplotlib when you need pixel-level control over layouts, custom color cycles, or multi-panel figures that don’t fit seaborn’s API. The Claude Skills 360 bundle includes Matplotlib skill sets covering make_figure()/apply_style()/save()/to_bytes() figure helpers, line_chart()/area_chart() time series, bar_chart()/grouped_bar_chart() categorical, histogram()/scatter_plot() distribution, and heatmap() annotated grid. Start with the free tier to try data visualization and chart generation code generation.