Skip to content

Commit fdfc689

Browse files
feat(bokeh): implement forest-basic (#2401)
## Implementation: `forest-basic` - bokeh Implements the **bokeh** version of `forest-basic`. **File:** `plots/forest-basic/implementations/bokeh.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20543282172)* --------- 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 a833d38 commit fdfc689

2 files changed

Lines changed: 201 additions & 0 deletions

File tree

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
""" pyplots.ai
2+
forest-basic: Meta-Analysis Forest Plot
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-27
5+
"""
6+
7+
import numpy as np
8+
from bokeh.io import export_png, save
9+
from bokeh.models import ColumnDataSource, Label, Span
10+
from bokeh.plotting import figure
11+
12+
13+
# Data - Meta-analysis of blood pressure reduction trials
14+
np.random.seed(42)
15+
16+
studies = [
17+
"Smith et al. 2018",
18+
"Johnson et al. 2019",
19+
"Williams et al. 2019",
20+
"Brown et al. 2020",
21+
"Davis et al. 2020",
22+
"Miller et al. 2021",
23+
"Wilson et al. 2021",
24+
"Moore et al. 2022",
25+
"Taylor et al. 2022",
26+
"Anderson et al. 2023",
27+
"Thomas et al. 2023",
28+
"Pooled Estimate",
29+
]
30+
31+
# Effect sizes (mean difference in mmHg) with confidence intervals
32+
effect_sizes = np.array([-3.2, -5.1, -2.8, -4.5, -6.2, -3.9, -4.1, -5.8, -3.5, -4.7, -2.9, -4.2])
33+
ci_lower = np.array([-5.8, -8.2, -5.1, -7.3, -9.1, -6.5, -6.8, -8.9, -6.2, -7.4, -5.6, -5.1])
34+
ci_upper = np.array([-0.6, -2.0, -0.5, -1.7, -3.3, -1.3, -1.4, -2.7, -0.8, -2.0, -0.2, -3.3])
35+
36+
# Weights based on sample size (larger = more precise)
37+
weights = np.array([8, 12, 6, 15, 10, 9, 11, 14, 7, 13, 5, 20])
38+
39+
# Y positions (reversed so first study is at top)
40+
y_positions = list(range(len(studies) - 1, -1, -1))
41+
42+
# Create figure
43+
p = figure(
44+
width=4800,
45+
height=2700,
46+
title="forest-basic · bokeh · pyplots.ai",
47+
x_axis_label="Mean Difference in Blood Pressure (mmHg)",
48+
y_range=(-0.5, len(studies) - 0.5),
49+
x_range=(-12, 4),
50+
tools="",
51+
toolbar_location=None,
52+
)
53+
54+
# Add vertical reference line at null effect (0)
55+
null_line = Span(location=0, dimension="height", line_color="#666666", line_width=3, line_dash="dashed")
56+
p.add_layout(null_line)
57+
58+
# Prepare data for individual studies (excluding pooled estimate)
59+
study_source = ColumnDataSource(
60+
data={
61+
"study": studies[:-1],
62+
"effect": effect_sizes[:-1],
63+
"ci_lower": ci_lower[:-1],
64+
"ci_upper": ci_upper[:-1],
65+
"y": y_positions[:-1],
66+
"size": (weights[:-1] / weights[:-1].max() * 25 + 10).tolist(),
67+
}
68+
)
69+
70+
# Draw confidence interval lines (whiskers)
71+
for i in range(len(studies) - 1):
72+
p.line(x=[ci_lower[i], ci_upper[i]], y=[y_positions[i], y_positions[i]], line_width=4, line_color="#306998")
73+
# Add CI end caps
74+
p.line(
75+
x=[ci_lower[i], ci_lower[i]],
76+
y=[y_positions[i] - 0.15, y_positions[i] + 0.15],
77+
line_width=3,
78+
line_color="#306998",
79+
)
80+
p.line(
81+
x=[ci_upper[i], ci_upper[i]],
82+
y=[y_positions[i] - 0.15, y_positions[i] + 0.15],
83+
line_width=3,
84+
line_color="#306998",
85+
)
86+
87+
# Plot effect size points (size proportional to weight)
88+
p.scatter(x="effect", y="y", source=study_source, size="size", color="#306998", alpha=0.9)
89+
90+
# Add study labels on the left
91+
for i, study in enumerate(studies[:-1]):
92+
label = Label(
93+
x=-11.5,
94+
y=y_positions[i],
95+
text=study,
96+
text_font_size="18pt",
97+
text_align="left",
98+
text_baseline="middle",
99+
text_color="#333333",
100+
)
101+
p.add_layout(label)
102+
103+
# Draw pooled estimate as a diamond
104+
pooled_y = y_positions[-1]
105+
pooled_effect = effect_sizes[-1]
106+
pooled_lower = ci_lower[-1]
107+
pooled_upper = ci_upper[-1]
108+
109+
# Diamond vertices
110+
diamond_x = [pooled_lower, pooled_effect, pooled_upper, pooled_effect, pooled_lower]
111+
diamond_y = [pooled_y, pooled_y + 0.25, pooled_y, pooled_y - 0.25, pooled_y]
112+
113+
p.patch(x=diamond_x, y=diamond_y, fill_color="#FFD43B", line_color="#306998", line_width=3, alpha=0.9)
114+
115+
# Add pooled estimate label
116+
pooled_label = Label(
117+
x=-11.5,
118+
y=pooled_y,
119+
text="Pooled Estimate",
120+
text_font_size="18pt",
121+
text_font_style="bold",
122+
text_align="left",
123+
text_baseline="middle",
124+
text_color="#333333",
125+
)
126+
p.add_layout(pooled_label)
127+
128+
# Add "Favors Treatment" and "Favors Control" labels
129+
favors_treatment = Label(
130+
x=-6,
131+
y=-0.35,
132+
text="← Favors Treatment",
133+
text_font_size="16pt",
134+
text_align="center",
135+
text_baseline="top",
136+
text_color="#666666",
137+
)
138+
p.add_layout(favors_treatment)
139+
140+
favors_control = Label(
141+
x=2,
142+
y=-0.35,
143+
text="Favors Control →",
144+
text_font_size="16pt",
145+
text_align="center",
146+
text_baseline="top",
147+
text_color="#666666",
148+
)
149+
p.add_layout(favors_control)
150+
151+
# Styling
152+
p.title.text_font_size = "28pt"
153+
p.title.text_color = "#333333"
154+
p.xaxis.axis_label_text_font_size = "22pt"
155+
p.yaxis.axis_label_text_font_size = "22pt"
156+
p.xaxis.major_label_text_font_size = "18pt"
157+
p.yaxis.major_label_text_font_size = "18pt"
158+
159+
# Hide y-axis ticks and labels (studies are labeled manually)
160+
p.yaxis.visible = False
161+
162+
# Grid styling
163+
p.xgrid.grid_line_color = "#cccccc"
164+
p.xgrid.grid_line_alpha = 0.3
165+
p.xgrid.grid_line_dash = "dashed"
166+
p.ygrid.grid_line_color = None
167+
168+
# Background
169+
p.background_fill_color = "#ffffff"
170+
p.border_fill_color = "#ffffff"
171+
172+
# Save as PNG and HTML
173+
export_png(p, filename="plot.png")
174+
save(p, filename="plot.html", title="forest-basic · bokeh · pyplots.ai")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: bokeh
2+
specification_id: forest-basic
3+
created: '2025-12-27T19:21:59Z'
4+
updated: '2025-12-27T19:30:02Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20543282172
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/forest-basic/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/forest-basic/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/forest-basic/bokeh/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent implementation of all forest plot elements (diamond, whiskers, reference
17+
line, weighted markers)
18+
- Realistic medical meta-analysis data with appropriate study naming conventions
19+
- Clean KISS code structure with proper Bokeh idioms (ColumnDataSource, Span, Label)
20+
- Good visual hierarchy distinguishing individual studies from pooled estimate
21+
- Proper use of Bokeh patch() for diamond shape
22+
weaknesses:
23+
- Right side of canvas has excessive whitespace (x_range extends to 4 but data only
24+
goes to ~-0.2)
25+
- All studies favor treatment - missing a study crossing the null line would better
26+
demonstrate forest plot capabilities
27+
- Could add HoverTool for interactivity (a Bokeh strength)

0 commit comments

Comments
 (0)