Skip to content

Commit a46189c

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

2 files changed

Lines changed: 50 additions & 36 deletions

File tree

plots/donut-basic/implementations/bokeh.py

Lines changed: 33 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
""" pyplots.ai
22
donut-basic: Basic Donut Chart
33
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-14
4+
Quality: 91/100 | Created: 2025-12-23
55
"""
66

77
from math import pi
@@ -13,19 +13,20 @@
1313
from bokeh.transform import cumsum
1414

1515

16-
# Data
16+
# Data - Portfolio allocation by asset class
1717
categories = ["Technology", "Healthcare", "Finance", "Energy", "Retail"]
1818
values = [35, 25, 20, 12, 8]
19+
total = sum(values)
1920

2021
# Calculate angles for donut segments
2122
data = {
2223
"category": categories,
2324
"value": values,
24-
"angle": [v / sum(values) * 2 * pi for v in values],
25-
"percentage": [f"{v / sum(values) * 100:.1f}%" for v in values],
25+
"angle": [v / total * 2 * pi for v in values],
26+
"percentage": [f"{v / total * 100:.1f}%" for v in values],
2627
}
2728

28-
# Cumulative angles for start/end positions
29+
# Cumulative angles for label positioning
2930
cumulative = np.cumsum([0] + data["angle"][:-1]).tolist()
3031
data["start_angle"] = cumulative
3132
data["end_angle"] = np.cumsum(data["angle"]).tolist()
@@ -36,63 +37,66 @@
3637

3738
source = ColumnDataSource(data=data)
3839

39-
# Create figure
40+
# Create figure (square format for circular chart)
4041
p = figure(
41-
width=4800,
42-
height=2700,
42+
width=3600,
43+
height=3600,
4344
title="donut-basic · bokeh · pyplots.ai",
4445
toolbar_location=None,
4546
tools="",
46-
x_range=(-1.2, 1.8),
47-
y_range=(-1.2, 1.2),
47+
x_range=(-1.4, 1.8),
48+
y_range=(-1.3, 1.3),
4849
)
4950

5051
# Draw donut using annular wedge
5152
p.annular_wedge(
5253
x=0,
5354
y=0,
54-
inner_radius=0.4,
55-
outer_radius=0.9,
55+
inner_radius=0.45,
56+
outer_radius=0.95,
5657
start_angle=cumsum("angle", include_zero=True),
5758
end_angle=cumsum("angle"),
5859
line_color="white",
59-
line_width=3,
60+
line_width=4,
6061
fill_color="color",
6162
source=source,
6263
)
6364

6465
# Add percentage labels on segments
65-
for pct, start, end in zip(data["percentage"], data["start_angle"], data["end_angle"], strict=True):
66+
for pct, start, end, color in zip(data["percentage"], data["start_angle"], data["end_angle"], colors, strict=True):
6667
mid_angle = (start + end) / 2
6768
# Position labels at middle of the ring
68-
label_radius = 0.65
69+
label_radius = 0.70
70+
# Adjust angle to start from top and go clockwise
6971
x = label_radius * np.cos(mid_angle - pi / 2 + pi)
7072
y = label_radius * np.sin(mid_angle - pi / 2 + pi)
7173

74+
# Use white text for dark backgrounds, dark for light backgrounds
75+
text_color = "white" if color in ["#306998", "#FF6B6B", "#4ECDC4"] else "#333333"
76+
7277
label = Label(
7378
x=x,
7479
y=y,
7580
text=pct,
76-
text_font_size="24pt",
77-
text_color="white",
81+
text_font_size="26pt",
82+
text_color=text_color,
7883
text_font_style="bold",
7984
text_align="center",
8085
text_baseline="middle",
8186
)
8287
p.add_layout(label)
8388

8489
# Add center text showing total
85-
total = sum(values)
8690
center_label = Label(
87-
x=0, y=0.05, text="Total", text_font_size="28pt", text_color="#333333", text_align="center", text_baseline="middle"
91+
x=0, y=0.08, text="Total", text_font_size="32pt", text_color="#555555", text_align="center", text_baseline="middle"
8892
)
8993
p.add_layout(center_label)
9094

9195
center_value = Label(
9296
x=0,
93-
y=-0.1,
97+
y=-0.12,
9498
text=str(total),
95-
text_font_size="40pt",
99+
text_font_size="48pt",
96100
text_color="#306998",
97101
text_font_style="bold",
98102
text_align="center",
@@ -101,28 +105,28 @@
101105
p.add_layout(center_value)
102106

103107
# Add legend entries on the right
104-
legend_x = 1.15
105-
legend_y_start = 0.5
106-
legend_spacing = 0.2
108+
legend_x = 1.20
109+
legend_y_start = 0.6
110+
legend_spacing = 0.24
107111

108112
for i, (cat, val, color) in enumerate(zip(categories, values, colors, strict=True)):
109113
y_pos = legend_y_start - i * legend_spacing
110114
# Color box
111-
p.rect(x=legend_x, y=y_pos, width=0.08, height=0.08, fill_color=color, line_color=None)
115+
p.rect(x=legend_x, y=y_pos, width=0.10, height=0.10, fill_color=color, line_color=None)
112116
# Label text
113117
legend_label = Label(
114-
x=legend_x + 0.08,
118+
x=legend_x + 0.10,
115119
y=y_pos,
116-
text=f"{cat} ({val})",
117-
text_font_size="20pt",
120+
text=f"{cat} ({val}%)",
121+
text_font_size="22pt",
118122
text_color="#333333",
119123
text_align="left",
120124
text_baseline="middle",
121125
)
122126
p.add_layout(legend_label)
123127

124128
# Style
125-
p.title.text_font_size = "32pt"
129+
p.title.text_font_size = "36pt"
126130
p.title.text_color = "#333333"
127131
p.axis.visible = False
128132
p.grid.visible = False
Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,27 @@
11
library: bokeh
22
specification_id: donut-basic
3-
created: 2025-12-14 10:15:17+00:00
4-
updated: 2025-12-14 10:15:17+00:00
3+
created: '2025-12-23T00:48:41Z'
4+
updated: '2025-12-23T01:22:13Z'
55
generated_by: claude-opus-4-5-20251101
6-
workflow_run: 20206400232
7-
issue: 733
6+
workflow_run: 20447973231
7+
issue: 0
88
python_version: 3.13.11
99
library_version: 3.8.1
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/donut-basic/bokeh/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/donut-basic/bokeh/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/donut-basic/bokeh/plot.html
1313
quality_score: 91
1414
review:
15-
strengths: []
16-
weaknesses: []
17-
improvements: []
15+
strengths:
16+
- Excellent use of Bokeh annular_wedge for creating donut shape with proper inner/outer
17+
radius
18+
- 'Clean center annotation showing summary statistic (Total: 100) as spec recommends'
19+
- Good use of ColumnDataSource and cumsum transform for angle calculations
20+
- Proper white segment separators (line_width=4) enhance readability
21+
- Smart text color contrast logic (white on dark backgrounds, dark on light)
22+
- Outputs both PNG and interactive HTML versions
23+
weaknesses:
24+
- Legend shows percentage values redundantly - should show actual values instead
25+
of duplicating percentages visible on segments
26+
- 'Two similar teal colors (#4ECDC4 and #95E1D3) could be harder to distinguish
27+
for some users'

0 commit comments

Comments
 (0)