|
1 | 1 | """ pyplots.ai |
2 | 2 | area-basic: Basic Area Chart |
3 | 3 | Library: pygal 3.1.0 | Python 3.14.2 |
4 | | -Quality: 94/100 | Created: 2025-12-23 |
| 4 | +Quality: 87/100 | Created: 2025-12-23 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import pygal |
8 | 8 | from pygal.style import Style |
9 | 9 |
|
10 | 10 |
|
11 | | -# Data - Daily website visitors over a month |
| 11 | +# Data - Daily website visitors over a month (three distinct phases) |
| 12 | +# Phase 1 (Days 1-10): Stable baseline with weekend dips |
| 13 | +# Phase 2 (Days 11-20): Growth period with increasing trend |
| 14 | +# Phase 3 (Days 21-30): Peak plateau with high variability |
12 | 15 | days = list(range(1, 31)) |
13 | 16 | visitors = [ |
14 | 17 | 1250, |
|
17 | 20 | 1180, |
18 | 21 | 980, |
19 | 22 | 890, |
20 | | - 920, |
| 23 | + 920, # Week 1: baseline, weekend dip |
21 | 24 | 1340, |
22 | 25 | 1520, |
23 | | - 1680, |
| 26 | + 1680, # Early week 2: recovery |
24 | 27 | 1590, |
25 | 28 | 1450, |
26 | 29 | 1120, |
27 | | - 1080, |
| 30 | + 1080, # Mid-month dip (weekend) |
28 | 31 | 1560, |
29 | 32 | 1720, |
30 | 33 | 1890, |
31 | 34 | 2010, |
32 | 35 | 1850, |
33 | 36 | 1420, |
34 | | - 1380, |
| 37 | + 1380, # Growth phase with weekend dip |
35 | 38 | 1680, |
36 | 39 | 1920, |
37 | 40 | 2150, |
38 | 41 | 2080, |
39 | 42 | 1950, |
40 | 43 | 1620, |
41 | | - 1540, |
| 44 | + 1540, # Peak phase |
42 | 45 | 1780, |
43 | | - 1920, |
| 46 | + 1920, # Final uptick |
44 | 47 | ] |
45 | 48 |
|
46 | | -# Custom style for 4800x2700 canvas |
| 49 | +# Key data points |
| 50 | +peak_idx = visitors.index(max(visitors)) |
| 51 | +low_idx = visitors.index(min(visitors)) |
| 52 | + |
| 53 | +# Trend line (simple linear fit using endpoint averages) |
| 54 | +n = len(visitors) |
| 55 | +first_half_avg = sum(visitors[: n // 2]) / (n // 2) |
| 56 | +second_half_avg = sum(visitors[n // 2 :]) / (n // 2) |
| 57 | +slope = (second_half_avg - first_half_avg) / (n // 2) |
| 58 | +trend_start = first_half_avg - slope * (n // 4) |
| 59 | +trend = [trend_start + slope * i for i in range(n)] |
| 60 | + |
| 61 | +# Custom style for 4800x2700 canvas — refined typography and subtle chrome |
47 | 62 | custom_style = Style( |
48 | 63 | background="white", |
49 | 64 | plot_background="white", |
50 | | - foreground="#333", |
51 | | - foreground_strong="#333", |
52 | | - foreground_subtle="#666", |
53 | | - colors=("#306998",), |
54 | | - title_font_size=72, |
55 | | - label_font_size=48, |
56 | | - major_label_font_size=42, |
57 | | - legend_font_size=42, |
58 | | - value_font_size=36, |
59 | | - tooltip_font_size=36, |
60 | | - opacity=0.4, |
61 | | - opacity_hover=0.6, |
| 65 | + foreground="#2d2d2d", |
| 66 | + foreground_strong="#2d2d2d", |
| 67 | + foreground_subtle="#e0e0e0", |
| 68 | + colors=("#306998", "#c45a00", "#7b2d8e", "#0e7c6b"), |
| 69 | + font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 70 | + title_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 71 | + title_font_size=56, |
| 72 | + label_font_size=40, |
| 73 | + major_label_font_size=36, |
| 74 | + value_font_size=32, |
| 75 | + legend_font_size=34, |
| 76 | + legend_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 77 | + label_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 78 | + major_label_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 79 | + value_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 80 | + opacity=0.40, |
| 81 | + opacity_hover=0.55, |
| 82 | + guide_stroke_color="#e0e0e0", |
| 83 | + guide_stroke_dasharray="3,3", |
| 84 | + major_guide_stroke_color="#cccccc", |
| 85 | + major_guide_stroke_dasharray="6,3", |
| 86 | + stroke_opacity=1.0, |
| 87 | + stroke_opacity_hover=1.0, |
| 88 | + tooltip_font_size=28, |
| 89 | + tooltip_font_family="DejaVu Sans, Helvetica, Arial, sans-serif", |
| 90 | + tooltip_border_radius=8, |
62 | 91 | ) |
63 | 92 |
|
64 | | -# Create area chart (Line with fill=True) |
| 93 | +# Create area chart — cubic interpolation for smooth curves (distinctive pygal feature) |
65 | 94 | chart = pygal.Line( |
66 | 95 | width=4800, |
67 | 96 | height=2700, |
68 | | - title="area-basic \u00b7 pygal \u00b7 pyplots.ai", |
| 97 | + title="Daily Website Visitors \u00b7 area-basic \u00b7 pygal \u00b7 pyplots.ai", |
69 | 98 | x_title="Day of Month", |
70 | | - y_title="Visitors (per day)", |
| 99 | + y_title="Number of Visitors (count)", |
71 | 100 | style=custom_style, |
72 | 101 | fill=True, |
73 | 102 | show_dots=True, |
74 | | - dots_size=8, |
75 | | - stroke_style={"width": 4}, |
| 103 | + dots_size=10, |
| 104 | + stroke_style={"width": 5}, |
76 | 105 | show_y_guides=True, |
77 | 106 | show_x_guides=False, |
78 | 107 | x_label_rotation=0, |
| 108 | + show_legend=True, |
79 | 109 | legend_at_bottom=True, |
80 | | - truncate_legend=-1, |
| 110 | + legend_box_size=28, |
81 | 111 | value_formatter=lambda x: f"{x:,.0f}", |
| 112 | + x_value_formatter=lambda x: f"Day {x}", |
| 113 | + interpolate="cubic", |
| 114 | + interpolation_precision=250, |
| 115 | + min_scale=4, |
| 116 | + max_scale=8, |
| 117 | + margin_bottom=120, |
| 118 | + margin_left=80, |
| 119 | + margin_right=40, |
| 120 | + margin_top=60, |
| 121 | + spacing=12, |
| 122 | + show_minor_x_labels=True, |
| 123 | + show_minor_y_labels=True, |
| 124 | + tooltip_border_radius=8, |
| 125 | + tooltip_fancy_mode=True, |
| 126 | + show_only_major_dots=False, |
82 | 127 | ) |
83 | 128 |
|
84 | | -# Add data - show every 5th day label for readability |
| 129 | +# Main area series with smooth cubic interpolation |
| 130 | +chart.add("Daily Visitors", visitors, fill=True, stroke_style={"width": 5}) |
| 131 | + |
| 132 | +# Trend line (dashed, no fill) with contrasting dark orange color |
| 133 | +chart.add( |
| 134 | + f"Trend (+{slope:.0f} visitors/day)", |
| 135 | + [round(t) for t in trend], |
| 136 | + fill=False, |
| 137 | + show_dots=False, |
| 138 | + stroke_style={"width": 4, "dasharray": "20, 12"}, |
| 139 | +) |
| 140 | + |
| 141 | +# Peak marker (dark purple - colorblind-safe) |
| 142 | +peak_series = [None] * n |
| 143 | +peak_series[peak_idx] = { |
| 144 | + "value": visitors[peak_idx], |
| 145 | + "label": f"Peak: {visitors[peak_idx]:,} visitors (Day {peak_idx + 1})", |
| 146 | +} |
| 147 | +chart.add(f"Peak: {visitors[peak_idx]:,}", peak_series, fill=False, show_dots=True, dots_size=22, stroke=False) |
| 148 | + |
| 149 | +# Low marker (teal - colorblind-safe) |
| 150 | +low_series = [None] * n |
| 151 | +low_series[low_idx] = {"value": visitors[low_idx], "label": f"Low: {visitors[low_idx]:,} visitors (Day {low_idx + 1})"} |
| 152 | +chart.add(f"Low: {visitors[low_idx]:,}", low_series, fill=False, show_dots=True, dots_size=22, stroke=False) |
| 153 | + |
| 154 | +# X-axis labels — major every 5th day for clean spacing |
85 | 155 | chart.x_labels = [str(d) if d % 5 == 0 or d == 1 else "" for d in days] |
86 | | -chart.add("Daily Visitors", visitors) |
87 | 156 |
|
88 | | -# Save outputs |
| 157 | +# Save outputs (SVG-native format + PNG via cairosvg) |
89 | 158 | chart.render_to_file("plot.html") |
90 | 159 | chart.render_to_png("plot.png") |
0 commit comments