Skip to content

Commit a87a80a

Browse files
feat(bokeh): implement errorbar-basic (#5386)
## Implementation: `errorbar-basic` - python/bokeh Implements the **python/bokeh** version of `errorbar-basic`. **File:** `plots/errorbar-basic/implementations/python/bokeh.py` **Parent Issue:** #973 --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/anyplot/actions/runs/24927158256)* --------- 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 2d9033f commit a87a80a

2 files changed

Lines changed: 267 additions & 165 deletions

File tree

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,74 +1,137 @@
1-
""" pyplots.ai
1+
""" anyplot.ai
22
errorbar-basic: Basic Error Bar Plot
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: bokeh 3.9.0 | Python 3.14.4
4+
Quality: 88/100 | Updated: 2026-04-25
55
"""
66

7+
import os
8+
79
import numpy as np
810
from bokeh.io import export_png, output_file, save
9-
from bokeh.models import ColumnDataSource, TeeHead, Whisker
11+
from bokeh.models import ColumnDataSource, Label, TeeHead, Whisker
1012
from bokeh.plotting import figure
1113

1214

13-
# Data - experimental measurements with associated uncertainties
15+
# Theme tokens
16+
THEME = os.getenv("ANYPLOT_THEME", "light")
17+
PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17"
18+
ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420"
19+
INK = "#1A1A17" if THEME == "light" else "#F0EFE8"
20+
INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0"
21+
INK_MUTED = "#6B6A63" if THEME == "light" else "#A8A79F"
22+
23+
# Okabe-Ito categorical palette (positions 1-6)
24+
OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9"]
25+
26+
# Data — experimental measurements with associated uncertainties
1427
np.random.seed(42)
1528
categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"]
1629
means = np.array([25.3, 38.7, 42.1, 35.8, 48.2, 31.5])
1730

18-
# Asymmetric errors to demonstrate feature (some treatments have larger lower uncertainty)
31+
# Asymmetric errors — Treatment C has highest variability, Treatment D has highest mean
1932
lower_errors = np.array([2.1, 3.5, 2.8, 6.5, 4.8, 2.5])
2033
upper_errors = np.array([2.1, 3.5, 2.8, 2.8, 2.2, 2.5])
2134

22-
# Calculate upper and lower bounds for whiskers
2335
upper = means + upper_errors
2436
lower = means - lower_errors
37+
colors = OKABE_ITO[: len(categories)]
2538

26-
# Create ColumnDataSource
27-
source = ColumnDataSource(data={"categories": categories, "means": means, "upper": upper, "lower": lower})
39+
source = ColumnDataSource(
40+
data={"categories": categories, "means": means, "upper": upper, "lower": lower, "colors": colors}
41+
)
2842

29-
# Create figure with categorical x-axis
43+
# Plot
3044
p = figure(
3145
width=4800,
3246
height=2700,
3347
x_range=categories,
34-
title="errorbar-basic · bokeh · pyplots.ai",
48+
title="errorbar-basic · bokeh · anyplot.ai",
3549
x_axis_label="Experimental Group",
3650
y_axis_label="Response Value (units)",
51+
toolbar_location=None,
52+
tools="",
3753
)
3854

39-
# Add error bars using Whisker with TeeHead caps
40-
whisker = Whisker(
41-
base="categories",
42-
upper="upper",
43-
lower="lower",
44-
source=source,
45-
line_color="#306998",
46-
line_width=4,
47-
upper_head=TeeHead(size=20, line_color="#306998", line_width=4),
48-
lower_head=TeeHead(size=20, line_color="#306998", line_width=4),
55+
# Error bars (Whisker with TeeHead caps) — one whisker per group so each can take its own color
56+
for cat, up, lo, col in zip(categories, upper, lower, colors, strict=True):
57+
grp_source = ColumnDataSource(data={"x": [cat], "upper": [up], "lower": [lo]})
58+
whisker = Whisker(
59+
base="x",
60+
upper="upper",
61+
lower="lower",
62+
source=grp_source,
63+
line_color=col,
64+
line_width=5,
65+
upper_head=TeeHead(size=40, line_color=col, line_width=5),
66+
lower_head=TeeHead(size=40, line_color=col, line_width=5),
67+
)
68+
p.add_layout(whisker)
69+
70+
# Mean markers — colored per group
71+
p.scatter(x="categories", y="means", source=source, size=28, color="colors", line_color=PAGE_BG, line_width=2)
72+
73+
# Annotation: highlight highest variability (Treatment C, index 3)
74+
focus_idx = int(np.argmax(lower_errors + upper_errors))
75+
focus_label = Label(
76+
x=focus_idx,
77+
y=float(lower[focus_idx]),
78+
x_units="data",
79+
y_units="data",
80+
x_offset=24,
81+
y_offset=-12,
82+
text="highest variability",
83+
text_color=INK_MUTED,
84+
text_font_size="20pt",
85+
text_font_style="italic",
4986
)
50-
p.add_layout(whisker)
87+
p.add_layout(focus_label)
5188

52-
# Add scatter points for the mean values
53-
p.scatter(x="categories", y="means", source=source, size=20, color="#306998", alpha=0.9)
89+
# Style — background and outline
90+
p.background_fill_color = PAGE_BG
91+
p.border_fill_color = PAGE_BG
92+
p.outline_line_color = None
5493

55-
# Styling for 4800x2700 px
94+
# Title
95+
p.title.text_color = INK
5696
p.title.text_font_size = "36pt"
57-
p.xaxis.axis_label_text_font_size = "28pt"
58-
p.yaxis.axis_label_text_font_size = "28pt"
59-
p.xaxis.major_label_text_font_size = "22pt"
60-
p.yaxis.major_label_text_font_size = "22pt"
61-
62-
# Grid styling
63-
p.ygrid.grid_line_alpha = 0.3
64-
p.ygrid.grid_line_dash = "dashed"
97+
p.title.text_font_style = "normal"
98+
p.title.align = "left"
99+
100+
# Axis labels
101+
p.xaxis.axis_label_text_color = INK
102+
p.yaxis.axis_label_text_color = INK
103+
p.xaxis.axis_label_text_font_size = "32pt"
104+
p.yaxis.axis_label_text_font_size = "32pt"
105+
p.xaxis.axis_label_text_font_style = "normal"
106+
p.yaxis.axis_label_text_font_style = "normal"
107+
108+
# Tick labels
109+
p.xaxis.major_label_text_color = INK_SOFT
110+
p.yaxis.major_label_text_color = INK_SOFT
111+
p.xaxis.major_label_text_font_size = "24pt"
112+
p.yaxis.major_label_text_font_size = "24pt"
113+
114+
# Axis lines and ticks
115+
p.xaxis.axis_line_color = INK_SOFT
116+
p.yaxis.axis_line_color = INK_SOFT
117+
p.xaxis.major_tick_line_color = INK_SOFT
118+
p.yaxis.major_tick_line_color = INK_SOFT
119+
p.xaxis.minor_tick_line_color = None
120+
p.yaxis.minor_tick_line_color = None
121+
122+
# Subtle y-grid only
123+
p.ygrid.grid_line_color = INK
124+
p.ygrid.grid_line_alpha = 0.10
65125
p.xgrid.grid_line_color = None
66126

67-
# Set y-axis range with padding
68-
p.y_range.start = 0
69-
p.y_range.end = max(upper) * 1.15
127+
# Y-range trimmed to data — eliminates dead space below
128+
y_min = float(min(lower))
129+
y_max = float(max(upper))
130+
y_pad = (y_max - y_min) * 0.15
131+
p.y_range.start = max(0.0, y_min - y_pad)
132+
p.y_range.end = y_max + y_pad
70133

71-
# Save outputs
72-
export_png(p, filename="plot.png")
73-
output_file("plot.html", title="errorbar-basic · bokeh · pyplots.ai")
134+
# Save
135+
export_png(p, filename=f"plot-{THEME}.png")
136+
output_file(f"plot-{THEME}.html", title="errorbar-basic · bokeh · anyplot.ai")
74137
save(p)

0 commit comments

Comments
 (0)