|
1 | 1 | """ anyplot.ai |
2 | 2 | step-basic: Basic Step Plot |
3 | | -Library: matplotlib 3.10.9 | Python 3.13.13 |
4 | | -Quality: 88/100 | Updated: 2026-04-30 |
| 3 | +Library: matplotlib 3.11.1 | Python 3.13.14 |
| 4 | +Quality: 90/100 | Updated: 2026-07-25 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import os |
8 | 8 |
|
9 | 9 | import matplotlib.pyplot as plt |
10 | 10 | import numpy as np |
| 11 | +from matplotlib.colors import to_rgba |
11 | 12 |
|
12 | 13 |
|
13 | 14 | # Theme tokens |
|
16 | 17 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
17 | 18 | INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
18 | 19 | INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
19 | | -BRAND = "#009E73" # Okabe-Ito position 1 |
| 20 | +BRAND = "#009E73" # Imprint palette position 1 |
20 | 21 |
|
21 | 22 | # Data - monthly cumulative sales figures |
22 | | -np.random.seed(42) |
23 | | -months = np.arange(1, 13) |
24 | 23 | month_labels = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] |
25 | 24 |
|
26 | 25 | monthly_sales = np.array([45, 52, 48, 61, 55, 72, 68, 85, 78, 92, 88, 105]) |
27 | 26 | cumulative_sales = np.cumsum(monthly_sales) |
| 27 | +edges = np.arange(0, 13) # 12 month-wide steps: edges[i] -> edges[i+1] holds cumulative_sales[i] |
| 28 | + |
| 29 | +# Milestone the running total crosses mid-year, for a data-storytelling callout |
| 30 | +milestone = 500 |
| 31 | +milestone_idx = int(np.argmax(cumulative_sales >= milestone)) |
28 | 32 |
|
29 | 33 | # Plot |
30 | | -fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG) |
| 34 | +fig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG) |
31 | 35 | ax.set_facecolor(PAGE_BG) |
32 | 36 |
|
33 | | -# Filled area under the step for visual emphasis |
34 | | -ax.fill_between(months, cumulative_sales, step="post", alpha=0.12, color=BRAND) |
35 | | - |
36 | | -# Step line with 'post' style — value applies from current point until next |
37 | | -ax.step(months, cumulative_sales, where="post", linewidth=3, color=BRAND, label="Cumulative Sales") |
| 37 | +# ax.stairs is the distinctive step-plot API (matplotlib >=3.4): a single |
| 38 | +# StepPatch driven by bin-style edges, used once filled (baseline=0) for the |
| 39 | +# area and once outline-only (baseline=None, no bottom edge) for the line. |
| 40 | +ax.stairs(cumulative_sales, edges, baseline=0, fill=True, color=BRAND, alpha=0.12) |
| 41 | +ax.stairs(cumulative_sales, edges, baseline=None, color=BRAND, linewidth=2.5, label="Cumulative Sales") |
38 | 42 |
|
39 | | -# Markers at each data point to highlight discrete change events |
| 43 | +# Markers at the start of each step ('post' style: value holds from this point until the next) |
| 44 | +# Semi-opaque brand fill (rather than a raw page-background cutout) keeps markers |
| 45 | +# prominent against the filled area at small/thumbnail scale in both themes. |
40 | 46 | ax.scatter( |
41 | | - months, cumulative_sales, s=200, color=PAGE_BG, edgecolors=BRAND, linewidth=2.5, zorder=5, label="Monthly Totals" |
| 47 | + edges[:-1], |
| 48 | + cumulative_sales, |
| 49 | + s=140, |
| 50 | + facecolors=to_rgba(BRAND, 0.35), |
| 51 | + edgecolors=BRAND, |
| 52 | + linewidth=2, |
| 53 | + zorder=5, |
| 54 | + label="Monthly Totals", |
| 55 | +) |
| 56 | + |
| 57 | +# Milestone callout |
| 58 | +ax.axhline(milestone, color=INK_SOFT, linewidth=1, linestyle="--", alpha=0.5) |
| 59 | +ax.annotate( |
| 60 | + f"{milestone}k milestone\nreached in {month_labels[milestone_idx]}", |
| 61 | + xy=(edges[milestone_idx], milestone), |
| 62 | + xytext=(edges[milestone_idx] - 2.6, milestone + 130), |
| 63 | + fontsize=8, |
| 64 | + color=INK, |
| 65 | + arrowprops={"arrowstyle": "->", "color": INK_SOFT, "linewidth": 1}, |
| 66 | + bbox={"facecolor": ELEVATED_BG, "edgecolor": INK_SOFT, "boxstyle": "round,pad=0.3", "alpha": 0.9}, |
42 | 67 | ) |
43 | 68 |
|
44 | 69 | # Style |
45 | | -ax.set_xlabel("Month", fontsize=20, color=INK) |
46 | | -ax.set_ylabel("Cumulative Sales (thousands $)", fontsize=20, color=INK) |
47 | | -ax.set_title("step-basic · matplotlib · anyplot.ai", fontsize=24, fontweight="medium", color=INK) |
| 70 | +ax.set_xlabel("Month", fontsize=10, color=INK) |
| 71 | +ax.set_ylabel("Cumulative Sales (thousands $)", fontsize=10, color=INK) |
| 72 | +ax.set_title("step-basic · matplotlib · anyplot.ai", fontsize=12, fontweight="medium", color=INK) |
48 | 73 |
|
49 | | -ax.set_xticks(months) |
| 74 | +ax.set_xlim(0, 12) |
| 75 | +ax.set_ylim(0, cumulative_sales.max() * 1.12) |
| 76 | +ax.set_xticks(edges[:-1] + 0.5) |
50 | 77 | ax.set_xticklabels(month_labels) |
51 | | -ax.tick_params(axis="both", labelsize=16, colors=INK_SOFT) |
| 78 | +ax.tick_params(axis="both", labelsize=8, colors=INK_SOFT) |
52 | 79 |
|
53 | 80 | ax.spines["top"].set_visible(False) |
54 | 81 | ax.spines["right"].set_visible(False) |
55 | 82 | for s in ("left", "bottom"): |
56 | 83 | ax.spines[s].set_color(INK_SOFT) |
57 | 84 |
|
58 | | -ax.yaxis.grid(True, alpha=0.10, linewidth=0.8, color=INK) |
| 85 | +ax.yaxis.grid(True, alpha=0.15, linewidth=0.8, color=INK) |
59 | 86 |
|
60 | | -leg = ax.legend(fontsize=16, loc="upper left") |
| 87 | +leg = ax.legend(fontsize=8, loc="upper left") |
61 | 88 | leg.get_frame().set_facecolor(ELEVATED_BG) |
62 | 89 | leg.get_frame().set_edgecolor(INK_SOFT) |
63 | 90 | plt.setp(leg.get_texts(), color=INK_SOFT) |
64 | 91 |
|
65 | 92 | plt.tight_layout() |
66 | | -plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) |
| 93 | +plt.savefig(f"plot-{THEME}.png", dpi=400, facecolor=PAGE_BG) # bbox_inches stays default (None) |
0 commit comments