|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | span-basic: Basic Span Plot (Highlighted Region) |
3 | | -Library: pygal 3.1.0 | Python 3.13.11 |
4 | | -Quality: 94/100 | Created: 2025-12-17 |
| 3 | +Library: pygal 3.1.0 | Python 3.13.13 |
| 4 | +Quality: 86/100 | Created: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | +import sys |
| 9 | + |
7 | 10 | import numpy as np |
8 | | -import pygal |
9 | | -from pygal.style import Style |
10 | 11 |
|
11 | 12 |
|
12 | | -# Data - Stock prices with highlighted recession period |
| 13 | +# Pop script dir so this file (pygal.py) doesn't shadow the installed pygal package |
| 14 | +_script_dir = sys.path.pop(0) |
| 15 | +import pygal # noqa: E402 |
| 16 | +from pygal.style import Style # noqa: E402 |
| 17 | + |
| 18 | + |
| 19 | +sys.path.insert(0, _script_dir) |
| 20 | + |
| 21 | +# Theme tokens |
| 22 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 23 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 24 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 25 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 26 | + |
| 27 | +# Boost span opacity on dark backgrounds so fills stay visible as "highlight regions" |
| 28 | +SPAN_OPACITY = ".45" if THEME == "dark" else ".25" |
| 29 | + |
| 30 | +OKABE_ITO = ("#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442") |
| 31 | + |
| 32 | +# Data - Stock prices with highlighted events |
13 | 33 | np.random.seed(42) |
14 | | -dates = np.arange(2006, 2016, 0.1) # 10 years of data |
15 | | -# Simulate stock price with trend and volatility |
| 34 | +dates = np.arange(2006, 2016, 0.1) |
16 | 35 | price = 100 + np.cumsum(np.random.randn(len(dates)) * 2) |
17 | | -# Add a dip during recession period (2008-2009) |
18 | 36 | recession_mask = (dates >= 2008) & (dates < 2010) |
19 | 37 | price[recession_mask] -= np.linspace(0, 30, recession_mask.sum()) |
20 | 38 | price[dates >= 2010] -= 30 |
21 | | -price = price + np.abs(price.min()) + 50 # Keep positive |
| 39 | +price = price + np.abs(price.min()) + 50 |
22 | 40 |
|
23 | | -# Calculate range for spans |
24 | | -y_min = 40 |
25 | | -y_max = price.max() + 20 |
| 41 | +y_min = 40.0 |
| 42 | +y_max = float(price.max()) + 20 |
26 | 43 |
|
27 | | -# Custom style with scaled sizes for 4800x2700 output |
| 44 | +# Style |
28 | 45 | custom_style = Style( |
29 | | - background="white", |
30 | | - plot_background="white", |
31 | | - foreground="#333333", |
32 | | - foreground_strong="#333333", |
33 | | - foreground_subtle="#666666", |
34 | | - colors=("#FFD43B", "#D62728", "#306998"), # Yellow span, Red span, Blue line |
35 | | - opacity=".25", # Semi-transparent for span regions |
36 | | - opacity_hover=".4", |
37 | | - title_font_size=60, |
38 | | - label_font_size=36, |
39 | | - major_label_font_size=36, |
40 | | - legend_font_size=36, |
41 | | - value_font_size=32, |
42 | | - stroke_width="4", |
| 46 | + background=PAGE_BG, |
| 47 | + plot_background=PAGE_BG, |
| 48 | + foreground=INK, |
| 49 | + foreground_strong=INK, |
| 50 | + foreground_subtle=INK_MUTED, |
| 51 | + colors=OKABE_ITO, |
| 52 | + opacity=SPAN_OPACITY, |
| 53 | + opacity_hover=".5", |
| 54 | + title_font_size=32, |
| 55 | + label_font_size=22, |
| 56 | + major_label_font_size=18, |
| 57 | + legend_font_size=18, |
| 58 | + value_font_size=16, |
| 59 | + stroke_width=3, |
43 | 60 | ) |
44 | 61 |
|
45 | | -# Create XY chart with fill enabled for span regions |
| 62 | +# Plot — x-guides disabled for a cleaner grid; human_readable for polished tooltips |
46 | 63 | chart = pygal.XY( |
47 | 64 | style=custom_style, |
48 | 65 | width=4800, |
49 | 66 | height=2700, |
50 | | - title="span-basic · pygal · pyplots.ai", |
| 67 | + title="span-basic · pygal · anyplot.ai", |
51 | 68 | x_title="Year", |
52 | 69 | y_title="Price ($)", |
53 | 70 | show_dots=False, |
54 | | - show_x_guides=True, |
| 71 | + show_x_guides=False, |
55 | 72 | show_y_guides=True, |
56 | 73 | range=(y_min, y_max), |
57 | 74 | xrange=(2005.5, 2016.5), |
58 | | - fill=True, # Enable fill for closed polygons |
| 75 | + fill=True, |
59 | 76 | stroke=True, |
60 | 77 | dots_size=0, |
| 78 | + human_readable=True, |
61 | 79 | ) |
62 | 80 |
|
63 | | -# Vertical span: Recession Period (2008-2009) - full height rectangle |
64 | | -# Create closed polygon: bottom-left -> top-left -> top-right -> bottom-right -> close |
65 | | -recession_span = [ |
66 | | - (2008, y_min), |
67 | | - (2008, y_max), |
68 | | - (2009, y_max), |
69 | | - (2009, y_min), |
70 | | - (2008, y_min), # Close the polygon |
71 | | -] |
| 81 | +# Vertical span: Recession Period (2008-2009) — closed polygon |
| 82 | +recession_span = [(2008, y_min), (2008, y_max), (2009, y_max), (2009, y_min), (2008, y_min)] |
72 | 83 | chart.add("Recession Period", recession_span) |
73 | 84 |
|
74 | | -# Horizontal span: Risk Zone (60-80) - full width rectangle |
75 | | -risk_span = [ |
76 | | - (2005.5, 60), |
77 | | - (2005.5, 80), |
78 | | - (2016.5, 80), |
79 | | - (2016.5, 60), |
80 | | - (2005.5, 60), # Close the polygon |
81 | | -] |
| 85 | +# Horizontal span: Risk Zone (price 60–80) — closed polygon |
| 86 | +risk_span = [(2005.5, 60), (2005.5, 80), (2016.5, 80), (2016.5, 60), (2005.5, 60)] |
82 | 87 | chart.add("Risk Zone", risk_span) |
83 | 88 |
|
84 | | -# Main line data (no fill, just stroke) |
85 | | -main_data = [(float(x), float(y)) for x, y in zip(dates, price, strict=True)] |
| 89 | +# Main line — dict format enables per-point custom tooltip labels (pygal interactive feature) |
| 90 | +main_data = [{"value": (float(x), float(y)), "label": f"${y:.0f}"} for x, y in zip(dates, price, strict=True)] |
86 | 91 | chart.add("Stock Price", main_data, fill=False, stroke=True) |
87 | 92 |
|
88 | | -# Save outputs |
89 | | -chart.render_to_png("plot.png") |
90 | | -chart.render_to_file("plot.html") |
| 93 | +# Save |
| 94 | +chart.render_to_png(f"plot-{THEME}.png") |
| 95 | +with open(f"plot-{THEME}.html", "wb") as f: |
| 96 | + f.write(chart.render()) |
0 commit comments