Skip to content

Commit 2d9033f

Browse files
feat(altair): implement errorbar-basic (#5385)
## 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/24927121489)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 52e915f commit 2d9033f

2 files changed

Lines changed: 229 additions & 168 deletions

File tree

Lines changed: 66 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,38 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
errorbar-basic: Basic Error Bar Plot
3-
Library: altair 6.0.0 | Python 3.13.11
4-
Quality: 92/100 | Created: 2025-12-23
3+
Library: altair 6.1.0 | Python 3.14.4
4+
Quality: 88/100 | Updated: 2026-04-25
55
"""
66

7-
import altair as alt
8-
import numpy as np
9-
import pandas as pd
7+
import importlib
8+
import os
9+
import sys
1010

1111

12-
# Data - experimental measurements with associated uncertainties
12+
# Drop script directory from sys.path so the `altair` package resolves, not this file
13+
sys.path[:] = [p for p in sys.path if os.path.abspath(p or ".") != os.path.dirname(os.path.abspath(__file__))]
14+
alt = importlib.import_module("altair")
15+
np = importlib.import_module("numpy")
16+
pd = importlib.import_module("pandas")
17+
18+
19+
# Theme tokens
20+
THEME = os.getenv("ANYPLOT_THEME", "light")
21+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
22+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
23+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
24+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
25+
BRAND = "#009E73" # Okabe-Ito position 1
26+
27+
# Data
1328
np.random.seed(42)
1429
categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"]
1530
y_values = [25.3, 38.7, 42.1, 35.8, 48.2, 31.5]
1631

17-
# Asymmetric errors: Treatment C and D have notably different lower/upper bounds
32+
# Asymmetric errors: Treatment C and D show notably different lower/upper bounds
1833
asymmetric_lower = [2.1, 3.5, 2.8, 6.5, 4.8, 2.5]
1934
asymmetric_upper = [2.1, 3.5, 2.8, 2.8, 2.2, 2.5]
2035

21-
# Create DataFrame with error bounds
2236
df = pd.DataFrame(
2337
{
2438
"category": categories,
@@ -28,43 +42,55 @@
2842
}
2943
)
3044

31-
# Base chart with shared properties
32-
base = alt.Chart(df).encode(
33-
x=alt.X(
34-
"category:N",
35-
title="Experimental Group",
36-
sort=categories,
37-
axis=alt.Axis(labelFontSize=18, titleFontSize=22, labelAngle=0),
38-
)
39-
)
45+
# Plot
46+
y_scale = alt.Scale(domain=[15, 55], nice=False)
47+
y_title = "Response Value (units)"
4048

41-
# Points for the central values
42-
points = base.mark_circle(size=300, color="#306998").encode(
43-
y=alt.Y(
44-
"value:Q",
45-
title="Response Value (units)",
46-
scale=alt.Scale(domain=[0, 55]),
47-
axis=alt.Axis(labelFontSize=18, titleFontSize=22, grid=True, gridOpacity=0.3, gridDash=[4, 4]),
48-
)
49-
)
49+
base = alt.Chart(df).encode(x=alt.X("category:N", title="Experimental Group", sort=categories))
5050

51-
# Error bars using mark_rule with y and y2
52-
error_bars = base.mark_rule(strokeWidth=3, color="#306998").encode(y=alt.Y("error_lower:Q"), y2="error_upper:Q")
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"
53+
)
5354

54-
# Error bar caps (top)
55-
caps_top = base.mark_tick(thickness=3, size=20, color="#306998").encode(y="error_upper:Q")
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)
57+
)
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)
60+
)
5661

57-
# Error bar caps (bottom)
58-
caps_bottom = base.mark_tick(thickness=3, size=20, color="#306998").encode(y="error_lower:Q")
62+
points = base.mark_circle(size=320, color=BRAND).encode(
63+
y=alt.Y("value:Q", title=y_title, scale=y_scale),
64+
tooltip=[
65+
alt.Tooltip("category:N", title="Group"),
66+
alt.Tooltip("value:Q", title="Mean", format=".2f"),
67+
alt.Tooltip("error_lower:Q", title="Lower bound", format=".2f"),
68+
alt.Tooltip("error_upper:Q", title="Upper bound", format=".2f"),
69+
],
70+
)
5971

60-
# Layer all elements
6172
chart = (
6273
alt.layer(error_bars, caps_bottom, caps_top, points)
63-
.properties(width=1600, height=900, title=alt.Title("errorbar-basic · altair · pyplots.ai", fontSize=28))
64-
.configure_axis(labelFontSize=18, titleFontSize=22)
65-
.configure_view(strokeWidth=0)
74+
.properties(
75+
width=1600,
76+
height=900,
77+
background=PAGE_BG,
78+
title=alt.Title("errorbar-basic · altair · anyplot.ai", fontSize=28, color=INK, anchor="start", offset=20),
79+
)
80+
.configure_view(fill=PAGE_BG, stroke=None)
81+
.configure_axis(
82+
labelFontSize=18,
83+
titleFontSize=22,
84+
labelColor=INK_SOFT,
85+
titleColor=INK,
86+
domainColor=INK_SOFT,
87+
tickColor=INK_SOFT,
88+
gridColor=INK,
89+
gridOpacity=0.10,
90+
labelAngle=0,
91+
)
92+
.configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)
6693
)
6794

68-
# Save
69-
chart.save("plot.png", scale_factor=3.0)
70-
chart.save("plot.html")
95+
chart.save(f"plot-{THEME}.png", scale_factor=3.0)
96+
chart.save(f"plot-{THEME}.html")

0 commit comments

Comments
 (0)