|
| 1 | +""" pyplots.ai |
| 2 | +line-timeseries-rolling: Time Series with Rolling Average Overlay |
| 3 | +Library: pygal 3.1.0 | Python 3.13.11 |
| 4 | +Quality: 91/100 | Created: 2025-12-30 |
| 5 | +""" |
| 6 | + |
| 7 | +import random |
| 8 | +from datetime import datetime, timedelta |
| 9 | + |
| 10 | +import pygal |
| 11 | +from pygal.style import Style |
| 12 | + |
| 13 | + |
| 14 | +# Seed for reproducibility |
| 15 | +random.seed(42) |
| 16 | + |
| 17 | +# Generate daily temperature readings for 4 months |
| 18 | +start_date = datetime(2024, 1, 1) |
| 19 | +dates = [start_date + timedelta(days=i) for i in range(120)] |
| 20 | + |
| 21 | +# Generate temperature data with seasonal trend and noise |
| 22 | +temperatures = [] |
| 23 | +for i in range(120): |
| 24 | + # Seasonal variation: winter to early spring |
| 25 | + seasonal = 5 + 8 * (i / 120) # Gradual warming from 5°C to 13°C |
| 26 | + noise = random.gauss(0, 3) |
| 27 | + temp = seasonal + noise |
| 28 | + temperatures.append(round(temp, 1)) |
| 29 | + |
| 30 | +# Calculate 7-day rolling average (None for first 6 days) |
| 31 | +window_size = 7 |
| 32 | +rolling_avg = [] |
| 33 | +for i in range(len(temperatures)): |
| 34 | + if i < window_size - 1: |
| 35 | + rolling_avg.append(None) # pygal handles None as gaps |
| 36 | + else: |
| 37 | + window = temperatures[i - window_size + 1 : i + 1] |
| 38 | + avg = sum(window) / window_size |
| 39 | + rolling_avg.append(round(avg, 1)) |
| 40 | + |
| 41 | +# Custom style for 4800x2700 canvas with larger fonts |
| 42 | +custom_style = Style( |
| 43 | + background="white", |
| 44 | + plot_background="white", |
| 45 | + foreground="#333333", |
| 46 | + foreground_strong="#333333", |
| 47 | + foreground_subtle="#666666", |
| 48 | + colors=("#306998", "#E67E22"), # Python Blue for raw, Orange for rolling avg (better visibility) |
| 49 | + title_font_size=72, |
| 50 | + label_font_size=48, |
| 51 | + major_label_font_size=42, |
| 52 | + legend_font_size=42, |
| 53 | + value_font_size=36, |
| 54 | + guide_stroke_color="#cccccc", |
| 55 | + guide_stroke_dasharray="2,4", |
| 56 | +) |
| 57 | + |
| 58 | +# Create line chart |
| 59 | +chart = pygal.Line( |
| 60 | + width=4800, |
| 61 | + height=2700, |
| 62 | + style=custom_style, |
| 63 | + title="line-timeseries-rolling · pygal · pyplots.ai", |
| 64 | + x_title="Date", |
| 65 | + y_title="Temperature (°C)", |
| 66 | + show_x_guides=False, |
| 67 | + show_y_guides=True, |
| 68 | + x_label_rotation=45, |
| 69 | + show_legend=True, |
| 70 | + legend_at_bottom=True, |
| 71 | + legend_at_bottom_columns=2, # Keep legend items together in 2 columns |
| 72 | + legend_box_size=24, # Larger legend box for visibility |
| 73 | + truncate_legend=-1, |
| 74 | + show_dots=False, |
| 75 | + stroke_style={"width": 4}, |
| 76 | + margin=60, |
| 77 | +) |
| 78 | + |
| 79 | +# Add raw temperature data (lighter line) |
| 80 | +chart.add("Raw Temperature", temperatures, stroke_style={"width": 2}) |
| 81 | + |
| 82 | +# Add rolling average (prominent line) |
| 83 | +chart.add("7-Day Rolling Average", rolling_avg, stroke_style={"width": 6}) |
| 84 | + |
| 85 | +# Set x-axis labels - show every 2 weeks |
| 86 | +x_labels = [] |
| 87 | +x_labels_major = [] |
| 88 | +for d in dates: |
| 89 | + if d.day in [1, 15]: # 1st and 15th of each month |
| 90 | + label = d.strftime("%b %d") |
| 91 | + x_labels.append(label) |
| 92 | + x_labels_major.append(label) |
| 93 | + else: |
| 94 | + x_labels.append("") |
| 95 | + |
| 96 | +chart.x_labels = x_labels |
| 97 | +chart.x_labels_major = x_labels_major |
| 98 | + |
| 99 | +# Save as HTML and PNG |
| 100 | +chart.render_to_file("plot.html") |
| 101 | +chart.render_to_png("plot.png") |
0 commit comments