Skip to content

Commit 304f9da

Browse files
update(bubble-packed): bokeh — comprehensive quality review
Comprehensive review improving code quality, data choice, visual design, spec compliance, and library feature usage.
1 parent 7e47de1 commit 304f9da

2 files changed

Lines changed: 72 additions & 46 deletions

File tree

plots/bubble-packed/implementations/bokeh.py

Lines changed: 67 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
""" pyplots.ai
1+
"""pyplots.ai
22
bubble-packed: Basic Packed Bubble Chart
3-
Library: bokeh 3.8.1 | Python 3.13.11
4-
Quality: 91/100 | Created: 2025-12-23
3+
Library: bokeh 3.8.2 | Python 3.14.3
4+
Quality: /100 | Updated: 2026-02-23
55
"""
66

77
import numpy as np
8-
from bokeh.io import export_png, output_file, save
9-
from bokeh.models import ColumnDataSource, LabelSet
8+
from bokeh.io import export_png
9+
from bokeh.models import ColumnDataSource, HoverTool, LabelSet
1010
from bokeh.plotting import figure
1111

1212

@@ -32,33 +32,33 @@
3232
values = [45, 32, 38, 25, 12, 18, 42, 8, 22, 15, 28, 14, 10, 20, 6]
3333

3434
# Calculate radii from values (scale by area for accurate perception)
35-
max_radius = 400
35+
max_radius = 380
3636
radii = np.sqrt(values) / np.sqrt(max(values)) * max_radius
3737

3838
# Circle packing simulation - position circles without overlap
3939
n = len(radii)
40-
center_x, center_y = 2400, 1350
40+
center_x, center_y = 2400, 1400
4141

4242
# Start with random positions near center
4343
x_pos = center_x + (np.random.rand(n) - 0.5) * 1000
4444
y_pos = center_y + (np.random.rand(n) - 0.5) * 600
4545

4646
# Force-directed packing iterations
47-
for _ in range(500):
48-
# Pull toward center
47+
padding = 10
48+
margin = 100
49+
for _ in range(800):
4950
for i in range(n):
5051
dx = center_x - x_pos[i]
5152
dy = center_y - y_pos[i]
5253
x_pos[i] += dx * 0.01
5354
y_pos[i] += dy * 0.01
5455

55-
# Push apart overlapping circles
5656
for i in range(n):
5757
for j in range(i + 1, n):
5858
dx = x_pos[j] - x_pos[i]
5959
dy = y_pos[j] - y_pos[i]
6060
dist = np.sqrt(dx**2 + dy**2) + 0.01
61-
min_dist = radii[i] + radii[j] + 10 # 10px padding
61+
min_dist = radii[i] + radii[j] + padding
6262

6363
if dist < min_dist:
6464
overlap = (min_dist - dist) / 2
@@ -67,28 +67,56 @@
6767
x_pos[j] += dx / dist * overlap
6868
y_pos[j] += dy / dist * overlap
6969

70-
# Create color palette - using Python Blue and Yellow with variations
71-
colors = [
72-
"#306998",
73-
"#FFD43B",
74-
"#4B8BBE",
75-
"#FFE873",
76-
"#3776AB",
77-
"#FFD43B",
78-
"#306998",
79-
"#4B8BBE",
80-
"#FFE873",
81-
"#3776AB",
70+
# Keep circles inside canvas bounds
71+
for i in range(n):
72+
x_pos[i] = np.clip(x_pos[i], radii[i] + margin, 4800 - radii[i] - margin)
73+
y_pos[i] = np.clip(y_pos[i], radii[i] + margin, 2700 - radii[i] - margin)
74+
75+
# Re-center the packed group within the canvas
76+
x_min = min(x_pos[i] - radii[i] for i in range(n))
77+
x_max = max(x_pos[i] + radii[i] for i in range(n))
78+
y_min = min(y_pos[i] - radii[i] for i in range(n))
79+
y_max = max(y_pos[i] + radii[i] for i in range(n))
80+
x_shift = (4800 - (x_min + x_max)) / 2
81+
y_shift = (2700 - (y_min + y_max)) / 2
82+
x_pos += x_shift
83+
y_pos += y_shift
84+
85+
# Color palette - cohesive blues/teals, all dark enough for white text
86+
palette = [
8287
"#306998",
83-
"#FFD43B",
84-
"#4B8BBE",
85-
"#FFE873",
86-
"#3776AB",
88+
"#2A5F8F",
89+
"#1B4F72",
90+
"#1A5276",
91+
"#2E86C1",
92+
"#21618C",
93+
"#2874A6",
94+
"#1F618D",
95+
"#2980B9",
96+
"#1B6B93",
97+
"#1C6EA4",
98+
"#256D85",
99+
"#2471A3",
100+
"#1A5276",
101+
"#154360",
87102
]
103+
# Sort indices by value descending so largest bubbles get most distinct colors
104+
sorted_idx = np.argsort(values)[::-1]
105+
colors = [""] * n
106+
for rank, idx in enumerate(sorted_idx):
107+
colors[idx] = palette[rank]
88108

89109
# Prepare data source
90110
source = ColumnDataSource(
91-
data={"x": x_pos, "y": y_pos, "radius": radii, "category": categories, "value": values, "color": colors}
111+
data={
112+
"x": x_pos,
113+
"y": y_pos,
114+
"radius": radii,
115+
"category": categories,
116+
"value": values,
117+
"color": colors,
118+
"budget_text": [f"${v}M" for v in values],
119+
}
92120
)
93121

94122
# Create figure
@@ -98,17 +126,21 @@
98126
title="Department Budgets · bubble-packed · bokeh · pyplots.ai",
99127
x_range=(0, 4800),
100128
y_range=(0, 2700),
101-
tools="hover",
102-
tooltips=[("Department", "@category"), ("Budget", "$@value M")],
129+
tools="",
130+
toolbar_location=None,
103131
)
104132

133+
# Add hover tool with formatted tooltips
134+
hover = HoverTool(tooltips=[("Department", "@category"), ("Budget", "@budget_text")])
135+
p.add_tools(hover)
136+
105137
# Draw circles
106138
p.circle(
107-
x="x", y="y", radius="radius", source=source, fill_color="color", fill_alpha=0.85, line_color="white", line_width=3
139+
x="x", y="y", radius="radius", source=source, fill_color="color", fill_alpha=0.88, line_color="white", line_width=3
108140
)
109141

110142
# Add labels to circles (only for larger circles)
111-
large_indices = [i for i in range(len(values)) if radii[i] > 120]
143+
large_indices = [i for i in range(n) if radii[i] > 200]
112144
label_source = ColumnDataSource(
113145
data={
114146
"x": [x_pos[i] for i in large_indices],
@@ -140,27 +172,21 @@
140172
text_align="center",
141173
text_baseline="middle",
142174
text_font_size="20pt",
143-
text_color="white",
175+
text_color="rgba(255, 255, 255, 0.85)",
144176
y_offset=-20,
145177
)
146178
p.add_layout(value_labels)
147179

148-
# Style the plot
180+
# Style
149181
p.title.text_font_size = "36pt"
150182
p.title.align = "center"
151-
152-
# Hide axes - packed bubble charts don't use positional axes
153183
p.xaxis.visible = False
154184
p.yaxis.visible = False
155185
p.xgrid.visible = False
156186
p.ygrid.visible = False
157-
158-
# Clean background
159187
p.background_fill_color = "#f8f9fa"
160188
p.border_fill_color = "#f8f9fa"
161189
p.outline_line_color = None
162190

163-
# Save as PNG and HTML
191+
# Save
164192
export_png(p, filename="plot.png")
165-
output_file("plot.html", title="Packed Bubble Chart")
166-
save(p)

plots/bubble-packed/metadata/bokeh.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: bokeh
22
specification_id: bubble-packed
33
created: '2025-12-23T09:17:14Z'
4-
updated: '2025-12-23T09:19:49Z'
5-
generated_by: claude-opus-4-5-20251101
4+
updated: 2026-02-23T15:35:00+00:00
5+
generated_by: claude-opus-4-6
66
workflow_run: 20456560162
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 3.8.1
8+
python_version: "3.14.3"
9+
library_version: "3.8.2"
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot.html
13-
quality_score: 91
13+
quality_score: null
1414
impl_tags:
1515
dependencies: []
1616
techniques:

0 commit comments

Comments
 (0)