Skip to content

Commit abd3785

Browse files
feat(pygal): implement slope-basic (#5643)
## Implementation: `slope-basic` - python/pygal Implements the **python/pygal** version of `slope-basic`. **File:** `plots/slope-basic/implementations/python/pygal.py` **Parent Issue:** #981 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25177667873)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent a1da3a6 commit abd3785

2 files changed

Lines changed: 292 additions & 80 deletions

File tree

Lines changed: 67 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,91 +1,99 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
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
55
"""
66

7-
import pygal
8-
from pygal.style import Style
7+
import os
8+
import sys
99

1010

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+
]
1340
q1_sales = [85, 72, 95, 45, 68, 52, 78, 62, 88, 40]
1441
q4_sales = [92, 58, 102, 75, 65, 71, 82, 48, 95, 55]
1542

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]]
2845

29-
# Build color tuple: increasing (blue) then decreasing (orange)
3046
series_colors = tuple([COLOR_INCREASE] * len(increasing) + [COLOR_DECREASE] * len(decreasing))
3147

32-
# Custom style for 4800x2700 px with clean legend (2 grouped entries)
3348
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,
3954
colors=series_colors,
40-
title_font_size=72,
55+
title_font_size=64,
4156
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,
4762
)
4863

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
5065
chart = pygal.Line(
5166
width=4800,
5267
height=2700,
53-
title="slope-basic · pygal · pyplots.ai",
68+
title="slope-basic · pygal · anyplot.ai",
5469
x_title="Time Period",
5570
y_title="Sales (units)",
5671
style=custom_style,
5772
show_dots=True,
58-
dots_size=16,
59-
stroke_style={"width": 5},
73+
dots_size=18,
74+
stroke_style={"width": 6},
6075
show_y_guides=True,
6176
show_x_guides=False,
6277
show_legend=True,
63-
legend_at_bottom=True,
64-
legend_box_size=32,
65-
truncate_legend=-1,
6678
interpolate=None,
67-
margin=120,
79+
margin=140,
6880
print_values=False,
69-
print_labels=False,
70-
range=(30, 110),
81+
print_labels=True,
82+
range=(30, 115),
83+
margin_right=340,
7184
)
7285

73-
# X-axis labels (two time points)
7486
chart.x_labels = ["Q1 2024", "Q4 2024"]
7587

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

Comments
 (0)