Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 44 additions & 31 deletions plots/ecdf-basic/implementations/python/matplotlib.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
""" anyplot.ai
ecdf-basic: Basic ECDF Plot
Library: matplotlib 3.10.9 | Python 3.14.4
Quality: 89/100 | Updated: 2026-04-24
Library: matplotlib 3.11.0 | Python 3.13.14
Quality: 91/100 | Updated: 2026-06-25
"""

import os
Expand All @@ -10,52 +10,65 @@
import numpy as np


# Theme
THEME = os.getenv("ANYPLOT_THEME", "light")
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
BRAND = "#009E73"
BRAND = "#009E73" # Imprint palette position 1

# Data: API response times (ms) — log-normal distribution with a long tail
np.random.seed(42)
response_times_ms = np.random.lognormal(mean=4.6, sigma=0.55, size=250)
# Data: IoT sensor temperature readings (°C) — bimodal mix of offices and server rooms
np.random.seed(7)
office = np.random.normal(loc=22.0, scale=1.8, size=160)
servers = np.random.normal(loc=18.5, scale=0.9, size=90)
temps = np.concatenate([office, servers])
t_min = temps.min()

# Plot
fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG)
# Canvas — 3200 × 1800 px landscape (figsize × dpi, no bbox_inches='tight')
fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)
ax.set_facecolor(PAGE_BG)

ax.ecdf(response_times_ms, color=BRAND, linewidth=3.5)
# Subtle fill under the ECDF curve for visual weight
sorted_temps = np.sort(temps)
ecdf_y = np.arange(1, len(sorted_temps) + 1) / len(sorted_temps)
ax.fill_between(sorted_temps, 0, ecdf_y, step="post", alpha=0.10, color=BRAND)

# Percentile reference lines (p50, p95, p99) — common SRE reading
percentiles = [50, 95, 99]
percentile_values = np.percentile(response_times_ms, percentiles)
for p, v in zip(percentiles, percentile_values, strict=True):
ax.axhline(y=p / 100, color=INK_SOFT, linestyle=":", linewidth=1.5, alpha=0.6)
ax.axvline(x=v, ymax=p / 100, color=INK_SOFT, linestyle=":", linewidth=1.5, alpha=0.6)
# ECDF step function (drawn on top of fill)
ax.ecdf(temps, color=BRAND, linewidth=2.5)

# Bimodal mode markers — highlight the two sensor populations
for mode_temp, label in [(18.5, "Server\nrooms"), (22.0, "Offices")]:
ax.axvline(mode_temp, color=INK_MUTED, linestyle="--", linewidth=0.8, alpha=0.45)
ax.text(mode_temp + 0.2, 0.05, label, fontsize=8, color=INK_MUTED, va="bottom", ha="left")

# Quartile reference guides (Q1, Q2, Q3)
quartiles = [25, 50, 75]
q_values = np.percentile(temps, quartiles)
for q, v in zip(quartiles, q_values, strict=True):
yf = q / 100
ax.plot([t_min - 0.3, v], [yf, yf], color=INK_MUTED, linestyle=":", linewidth=0.8, alpha=0.6)
ax.plot([v, v], [0, yf], color=INK_MUTED, linestyle=":", linewidth=0.8, alpha=0.6)
ax.annotate(
f"p{p} {v:.0f} ms",
xy=(v, p / 100),
xytext=(10, -18 if p == 99 else 8),
textcoords="offset points",
fontsize=14,
color=INK_SOFT,
f"Q{q // 25} {v:.1f}°C", xy=(v, yf), xytext=(5, 5), textcoords="offset points", fontsize=9, color=INK_MUTED
)

# Style
ax.set_xlabel("Response Time (ms)", fontsize=20, color=INK)
ax.set_ylabel("Cumulative Proportion of Requests", fontsize=20, color=INK)
ax.set_title("API Response Times · ecdf-basic · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK)
ax.tick_params(axis="both", labelsize=16, colors=INK_SOFT)
# Chrome
title = "Sensor Temperatures · ecdf-basic · python · matplotlib · anyplot.ai"
title_fs = max(8, round(12 * 67 / len(title))) if len(title) > 67 else 12
ax.set_title(title, fontsize=title_fs, fontweight="medium", color=INK)
ax.set_xlabel("Temperature (°C)", fontsize=10, color=INK)
ax.set_ylabel("Cumulative Proportion", fontsize=10, color=INK)
ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)
ax.set_ylim(0, 1.02)
ax.set_xlim(left=0)
ax.set_xlim(left=t_min - 0.5)

ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
for s in ("left", "bottom"):
ax.spines[s].set_color(INK_SOFT)
ax.grid(True, alpha=0.10, linewidth=0.8, color=INK)

plt.tight_layout()
plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG)
ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK)

fig.subplots_adjust(left=0.09, right=0.97, top=0.92, bottom=0.12)
plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG)
Loading
Loading