|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | slope-basic: Basic Slope Chart (Slopegraph) |
3 | | -Library: pygal 3.1.0 | Python 3.13.11 |
4 | | -Quality: 86/100 | Created: 2025-12-23 |
| 3 | +Library: pygal 3.1.0 | Python 3.13.13 |
| 4 | +Quality: 86/100 | Updated: 2026-04-30 |
5 | 5 | """ |
6 | 6 |
|
7 | | -import pygal |
8 | | -from pygal.style import Style |
| 7 | +import os |
| 8 | +import sys |
9 | 9 |
|
10 | 10 |
|
11 | | -# Data - Sales figures comparing Q1 vs Q4 for 10 products |
12 | | -products = ["A", "B", "C", "D", "E", "F", "G", "H", "I", "J"] |
| 11 | +# Pop script dir so this file (pygal.py) doesn't shadow the installed pygal package |
| 12 | +_script_dir = sys.path.pop(0) |
| 13 | +import pygal # noqa: E402 |
| 14 | +from pygal.style import Style # noqa: E402 |
| 15 | + |
| 16 | + |
| 17 | +sys.path.insert(0, _script_dir) |
| 18 | + |
| 19 | +THEME = os.getenv("ANYPLOT_THEME", "light") |
| 20 | +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
| 21 | +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
| 22 | +INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F" |
| 23 | + |
| 24 | +COLOR_INCREASE = "#009E73" # Okabe-Ito position 1 — upward change |
| 25 | +COLOR_DECREASE = "#D55E00" # Okabe-Ito position 2 — downward change |
| 26 | + |
| 27 | +# Realistic product category data — Q1 vs Q4 sales comparison |
| 28 | +categories = [ |
| 29 | + "Electronics", |
| 30 | + "Apparel", |
| 31 | + "Furniture", |
| 32 | + "Automotive", |
| 33 | + "Food & Bev", |
| 34 | + "Sporting Goods", |
| 35 | + "Home Decor", |
| 36 | + "Office Supplies", |
| 37 | + "Beauty", |
| 38 | + "Toys", |
| 39 | +] |
13 | 40 | q1_sales = [85, 72, 95, 45, 68, 52, 78, 62, 88, 40] |
14 | 41 | q4_sales = [92, 58, 102, 75, 65, 71, 82, 48, 95, 55] |
15 | 42 |
|
16 | | -# Colorblind-friendly colors for increase vs decrease (spec requirement) |
17 | | -COLOR_INCREASE = "#2166AC" # Blue - value went up |
18 | | -COLOR_DECREASE = "#D6604D" # Orange-red - value went down |
19 | | - |
20 | | -# Separate products by direction for proper color grouping |
21 | | -increasing = [] |
22 | | -decreasing = [] |
23 | | -for i, p in enumerate(products): |
24 | | - if q4_sales[i] >= q1_sales[i]: |
25 | | - increasing.append((p, q1_sales[i], q4_sales[i])) |
26 | | - else: |
27 | | - decreasing.append((p, q1_sales[i], q4_sales[i])) |
| 43 | +increasing = [(c, q1_sales[i], q4_sales[i]) for i, c in enumerate(categories) if q4_sales[i] >= q1_sales[i]] |
| 44 | +decreasing = [(c, q1_sales[i], q4_sales[i]) for i, c in enumerate(categories) if q4_sales[i] < q1_sales[i]] |
28 | 45 |
|
29 | | -# Build color tuple: increasing (blue) then decreasing (orange) |
30 | 46 | series_colors = tuple([COLOR_INCREASE] * len(increasing) + [COLOR_DECREASE] * len(decreasing)) |
31 | 47 |
|
32 | | -# Custom style for 4800x2700 px with clean legend (2 grouped entries) |
33 | 48 | custom_style = Style( |
34 | | - background="white", |
35 | | - plot_background="white", |
36 | | - foreground="#333333", |
37 | | - foreground_strong="#333333", |
38 | | - foreground_subtle="#666666", |
| 49 | + background=PAGE_BG, |
| 50 | + plot_background=PAGE_BG, |
| 51 | + foreground=INK, |
| 52 | + foreground_strong=INK, |
| 53 | + foreground_subtle=INK_MUTED, |
39 | 54 | colors=series_colors, |
40 | | - title_font_size=72, |
| 55 | + title_font_size=64, |
41 | 56 | label_font_size=44, |
42 | | - major_label_font_size=48, |
43 | | - legend_font_size=48, |
44 | | - value_font_size=36, |
45 | | - stroke_width=5, |
46 | | - value_label_font_size=32, |
| 57 | + major_label_font_size=44, |
| 58 | + legend_font_size=38, |
| 59 | + value_font_size=32, |
| 60 | + value_label_font_size=36, |
| 61 | + stroke_width=6, |
47 | 62 | ) |
48 | 63 |
|
49 | | -# Create slope chart using Line chart with only 2 x-axis points |
| 64 | +# Slope chart: Line chart with only 2 x-axis time points |
50 | 65 | chart = pygal.Line( |
51 | 66 | width=4800, |
52 | 67 | height=2700, |
53 | | - title="slope-basic · pygal · pyplots.ai", |
| 68 | + title="slope-basic · pygal · anyplot.ai", |
54 | 69 | x_title="Time Period", |
55 | 70 | y_title="Sales (units)", |
56 | 71 | style=custom_style, |
57 | 72 | show_dots=True, |
58 | | - dots_size=16, |
59 | | - stroke_style={"width": 5}, |
| 73 | + dots_size=18, |
| 74 | + stroke_style={"width": 6}, |
60 | 75 | show_y_guides=True, |
61 | 76 | show_x_guides=False, |
62 | 77 | show_legend=True, |
63 | | - legend_at_bottom=True, |
64 | | - legend_box_size=32, |
65 | | - truncate_legend=-1, |
66 | 78 | interpolate=None, |
67 | | - margin=120, |
| 79 | + margin=140, |
68 | 80 | print_values=False, |
69 | | - print_labels=False, |
70 | | - range=(30, 110), |
| 81 | + print_labels=True, |
| 82 | + range=(30, 115), |
| 83 | + margin_right=340, |
71 | 84 | ) |
72 | 85 |
|
73 | | -# X-axis labels (two time points) |
74 | 86 | chart.x_labels = ["Q1 2024", "Q4 2024"] |
75 | 87 |
|
76 | | -# Add series - first entry in each group shows in legend, rest use None to hide |
77 | | -for i, (p, start, end) in enumerate(increasing): |
78 | | - chart.add( |
79 | | - "Increasing" if i == 0 else None, |
80 | | - [{"value": start, "label": f"Product {p}: {start}"}, {"value": end, "label": f"Product {p}: {end}"}], |
81 | | - ) |
82 | | - |
83 | | -for i, (p, start, end) in enumerate(decreasing): |
84 | | - chart.add( |
85 | | - "Decreasing" if i == 0 else None, |
86 | | - [{"value": start, "label": f"Product {p}: {start}"}, {"value": end, "label": f"Product {p}: {end}"}], |
87 | | - ) |
88 | | - |
89 | | -# Save as PNG and HTML (HTML provides interactive hover labels) |
90 | | -chart.render_to_png("plot.png") |
91 | | -chart.render_to_file("plot.html") |
| 88 | +# Increasing categories — Okabe-Ito green |
| 89 | +for c, start, end in increasing: |
| 90 | + chart.add(c, [{"value": start, "label": c}, {"value": end, "label": c}]) |
| 91 | + |
| 92 | +# Decreasing categories — Okabe-Ito vermillion |
| 93 | +for c, start, end in decreasing: |
| 94 | + chart.add(c, [{"value": start, "label": c}, {"value": end, "label": c}]) |
| 95 | + |
| 96 | +# Save PNG and interactive HTML |
| 97 | +chart.render_to_png(f"plot-{THEME}.png") |
| 98 | +with open(f"plot-{THEME}.html", "wb") as f: |
| 99 | + f.write(chart.render()) |
0 commit comments