Skip to content

Commit 63277b9

Browse files
feat(pygal): implement point-basic (#6387)
## Implementation: `point-basic` - python/pygal Implements the **python/pygal** version of `point-basic`. **File:** `plots/point-basic/implementations/python/pygal.py` **Parent Issue:** #2551 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/25685760658)* --------- 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 7210ffc commit 63277b9

2 files changed

Lines changed: 216 additions & 171 deletions

File tree

Lines changed: 61 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,106 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
point-basic: Point Estimate Plot
3-
Library: pygal 3.1.0 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-30
3+
Library: pygal 3.1.0 | Python 3.13.13
4+
Quality: 87/100 | Updated: 2026-05-11
55
"""
66

7+
import os
8+
79
import pygal
810
from pygal.style import Style
911

1012

11-
# Data: Treatment effects with 95% confidence intervals
12-
categories = ["Treatment A", "Treatment B", "Treatment C", "Treatment D", "Control"]
13-
estimates = [2.4, 1.8, 3.2, 0.9, 0.0]
14-
lower_bounds = [1.6, 0.9, 2.5, -0.2, -0.5]
15-
upper_bounds = [3.2, 2.7, 3.9, 2.0, 0.5]
13+
# Theme tokens
14+
THEME = os.getenv("ANYPLOT_THEME", "light")
15+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
16+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
17+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
18+
BRAND = "#009E73" # Okabe-Ito position 1
1619

17-
# Custom style for 4800x2700 canvas
18-
ci_color = "#306998"
19-
point_color = "#FFD43B"
20-
ref_color = "#888888"
20+
# Data: Sensor calibration accuracy with 95% confidence intervals
21+
categories = ["Sensor A", "Sensor B", "Sensor C", "Sensor D", "Sensor E"]
22+
estimates = [0.3, 0.8, -0.2, 1.1, 0.5]
23+
lower_bounds = [-0.4, 0.1, -0.9, 0.3, -0.1]
24+
upper_bounds = [1.0, 1.5, 0.5, 1.9, 1.1]
2125

26+
# Custom style for 4800x2700 canvas with theme support
2227
custom_style = Style(
23-
background="white",
24-
plot_background="white",
25-
foreground="#333333",
26-
foreground_strong="#333333",
27-
foreground_subtle="#999999",
28-
colors=(ref_color, ci_color, ci_color, ci_color, ci_color, ci_color, point_color),
29-
title_font_size=72,
30-
label_font_size=48,
31-
major_label_font_size=42,
32-
legend_font_size=42,
33-
value_font_size=36,
34-
stroke_width=6,
35-
guide_stroke_color="#e0e0e0",
28+
background=PAGE_BG,
29+
plot_background=PAGE_BG,
30+
foreground=INK,
31+
foreground_strong=INK,
32+
foreground_subtle=INK_SOFT,
33+
colors=(BRAND, "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9", "#F0E442"),
34+
title_font_size=28,
35+
label_font_size=22,
36+
major_label_font_size=18,
37+
legend_font_size=16,
38+
value_font_size=14,
39+
stroke_width=3,
40+
guide_stroke_color="rgba(26,26,23,0.10)" if THEME == "light" else "rgba(240,239,232,0.10)",
3641
)
3742

3843
# Create XY chart for point estimates with confidence intervals
3944
chart = pygal.XY(
4045
width=4800,
4146
height=2700,
4247
style=custom_style,
43-
title="point-basic · pygal · pyplots.ai",
44-
x_title="Effect Size",
48+
title="point-basic · pygal · anyplot.ai",
49+
x_title="Calibration Error (μV)",
4550
show_legend=True,
4651
legend_at_bottom=True,
4752
legend_at_bottom_columns=3,
48-
show_y_guides=True,
53+
show_y_guides=False,
4954
show_x_guides=True,
50-
dots_size=30,
55+
dots_size=24,
5156
stroke=False,
5257
margin_left=120,
5358
margin_right=120,
5459
margin_top=150,
5560
margin_bottom=180,
56-
xrange=(-1.5, 4.5),
61+
xrange=(-1.5, 2.5),
5762
range=(0, 6),
5863
)
5964

6065
# Map categories to y-values (numeric) - reversed for top-to-bottom display
6166
y_positions = list(range(len(categories), 0, -1))
6267

63-
# Add reference line at zero first (so it appears behind other elements)
68+
# Add reference line at zero (null hypothesis)
6469
ref_line = [(0, 0.3), (0, 5.7)]
65-
chart.add("Reference (x=0)", ref_line, stroke=True, show_dots=False, stroke_width=3)
70+
chart.add("Reference (zero error)", ref_line, stroke=True, show_dots=False, stroke_width=4)
6671

67-
# Add each CI as a separate series (to avoid connecting lines)
72+
# Add each CI as a separate series with caps
6873
for i, (low, high, y) in enumerate(zip(lower_bounds, upper_bounds, y_positions, strict=True)):
69-
ci_data = [(low, y), (high, y)]
70-
# First CI gets label, others are hidden from legend using None
74+
# CI line with caps (drawn as separate segments)
75+
cap_height = 0.15
76+
ci_data = [
77+
(low, y - cap_height), # left cap bottom
78+
(low, y + cap_height), # left cap top
79+
(low, y), # start of CI line
80+
(high, y), # end of CI line
81+
(high, y - cap_height), # right cap bottom
82+
(high, y + cap_height), # right cap top
83+
]
7184
if i == 0:
72-
chart.add("95% CI", ci_data, stroke=True, show_dots=False, stroke_width=8)
85+
chart.add("95% CI", ci_data, stroke=True, show_dots=False, stroke_width=5)
7386
else:
74-
chart.add(None, ci_data, stroke=True, show_dots=False, stroke_width=8)
87+
chart.add(None, ci_data, stroke=True, show_dots=False, stroke_width=5)
7588

7689
# Add point estimates (on top of CI lines)
7790
point_data = [(est, y) for est, y in zip(estimates, y_positions, strict=True)]
78-
chart.add("Point Estimate", point_data, dots_size=32, stroke=False)
91+
chart.add("Point Estimate", point_data, dots_size=28, stroke=False)
7992

80-
# Custom y-axis labels with category names - use major labels
93+
# Custom y-axis labels with category names
8194
chart.y_labels_major = [5, 4, 3, 2, 1]
8295
chart.y_labels = [
83-
{"value": 5, "label": "Treatment A"},
84-
{"value": 4, "label": "Treatment B"},
85-
{"value": 3, "label": "Treatment C"},
86-
{"value": 2, "label": "Treatment D"},
87-
{"value": 1, "label": "Control"},
96+
{"value": 5, "label": "Sensor A"},
97+
{"value": 4, "label": "Sensor B"},
98+
{"value": 3, "label": "Sensor C"},
99+
{"value": 2, "label": "Sensor D"},
100+
{"value": 1, "label": "Sensor E"},
88101
]
89102

90-
# Save as PNG and HTML
91-
chart.render_to_png("plot.png")
92-
chart.render_to_file("plot.html")
103+
# Save as PNG and HTML with theme suffix
104+
chart.render_to_png(f"plot-{THEME}.png")
105+
with open(f"plot-{THEME}.html", "wb") as f:
106+
f.write(chart.render())

0 commit comments

Comments
 (0)