"""Matplotlib helpers: render figures to base64 PNG for inline
embeds.
Never call plt.show() - the app is headless. We force the non-interactive
'Agg' backend so figures render without a display server.
"""
import base64
import io
import matplotlib
matplotlib.use("Agg") # headless backend - must be set before pyplot import
import matplotlib.pyplot as plt # noqa: E402
def fig_to_base64(fig) -> str:
"""Serialize a Matplotlib figure to a base64 PNG data URI body.
Returns the base64 string (no data: prefix). Caller embeds it as:
"""
buf = io.BytesIO()
fig.savefig(buf, format="png", dpi=110, bbox_inches="tight")
plt.close(fig) # free memory - critical in a long-running server
buf.seek(0)
return base64.b64encode(buf.read()).decode("ascii")
def cluster_scatter(coords, labels, title, centroids=None, noise_label=None):
"""PCA-2D scatter colored by cluster label -> base64 PNG.
coords: (n, 2) array. labels: cluster id per point. centroids: optional
(k, 2) array to mark with X. noise_label: if set (DBSCAN's -1), those
points are drawn black.
"""
import numpy as np
fig, ax = plt.subplots(figsize=(6, 5))
uniq = sorted(set(labels))
cmap = plt.get_cmap("tab10")
for i, lab in enumerate(uniq):
mask = np.asarray(labels) == lab
if noise_label is not None and lab == noise_label:
ax.scatter(coords[mask, 0], coords[mask, 1], c="black",
s=28, alpha=0.6, label="noise")
else:
ax.scatter(coords[mask, 0], coords[mask, 1],
color=cmap(i % 10), s=40, alpha=0.8,
label=f"cluster {lab}")
if centroids is not None:
ax.scatter(centroids[:, 0], centroids[:, 1], c="black", marker="X",
s=180, edgecolors="white", linewidths=1.5, label="centroids")
ax.set_title(title)
ax.set_xlabel("PC 1")
ax.set_ylabel("PC 2")
ax.legend(fontsize=8, loc="best")
return fig_to_base64(fig)
def elbow_plot(ks, inertias):
"""Line plot of inertia vs k for the K-Means elbow method -> base64 PNG."""
fig, ax = plt.subplots(figsize=(6, 4))
ax.plot(list(ks), inertias, "o-", color="#6C4DF6")
ax.set_title("Elbow Method (inertia vs k)")
ax.set_xlabel("k")
ax.set_ylabel("Inertia")
ax.grid(True, alpha=0.3)
return fig_to_base64(fig)