|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import pygal |
8 | 10 | from pygal.style import Style |
9 | 11 |
|
10 | 12 |
|
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 |
16 | 19 |
|
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] |
21 | 25 |
|
| 26 | +# Custom style for 4800x2700 canvas with theme support |
22 | 27 | 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)", |
36 | 41 | ) |
37 | 42 |
|
38 | 43 | # Create XY chart for point estimates with confidence intervals |
39 | 44 | chart = pygal.XY( |
40 | 45 | width=4800, |
41 | 46 | height=2700, |
42 | 47 | 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)", |
45 | 50 | show_legend=True, |
46 | 51 | legend_at_bottom=True, |
47 | 52 | legend_at_bottom_columns=3, |
48 | | - show_y_guides=True, |
| 53 | + show_y_guides=False, |
49 | 54 | show_x_guides=True, |
50 | | - dots_size=30, |
| 55 | + dots_size=24, |
51 | 56 | stroke=False, |
52 | 57 | margin_left=120, |
53 | 58 | margin_right=120, |
54 | 59 | margin_top=150, |
55 | 60 | margin_bottom=180, |
56 | | - xrange=(-1.5, 4.5), |
| 61 | + xrange=(-1.5, 2.5), |
57 | 62 | range=(0, 6), |
58 | 63 | ) |
59 | 64 |
|
60 | 65 | # Map categories to y-values (numeric) - reversed for top-to-bottom display |
61 | 66 | y_positions = list(range(len(categories), 0, -1)) |
62 | 67 |
|
63 | | -# Add reference line at zero first (so it appears behind other elements) |
| 68 | +# Add reference line at zero (null hypothesis) |
64 | 69 | 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) |
66 | 71 |
|
67 | | -# Add each CI as a separate series (to avoid connecting lines) |
| 72 | +# Add each CI as a separate series with caps |
68 | 73 | 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 | + ] |
71 | 84 | 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) |
73 | 86 | 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) |
75 | 88 |
|
76 | 89 | # Add point estimates (on top of CI lines) |
77 | 90 | 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) |
79 | 92 |
|
80 | | -# Custom y-axis labels with category names - use major labels |
| 93 | +# Custom y-axis labels with category names |
81 | 94 | chart.y_labels_major = [5, 4, 3, 2, 1] |
82 | 95 | 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"}, |
88 | 101 | ] |
89 | 102 |
|
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