Skip to content

Commit 6a0e89b

Browse files
feat(bokeh): implement box-horizontal (#2581)
## Implementation: `box-horizontal` - bokeh Implements the **bokeh** version of `box-horizontal`. **File:** `plots/box-horizontal/implementations/bokeh.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20593349206)* --------- Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 67f9917 commit 6a0e89b

2 files changed

Lines changed: 175 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
""" pyplots.ai
2+
box-horizontal: Horizontal Box Plot
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
5+
"""
6+
7+
import numpy as np
8+
from bokeh.io import export_png
9+
from bokeh.models import FactorRange
10+
from bokeh.plotting import figure
11+
12+
13+
# Data - Response times (ms) by service type
14+
np.random.seed(42)
15+
16+
categories = ["Cache Layer", "API Gateway", "Authentication", "Database Query", "File Storage"]
17+
18+
# Generate different distributions for each service (sorted by median)
19+
data = {
20+
"Cache Layer": np.random.normal(15, 5, 80),
21+
"API Gateway": np.random.normal(45, 12, 80),
22+
"Authentication": np.concatenate([np.random.normal(65, 15, 75), [130, 145, 150]]), # Outliers
23+
"Database Query": np.concatenate([np.random.normal(120, 30, 80), [220, 240]]), # Some slow queries
24+
"File Storage": np.random.normal(200, 50, 80),
25+
}
26+
27+
# Calculate box plot statistics for each category
28+
stats = {}
29+
outliers_x = []
30+
outliers_y = []
31+
32+
for cat in categories:
33+
values = data[cat]
34+
q1 = np.percentile(values, 25)
35+
q2 = np.percentile(values, 50)
36+
q3 = np.percentile(values, 75)
37+
iqr = q3 - q1
38+
upper_fence = q3 + 1.5 * iqr
39+
lower_fence = q1 - 1.5 * iqr
40+
41+
# Whiskers extend to furthest data point within fence
42+
mask = (values >= lower_fence) & (values <= upper_fence)
43+
upper = values[mask].max() if mask.any() else q3
44+
lower = values[mask].min() if mask.any() else q1
45+
46+
# Find outliers
47+
outlier_mask = (values < lower_fence) | (values > upper_fence)
48+
for o in values[outlier_mask]:
49+
outliers_x.append(o)
50+
outliers_y.append(cat)
51+
52+
stats[cat] = {"q1": q1, "q2": q2, "q3": q3, "upper": upper, "lower": lower}
53+
54+
# Create figure with categorical y-axis (horizontal orientation)
55+
p = figure(
56+
width=4800,
57+
height=2700,
58+
y_range=FactorRange(*categories),
59+
x_axis_label="Response Time (ms)",
60+
y_axis_label="Service Type",
61+
title="box-horizontal · bokeh · pyplots.ai",
62+
)
63+
64+
# Colors
65+
box_color = "#306998"
66+
median_color = "#FFD43B"
67+
whisker_color = "#444444"
68+
69+
# Box and whisker dimensions
70+
box_height = 0.6
71+
cap_height = 0.3
72+
73+
for cat in categories:
74+
s = stats[cat]
75+
76+
# Draw box (IQR)
77+
p.hbar(
78+
y=[cat],
79+
left=[s["q1"]],
80+
right=[s["q3"]],
81+
height=box_height,
82+
fill_color=box_color,
83+
fill_alpha=0.7,
84+
line_color="#1a3a5c",
85+
line_width=2,
86+
)
87+
88+
# Draw median line
89+
p.hbar(
90+
y=[cat],
91+
left=[s["q2"] - 1],
92+
right=[s["q2"] + 1],
93+
height=box_height,
94+
fill_color=median_color,
95+
line_color=median_color,
96+
line_width=0,
97+
)
98+
99+
# Draw whiskers (horizontal lines from box to whisker ends)
100+
p.hbar(y=[cat], left=[s["lower"]], right=[s["q1"]], height=0.02, fill_color=whisker_color, line_color=whisker_color)
101+
p.hbar(y=[cat], left=[s["q3"]], right=[s["upper"]], height=0.02, fill_color=whisker_color, line_color=whisker_color)
102+
103+
# Draw whisker caps (vertical lines at whisker ends)
104+
p.hbar(
105+
y=[cat],
106+
left=[s["lower"] - 0.5],
107+
right=[s["lower"] + 0.5],
108+
height=cap_height,
109+
fill_color=whisker_color,
110+
line_color=whisker_color,
111+
)
112+
p.hbar(
113+
y=[cat],
114+
left=[s["upper"] - 0.5],
115+
right=[s["upper"] + 0.5],
116+
height=cap_height,
117+
fill_color=whisker_color,
118+
line_color=whisker_color,
119+
)
120+
121+
# Draw outliers
122+
if outliers_x:
123+
p.scatter(
124+
x=outliers_x, y=outliers_y, size=15, fill_color="white", line_color=box_color, line_width=2, marker="circle"
125+
)
126+
127+
# Style settings
128+
p.title.text_font_size = "28pt"
129+
p.xaxis.axis_label_text_font_size = "22pt"
130+
p.yaxis.axis_label_text_font_size = "22pt"
131+
p.xaxis.major_label_text_font_size = "18pt"
132+
p.yaxis.major_label_text_font_size = "18pt"
133+
134+
# Grid styling
135+
p.xgrid.grid_line_alpha = 0.3
136+
p.xgrid.grid_line_dash = [6, 4]
137+
p.ygrid.grid_line_alpha = 0.3
138+
p.ygrid.grid_line_dash = [6, 4]
139+
140+
# Background
141+
p.background_fill_color = "#fafafa"
142+
p.border_fill_color = "white"
143+
144+
# Axis styling
145+
p.xaxis.axis_line_color = "#666666"
146+
p.yaxis.axis_line_color = "#666666"
147+
148+
# Save
149+
export_png(p, filename="plot.png")
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
library: bokeh
2+
specification_id: box-horizontal
3+
created: '2025-12-30T09:32:28Z'
4+
updated: '2025-12-30T09:43:47Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20593349206
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/box-horizontal/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/box-horizontal/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/box-horizontal/bokeh/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent horizontal box plot implementation with all statistical elements (median,
17+
quartiles, whiskers, outliers)
18+
- Categories sorted by median value for easy comparison as suggested in spec
19+
- Realistic service response time scenario with appropriate variation across categories
20+
- Clean color scheme with good contrast (blue boxes, yellow median lines)
21+
- Proper use of bokeh hbar for horizontal orientation
22+
weaknesses:
23+
- Code does not use ColumnDataSource which is the idiomatic bokeh way to handle
24+
data
25+
- Median line implementation uses a narrow hbar with fixed width which may not scale
26+
well with different data ranges

0 commit comments

Comments
 (0)