|
1 | 1 | """ anyplot.ai |
2 | 2 | errorbar-basic: Basic Error Bar Plot |
3 | | -Library: altair 6.1.0 | Python 3.14.4 |
4 | | -Quality: 88/100 | Updated: 2026-04-25 |
| 3 | +Library: altair 6.2.2 | Python 3.13.14 |
| 4 | +Quality: 94/100 | Updated: 2026-06-30 |
5 | 5 | """ |
6 | 6 |
|
7 | 7 | import importlib |
|
14 | 14 | alt = importlib.import_module("altair") |
15 | 15 | np = importlib.import_module("numpy") |
16 | 16 | pd = importlib.import_module("pandas") |
17 | | - |
| 17 | +PILImage = importlib.import_module("PIL.Image") |
18 | 18 |
|
19 | 19 | # Theme tokens |
20 | 20 | THEME = os.getenv("ANYPLOT_THEME", "light") |
21 | 21 | PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" |
22 | 22 | ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" |
23 | 23 | INK = "#1A1A17" if THEME == "light" else "#F0EFE8" |
24 | 24 | INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" |
25 | | -BRAND = "#009E73" # Okabe-Ito position 1 |
| 25 | +BRAND = "#009E73" # Imprint palette position 1 |
| 26 | +ACCENT = "#BD8233" # Imprint ochre — highlights peak-response group |
26 | 27 |
|
27 | 28 | # Data |
28 | 29 | np.random.seed(42) |
29 | 30 | categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"] |
30 | 31 | y_values = [25.3, 38.7, 42.1, 35.8, 48.2, 31.5] |
31 | 32 |
|
32 | | -# Asymmetric errors: Treatment C and D show notably different lower/upper bounds |
| 33 | +# Asymmetric errors: Treatment C shows wide lower bound; Treatment D peaks highest |
33 | 34 | asymmetric_lower = [2.1, 3.5, 2.8, 6.5, 4.8, 2.5] |
34 | 35 | asymmetric_upper = [2.1, 3.5, 2.8, 2.8, 2.2, 2.5] |
35 | 36 |
|
|
48 | 49 |
|
49 | 50 | base = alt.Chart(df).encode(x=alt.X("category:N", title="Experimental Group", sort=categories)) |
50 | 51 |
|
51 | | -error_bars = base.mark_rule(strokeWidth=3, color=BRAND).encode( |
52 | | - y=alt.Y("error_lower:Q", title=y_title, scale=y_scale), y2="error_upper:Q" |
| 52 | +# Condition-based encoding: Treatment D (peak response) accented in ochre via alt.condition() |
| 53 | +highlight = alt.datum.category == "Treatment D" |
| 54 | + |
| 55 | +error_bars = base.mark_rule(strokeWidth=3).encode( |
| 56 | + y=alt.Y("error_lower:Q", title=y_title, scale=y_scale), |
| 57 | + y2="error_upper:Q", |
| 58 | + color=alt.condition(highlight, alt.value(ACCENT), alt.value(BRAND)), |
53 | 59 | ) |
54 | 60 |
|
55 | | -caps_top = base.mark_tick(thickness=3, size=22, color=BRAND).encode( |
56 | | - y=alt.Y("error_upper:Q", title=y_title, scale=y_scale) |
| 61 | +caps_top = base.mark_tick(thickness=3, size=22).encode( |
| 62 | + y=alt.Y("error_upper:Q", title=y_title, scale=y_scale), |
| 63 | + color=alt.condition(highlight, alt.value(ACCENT), alt.value(BRAND)), |
57 | 64 | ) |
58 | | -caps_bottom = base.mark_tick(thickness=3, size=22, color=BRAND).encode( |
59 | | - y=alt.Y("error_lower:Q", title=y_title, scale=y_scale) |
| 65 | + |
| 66 | +caps_bottom = base.mark_tick(thickness=3, size=22).encode( |
| 67 | + y=alt.Y("error_lower:Q", title=y_title, scale=y_scale), |
| 68 | + color=alt.condition(highlight, alt.value(ACCENT), alt.value(BRAND)), |
60 | 69 | ) |
61 | 70 |
|
62 | | -points = base.mark_circle(size=320, color=BRAND).encode( |
| 71 | +points = base.mark_circle().encode( |
63 | 72 | y=alt.Y("value:Q", title=y_title, scale=y_scale), |
| 73 | + color=alt.condition(highlight, alt.value(ACCENT), alt.value(BRAND)), |
| 74 | + size=alt.condition(highlight, alt.value(480), alt.value(280)), |
64 | 75 | tooltip=[ |
65 | 76 | alt.Tooltip("category:N", title="Group"), |
66 | 77 | alt.Tooltip("value:Q", title="Mean", format=".2f"), |
|
69 | 80 | ], |
70 | 81 | ) |
71 | 82 |
|
| 83 | +# Annotation surfacing the "Peak response" label for Treatment D |
| 84 | +peak_df = df[df["category"] == "Treatment D"] |
| 85 | +annotation = ( |
| 86 | + alt.Chart(peak_df) |
| 87 | + .mark_text(dy=-28, color=ACCENT, fontSize=10, fontStyle="italic", text="Peak response") |
| 88 | + .encode(x=alt.X("category:N", sort=categories), y=alt.Y("error_upper:Q", scale=y_scale)) |
| 89 | +) |
| 90 | + |
72 | 91 | chart = ( |
73 | | - alt.layer(error_bars, caps_bottom, caps_top, points) |
| 92 | + alt.layer(error_bars, caps_bottom, caps_top, points, annotation) |
74 | 93 | .properties( |
75 | | - width=1600, |
76 | | - height=900, |
| 94 | + width=620, |
| 95 | + height=320, |
77 | 96 | background=PAGE_BG, |
78 | | - title=alt.Title("errorbar-basic · altair · anyplot.ai", fontSize=28, color=INK, anchor="start", offset=20), |
| 97 | + title=alt.Title( |
| 98 | + "errorbar-basic · python · altair · anyplot.ai", fontSize=16, color=INK, anchor="start", offset=20 |
| 99 | + ), |
79 | 100 | ) |
80 | 101 | .configure_view(fill=PAGE_BG, stroke=None) |
81 | 102 | .configure_axis( |
82 | | - labelFontSize=18, |
83 | | - titleFontSize=22, |
| 103 | + labelFontSize=11, |
| 104 | + titleFontSize=12, |
84 | 105 | labelColor=INK_SOFT, |
85 | 106 | titleColor=INK, |
86 | 107 | domainColor=INK_SOFT, |
| 108 | + domainOpacity=0, |
87 | 109 | tickColor=INK_SOFT, |
88 | 110 | gridColor=INK, |
89 | | - gridOpacity=0.10, |
| 111 | + gridOpacity=0.13, |
90 | 112 | labelAngle=0, |
91 | 113 | ) |
92 | 114 | .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK) |
93 | 115 | ) |
94 | 116 |
|
95 | | -chart.save(f"plot-{THEME}.png", scale_factor=3.0) |
| 117 | +chart.save(f"plot-{THEME}.png", scale_factor=4.0) |
| 118 | + |
| 119 | +# Pad to exact 3200×1800 target — only expand, never crop (AR-09 guard) |
| 120 | +TW, TH = 3200, 1800 |
| 121 | +_img = PILImage.open(f"plot-{THEME}.png").convert("RGB") |
| 122 | +_w, _h = _img.size |
| 123 | +if _w > TW or _h > TH: |
| 124 | + raise SystemExit( |
| 125 | + f"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. " |
| 126 | + f"Shrink chart .properties(width=, height=) values and re-render." |
| 127 | + ) |
| 128 | +if _w < TW or _h < TH: |
| 129 | + _canvas = PILImage.new("RGB", (TW, TH), PAGE_BG) |
| 130 | + _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2)) |
| 131 | + _canvas.save(f"plot-{THEME}.png") |
| 132 | + |
96 | 133 | chart.save(f"plot-{THEME}.html") |
0 commit comments