|
1 | | -""" pyplots.ai |
| 1 | +""" anyplot.ai |
2 | 2 | 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 |
5 | 5 | """ |
6 | 6 |
|
| 7 | +import os |
| 8 | + |
7 | 9 | import numpy as np |
8 | 10 | 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 |
10 | 12 | from bokeh.plotting import figure |
11 | 13 |
|
12 | 14 |
|
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 |
14 | 27 | np.random.seed(42) |
15 | 28 | categories = ["Control", "Treatment A", "Treatment B", "Treatment C", "Treatment D", "Treatment E"] |
16 | 29 | means = np.array([25.3, 38.7, 42.1, 35.8, 48.2, 31.5]) |
17 | 30 |
|
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 |
19 | 32 | lower_errors = np.array([2.1, 3.5, 2.8, 6.5, 4.8, 2.5]) |
20 | 33 | upper_errors = np.array([2.1, 3.5, 2.8, 2.8, 2.2, 2.5]) |
21 | 34 |
|
22 | | -# Calculate upper and lower bounds for whiskers |
23 | 35 | upper = means + upper_errors |
24 | 36 | lower = means - lower_errors |
| 37 | +colors = OKABE_ITO[: len(categories)] |
25 | 38 |
|
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 | +) |
28 | 42 |
|
29 | | -# Create figure with categorical x-axis |
| 43 | +# Plot |
30 | 44 | p = figure( |
31 | 45 | width=4800, |
32 | 46 | height=2700, |
33 | 47 | x_range=categories, |
34 | | - title="errorbar-basic · bokeh · pyplots.ai", |
| 48 | + title="errorbar-basic · bokeh · anyplot.ai", |
35 | 49 | x_axis_label="Experimental Group", |
36 | 50 | y_axis_label="Response Value (units)", |
| 51 | + toolbar_location=None, |
| 52 | + tools="", |
37 | 53 | ) |
38 | 54 |
|
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", |
49 | 86 | ) |
50 | | -p.add_layout(whisker) |
| 87 | +p.add_layout(focus_label) |
51 | 88 |
|
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 |
54 | 93 |
|
55 | | -# Styling for 4800x2700 px |
| 94 | +# Title |
| 95 | +p.title.text_color = INK |
56 | 96 | 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 |
65 | 125 | p.xgrid.grid_line_color = None |
66 | 126 |
|
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 |
70 | 133 |
|
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") |
74 | 137 | save(p) |
0 commit comments