Spaces:
Running
Running
Download make_examples.py from surogate/chart-check: direct link, hf CLI and curl.
- Browser
- Download file 11.8 kB
-
https://huggingface.co/spaces/surogate/chart-check/resolve/main/make_examples.py
- Command line
-
hf download hf://spaces/surogate/chart-check/make_examples.py
-
curl -L -o make_examples.py https://huggingface.co/spaces/surogate/chart-check/resolve/main/make_examples.py
11.8 kB
| """Draw the example charts in examples/. Run once after an edit; the PNGs are committed. | |
| python make_examples.py | |
| matplotlib is only needed here, not in the Space. Every company, place and number is made up. | |
| """ | |
| from pathlib import Path | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt # noqa: E402 | |
| import numpy as np # noqa: E402 | |
| from matplotlib.patches import Polygon # noqa: E402 | |
| OUT = Path(__file__).parent / "examples" | |
| SIZE, DPI = (10, 6.25), 110 | |
| def figure(bg="white"): | |
| fig, ax = plt.subplots(figsize=SIZE, dpi=DPI, facecolor=bg) | |
| ax.set_facecolor(bg) | |
| fig.subplots_adjust(left=0.09, right=0.93, top=0.8, bottom=0.14) | |
| for s in ("top", "right"): | |
| ax.spines[s].set_visible(False) | |
| return fig, ax | |
| def titles(fig, title, sub, source, color="#111", sub_color="#555"): | |
| fig.text(0.09, 0.93, title, fontsize=22, fontweight="bold", color=color) | |
| fig.text(0.09, 0.87, sub, fontsize=13, color=sub_color) | |
| fig.text(0.09, 0.03, source, fontsize=9.5, color=sub_color) | |
| def save(fig, name): | |
| fig.savefig(OUT / f"{name}.png", facecolor=fig.get_facecolor()) | |
| plt.close(fig) | |
| def upside_down_deaths(): | |
| years = np.arange(2012, 2026) | |
| deaths = [196, 188, 192, 185, 190, 183, 187, 191, 228, 251, 262, 280, 297, 311] | |
| fig, ax = figure() | |
| ax.fill_between(years, deaths, 0, color="#b3121f", alpha=0.95) | |
| ax.plot(years, deaths, color="#7a0a14", lw=2) | |
| ax.set_ylim(350, 0) # upside down: 0 at the top | |
| ax.set_xlim(2012, 2025) | |
| ax.set_yticks([0, 50, 100, 150, 200, 250, 300, 350]) | |
| ax.axvline(2019, color="white", lw=1.5, ls="--") | |
| ax.text(2019.15, 30, "2019: speed-camera law", color="white", fontsize=12, fontweight="bold") | |
| ax.set_ylabel("Road deaths per year") | |
| titles(fig, "Road deaths in Norvale", "Yearly road deaths, 2012–2025", "Source: Norvale Transport Office") | |
| save(fig, "upside-down-deaths") | |
| def ice_cream_sharks(): | |
| years = np.arange(2015, 2026) | |
| ice = [51.2, 51.9, 52.3, 53.4, 53.9, 54.1, 55.6, 56.2, 57.0, 57.4, 58.3] | |
| sharks = [2, 3, 3, 5, 5, 6, 7, 8, 9, 9, 11] | |
| fig, ax = figure() | |
| ax.plot(years, ice, color="#e07b00", lw=4, marker="o", ms=8, label="Ice cream sales (€ million)") | |
| ax.set_ylim(50, 59) | |
| ax.set_ylabel("Ice cream sales (€ million)", color="#e07b00", fontsize=12) | |
| ax.tick_params(axis="y", colors="#e07b00") | |
| ax2 = ax.twinx() | |
| ax2.plot(years, sharks, color="#1560bd", lw=4, marker="s", ms=8, label="Shark attacks") | |
| ax2.set_ylim(0, 12) | |
| ax2.set_ylabel("Shark attacks", color="#1560bd", fontsize=12) | |
| ax2.tick_params(axis="y", colors="#1560bd") | |
| ax2.spines["top"].set_visible(False) | |
| ax.legend(handles=ax.get_lines() + ax2.get_lines(), loc="upper left", frameon=False, fontsize=12) | |
| titles(fig, "Ice cream sales vs. shark attacks", "Coral Bay, 2015–2025. Left axis: sales. Right axis: attacks.", | |
| "Source: Coral Bay Tourism Board, Coral Bay Lifeguard Service") | |
| save(fig, "ice-cream-sharks") | |
| def users_still_growing(): | |
| new = [1.9, 1.8, 1.7, 1.6, 1.4, 1.2, 1.0, 0.85, 0.7, 0.55, 0.45, 0.35] | |
| total = np.cumsum(new) | |
| months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] | |
| fig, ax = figure("#0f1b2d") | |
| ax.bar(months, total, color="#39d98a", width=0.7) | |
| for i, v in enumerate(total): | |
| ax.text(i, v + 0.2, f"{v:.1f}M", ha="center", color="white", fontsize=11, fontweight="bold") | |
| ax.set_ylim(0, 15) | |
| ax.set_ylabel("Total users since launch (millions)", color="#c9d3e0") | |
| ax.tick_params(colors="#c9d3e0") | |
| for s in ("left", "bottom"): | |
| ax.spines[s].set_color("#51607a") | |
| titles(fig, f"Pingly: {total[-1]:.1f} million users and counting", "Cumulative sign-ups since launch, 2025", | |
| "Pingly investor update, Q4 2025", color="white", sub_color="#c9d3e0") | |
| save(fig, "users-still-growing") | |
| def pie_3d(): | |
| shares = {"Brightwave": 24, "Kestrel": 29, "Novaline": 26, "Others": 21} | |
| colors = {"Brightwave": "#ff5a1f", "Kestrel": "#6b7a8f", "Novaline": "#9aa7b8", "Others": "#c8d0da"} | |
| fig, ax = plt.subplots(figsize=SIZE, dpi=DPI, facecolor="white") | |
| ax.set_aspect("equal") | |
| ax.axis("off") | |
| tilt, depth = 0.42, 0.22 | |
| # Brightwave sits in front (around 270°) and is pulled out toward the viewer. | |
| start = 270 - shares["Brightwave"] * 3.6 / 2 | |
| wedges = [] | |
| for name, pct in shares.items(): | |
| a0, a1 = start, start + pct * 3.6 | |
| start = a1 | |
| mid = np.radians((a0 + a1) / 2) | |
| off = 0.22 if name == "Brightwave" else 0.0 | |
| wedges.append((name, pct, a0, a1, off * np.cos(mid), off * np.sin(mid) * tilt)) | |
| def rim(a0, a1, dx, dy, dz=0.0): | |
| t = np.radians(np.linspace(a0, a1, 80)) | |
| return np.column_stack([dx + np.cos(t), dy + tilt * np.sin(t) - dz]) | |
| def shade(c, f): | |
| r, g, b = matplotlib.colors.to_rgb(c) | |
| return (r * f, g * f, b * f) | |
| # back to front: wedges whose middle is further up are drawn first | |
| for name, pct, a0, a1, dx, dy in sorted(wedges, key=lambda w: -np.sin(np.radians((w[2] + w[3]) / 2))): | |
| top = rim(a0, a1, dx, dy) | |
| side = np.vstack([top, rim(a1, a0, dx, dy, depth)]) | |
| ax.add_patch(Polygon(side, color=shade(colors[name], 0.7), lw=0)) | |
| ax.add_patch(Polygon(np.vstack([[dx, dy], top]), color=colors[name], ec="white", lw=1.5)) | |
| mid = np.radians((a0 + a1) / 2) | |
| big = name == "Brightwave" | |
| ax.text(dx + 0.62 * np.cos(mid), dy + 0.62 * tilt * np.sin(mid) - (0.05 if big else 0), f"{name}\n{pct}%", | |
| ha="center", va="center", fontsize=20 if big else 11, fontweight="bold", | |
| color="white" if big else "#222") | |
| ax.set_xlim(-1.5, 1.5) | |
| ax.set_ylim(-0.95, 0.75) | |
| fig.subplots_adjust(left=0.02, right=0.98, top=0.82, bottom=0.08) | |
| titles(fig, "Smart speaker market share", "Eastland, 2025 unit sales", "Source: Brightwave marketing team") | |
| save(fig, "pie-3d") | |
| def revenue_from_98(): | |
| q = ["Q1 2025", "Q2 2025", "Q3 2025", "Q4 2025"] | |
| rev = [98.4, 98.9, 99.3, 100.6] | |
| fig, ax = figure("#101828") | |
| ax.bar(q, rev, color=["#475467"] * 3 + ["#fdb022"], width=0.6) | |
| for i, v in enumerate(rev): | |
| ax.text(i, v + 0.05, f"${v:.1f}M", ha="center", color="white", fontsize=15, fontweight="bold") | |
| ax.set_ylim(98, 101) | |
| ax.set_yticks([98, 98.5, 99, 99.5, 100, 100.5, 101]) | |
| ax.tick_params(colors="#d0d5dd", labelsize=12) | |
| for s in ("left", "bottom"): | |
| ax.spines[s].set_color("#475467") | |
| ax.set_ylabel("Revenue ($ million)", color="#d0d5dd") | |
| titles(fig, "Q4: record revenue", "Halvorsen Logistics, quarterly revenue", "Halvorsen Logistics Q4 2025 earnings deck", | |
| color="white", sub_color="#d0d5dd") | |
| save(fig, "revenue-from-98") | |
| def best_three_months(): | |
| rng = np.random.default_rng(7) | |
| days = np.arange(92) | |
| walk = rng.normal(0, 0.12, 92).cumsum() | |
| price = 41 + 7.4 * days / 91 + walk - walk[-1] * days / 91 # ends at exactly $48.40 | |
| fig, ax = figure() | |
| ax.plot(days, price, color="#12b76a", lw=2.5) | |
| ax.fill_between(days, price, 0, color="#12b76a", alpha=0.12) | |
| ax.set_ylim(0, 55) | |
| ax.set_xlim(0, 91) | |
| ax.set_xticks([0, 30, 61, 91], ["1 Jul", "31 Jul", "31 Aug", "30 Sep"]) | |
| ax.yaxis.tick_right() | |
| ax.spines["left"].set_visible(False) | |
| ax.spines["right"].set_visible(True) | |
| ax.set_yticks(range(0, 51, 10), [f"${v}" for v in range(0, 51, 10)]) | |
| for i, r in enumerate(["1M", "3M", "1Y", "5Y"]): | |
| sel = r == "3M" | |
| fig.text(0.62 + i * 0.075, 0.87, r, fontsize=13, fontweight="bold", ha="center", | |
| color="white" if sel else "#667085", | |
| bbox=dict(boxstyle="round,pad=0.35", fc="#12b76a" if sel else "#f2f4f7", ec="none")) | |
| fig.text(0.09, 0.87, "HRBR $48.40 +18.0% (3M)", fontsize=14, color="#12b76a", fontweight="bold") | |
| # the 5-year view the post left out: flat, with the last 3 months shaded | |
| t = np.arange(60) | |
| five = 41 + np.sin(t / 4.0) * 3.5 + rng.normal(0, 0.6, 60) | |
| five[-3:] = [43.5, 46, 48.4] | |
| five[0] = 47.8 | |
| inset = fig.add_axes([0.14, 0.2, 0.42, 0.3], facecolor="white") | |
| inset.plot(t, five, color="#98a2b3", lw=1.8) | |
| inset.axvspan(57, 59, color="#12b76a", alpha=0.3) | |
| inset.set_ylim(30, 55) | |
| inset.set_xticks([0, 59], ["2021", "2026"], fontsize=9) | |
| inset.set_yticks([]) | |
| for s in ("top", "right", "left"): | |
| inset.spines[s].set_visible(False) | |
| inset.set_title("Same stock over 5 years: +1.2%", fontsize=13, fontweight="bold", color="#344054", loc="left") | |
| inset.text(58, 51, "these\n3 months", fontsize=9.5, color="#067647", ha="right", va="top") | |
| fig.text(0.09, 0.93, "Harbor Bank", fontsize=22, fontweight="bold", color="#111") | |
| fig.text(0.09, 0.03, "Share price, last 3 months · 2026", fontsize=9.5, color="#555") | |
| save(fig, "best-three-months") | |
| def log_flattening(): | |
| weeks = np.arange(1, 21) | |
| cases = np.round(3 * np.exp(np.cumsum(np.linspace(0.7, 0.12, 20)))).astype(int) | |
| fig, ax = figure() | |
| ax.plot(weeks, cases, color="#7a5af8", lw=3.5, marker="o", ms=6) | |
| ax.set_yscale("log") | |
| ax.set_ylim(1, 100000) | |
| ax.set_yticks([1, 10, 100, 1000, 10000, 100000], ["1", "10", "100", "1,000", "10,000", "100,000"]) | |
| ax.minorticks_off() | |
| ax.set_xticks([1, 5, 10, 15, 20]) | |
| ax.set_xlabel("Week of outbreak") | |
| ax.set_ylabel("Total measles cases") | |
| ax.axvspan(16, 20, color="#7a5af8", alpha=0.08) | |
| ax.text(18, 1.6, "last month", ha="center", color="#7a5af8", fontsize=11) | |
| titles(fig, "Measles in Ostmark County", f"Total confirmed cases: {cases[-1]:,}", "Source: Ostmark County Health Department") | |
| save(fig, "log-flattening") | |
| def crime_totals(): | |
| cities = ["Riverton\npop. 2,100,000", "Millbrook\npop. 310,000", "Ashby\npop. 95,000", "Elm Falls\npop. 38,000"] | |
| burglaries = [8400, 2100, 610, 420] | |
| fig, ax = figure() | |
| ax.bar(cities, burglaries, color=["#d92d20", "#98a2b3", "#98a2b3", "#98a2b3"], width=0.6) | |
| for i, v in enumerate(burglaries): | |
| ax.text(i, v + 150, f"{v:,}", ha="center", fontsize=15, fontweight="bold") | |
| ax.set_ylim(0, 9500) | |
| ax.set_ylabel("Reported burglaries, 2025") | |
| ax.tick_params(axis="x", labelsize=12) | |
| titles(fig, "Where are the most break-ins?", "Reported burglaries by city, 2025", "Source: State Police annual report") | |
| save(fig, "crime-totals") | |
| def honest_library(): | |
| years = ["2021", "2022", "2023", "2024", "2025"] | |
| loans = [41, 49, 58, 70, 83] | |
| fig, ax = figure() | |
| ax.bar(years, loans, color="#2e90fa", width=0.6) | |
| for i, v in enumerate(loans): | |
| ax.text(i, v + 1.5, f"{v}k", ha="center", fontsize=14, fontweight="bold") | |
| ax.set_ylim(0, 100) | |
| ax.set_ylabel("Books loaned (thousands)") | |
| titles(fig, "Tallow Creek Library loans", "Books loaned per year", "Source: Tallow Creek Public Library") | |
| save(fig, "honest-library") | |
| def honest_tram(): | |
| periods = ["2024\nbefore the tram", "2025\nafter the tram"] | |
| commute = [32.1, 29.0] | |
| fig, ax = figure() | |
| ax.bar(periods, commute, color=["#98a2b3", "#0e9384"], width=0.5) | |
| for i, v in enumerate(commute): | |
| ax.text(i, v + 0.8, f"{v:.1f} min", ha="center", fontsize=16, fontweight="bold") | |
| ax.set_ylim(0, 40) | |
| ax.set_ylabel("Average one-way commute (minutes)") | |
| ax.tick_params(axis="x", labelsize=13) | |
| titles(fig, "Commute times in Larkspur", "Average one-way commute, the year before and after the new tram line", | |
| "Source: Larkspur City Travel Survey") | |
| save(fig, "honest-tram") | |
| if __name__ == "__main__": | |
| OUT.mkdir(exist_ok=True) | |
| for draw in (upside_down_deaths, ice_cream_sharks, users_still_growing, pie_3d, revenue_from_98, | |
| best_three_months, log_flattening, crime_totals, honest_library, honest_tram): | |
| draw() | |