Skip to content

Commit 9f78c6a

Browse files
feat(altair): implement errorbar-basic (#9520)
## Implementation: `errorbar-basic` - python/altair Implements the **python/altair** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/python/altair.py` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/28473279872)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com>
1 parent bd81ff4 commit 9f78c6a

2 files changed

Lines changed: 142 additions & 105 deletions

File tree

Lines changed: 57 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" anyplot.ai
22
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
55
"""
66

77
import importlib
@@ -14,22 +14,23 @@
1414
alt = importlib.import_module("altair")
1515
np = importlib.import_module("numpy")
1616
pd = importlib.import_module("pandas")
17-
17+
PILImage = importlib.import_module("PIL.Image")
1818

1919
# Theme tokens
2020
THEME = os.getenv("ANYPLOT_THEME", "light")
2121
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
2222
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
2323
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
2424
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
2627

2728
# Data
2829
np.random.seed(42)
2930
categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"]
3031
y_values = [25.3, 38.7, 42.1, 35.8, 48.2, 31.5]
3132

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
3334
asymmetric_lower = [2.1, 3.5, 2.8, 6.5, 4.8, 2.5]
3435
asymmetric_upper = [2.1, 3.5, 2.8, 2.8, 2.2, 2.5]
3536

@@ -48,19 +49,29 @@
4849

4950
base = alt.Chart(df).encode(x=alt.X("category:N", title="Experimental Group", sort=categories))
5051

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)),
5359
)
5460

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)),
5764
)
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)),
6069
)
6170

62-
points = base.mark_circle(size=320, color=BRAND).encode(
71+
points = base.mark_circle().encode(
6372
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)),
6475
tooltip=[
6576
alt.Tooltip("category:N", title="Group"),
6677
alt.Tooltip("value:Q", title="Mean", format=".2f"),
@@ -69,28 +80,54 @@
6980
],
7081
)
7182

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+
7291
chart = (
73-
alt.layer(error_bars, caps_bottom, caps_top, points)
92+
alt.layer(error_bars, caps_bottom, caps_top, points, annotation)
7493
.properties(
75-
width=1600,
76-
height=900,
94+
width=620,
95+
height=320,
7796
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+
),
79100
)
80101
.configure_view(fill=PAGE_BG, stroke=None)
81102
.configure_axis(
82-
labelFontSize=18,
83-
titleFontSize=22,
103+
labelFontSize=11,
104+
titleFontSize=12,
84105
labelColor=INK_SOFT,
85106
titleColor=INK,
86107
domainColor=INK_SOFT,
108+
domainOpacity=0,
87109
tickColor=INK_SOFT,
88110
gridColor=INK,
89-
gridOpacity=0.10,
111+
gridOpacity=0.13,
90112
labelAngle=0,
91113
)
92114
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)
93115
)
94116

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+
96133
chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)