Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
182 changes: 182 additions & 0 deletions plots/bubble-packed/implementations/bokeh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
"""
bubble-packed: Basic Packed Bubble Chart
Library: bokeh
"""

import numpy as np
from bokeh.io import export_png, output_file, save
from bokeh.models import ColumnDataSource, LabelSet
from bokeh.plotting import figure


# Data - department budgets (in millions)
np.random.seed(42)
categories = [
"Engineering",
"Marketing",
"Sales",
"Operations",
"HR",
"Finance",
"R&D",
"Legal",
"IT",
"Customer Support",
"Product",
"Design",
"QA",
"Data Science",
"Security",
]
values = [45, 32, 38, 25, 12, 18, 42, 8, 22, 15, 28, 14, 10, 20, 6]

# Calculate radii from values (scale by area for accurate perception)
# r = sqrt(value) * scaling factor
max_radius = 400 # max radius in pixels for display
radii = np.sqrt(values) / np.sqrt(max(values)) * max_radius


# Circle packing simulation - position circles without overlap
def pack_circles(radii, center=(2400, 1350), iterations=500):
"""Simple force-directed packing algorithm."""
n = len(radii)
# Start with random positions near center
np.random.seed(42)
x = center[0] + (np.random.rand(n) - 0.5) * 1000
y = center[1] + (np.random.rand(n) - 0.5) * 600

for _ in range(iterations):
# Pull toward center
for i in range(n):
dx = center[0] - x[i]
dy = center[1] - y[i]
dist = np.sqrt(dx**2 + dy**2) + 0.01
x[i] += dx * 0.01
y[i] += dy * 0.01

# Push apart overlapping circles
for i in range(n):
for j in range(i + 1, n):
dx = x[j] - x[i]
dy = y[j] - y[i]
dist = np.sqrt(dx**2 + dy**2) + 0.01
min_dist = radii[i] + radii[j] + 10 # 10px padding

if dist < min_dist:
overlap = (min_dist - dist) / 2
x[i] -= dx / dist * overlap
y[i] -= dy / dist * overlap
x[j] += dx / dist * overlap
y[j] += dy / dist * overlap

return x, y


# Pack circles
x_pos, y_pos = pack_circles(radii)

# Create color palette - using Python Blue as base with variations
colors = [
"#306998",
"#FFD43B",
"#4B8BBE",
"#FFE873",
"#3776AB",
"#FFD43B",
"#306998",
"#4B8BBE",
"#FFE873",
"#3776AB",
"#306998",
"#FFD43B",
"#4B8BBE",
"#FFE873",
"#3776AB",
]

# Prepare data source
source = ColumnDataSource(
data={
"x": x_pos,
"y": y_pos,
"radius": radii,
"category": categories,
"value": values,
"color": colors,
"label": [f"{c}\n${v}M" for c, v in zip(categories, values, strict=True)],
}
)

# Create figure
p = figure(
width=4800,
height=2700,
title="Department Budgets · bubble-packed · bokeh · pyplots.ai",
x_range=(0, 4800),
y_range=(0, 2700),
tools="hover",
tooltips=[("Department", "@category"), ("Budget", "$@value M")],
)

# Draw circles
p.circle(
x="x", y="y", radius="radius", source=source, fill_color="color", fill_alpha=0.85, line_color="white", line_width=3
)

# Add labels to circles (only for larger circles)
# Filter labels for bubbles large enough to show text
label_source = ColumnDataSource(
data={
"x": [x_pos[i] for i in range(len(values)) if radii[i] > 120],
"y": [y_pos[i] for i in range(len(values)) if radii[i] > 120],
"text": [categories[i] for i in range(len(values)) if radii[i] > 120],
"value_text": [f"${values[i]}M" for i in range(len(values)) if radii[i] > 120],
}
)

labels = LabelSet(
x="x",
y="y",
text="text",
source=label_source,
text_align="center",
text_baseline="middle",
text_font_size="24pt",
text_color="white",
text_font_style="bold",
y_offset=15,
)
p.add_layout(labels)

value_labels = LabelSet(
x="x",
y="y",
text="value_text",
source=label_source,
text_align="center",
text_baseline="middle",
text_font_size="20pt",
text_color="white",
y_offset=-20,
)
p.add_layout(value_labels)

# Style the plot
p.title.text_font_size = "36pt"
p.title.align = "center"

# Hide axes - packed bubble charts don't use positional axes
p.xaxis.visible = False
p.yaxis.visible = False
p.xgrid.visible = False
p.ygrid.visible = False

# Clean background
p.background_fill_color = "#f8f9fa"
p.border_fill_color = "#f8f9fa"
p.outline_line_color = None

# Save as PNG and HTML
export_png(p, filename="plot.png")
output_file("plot.html", title="Packed Bubble Chart")
save(p)
23 changes: 23 additions & 0 deletions plots/bubble-packed/metadata/bokeh.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Per-library metadata for bokeh implementation of bubble-packed
# Auto-generated by impl-generate.yml

library: bokeh
specification_id: bubble-packed

# Preview URLs (filled by workflow)
preview_url: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot.png
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot_thumb.png
preview_html: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/bokeh/plot.html

current:
version: 0
generated_at: 2025-12-16T19:12:52Z
generated_by: claude-opus-4-5-20251101
workflow_run: 20279594515
issue: 992
quality_score: 94
# Version info (filled by workflow)
python_version: "3.13.11"
library_version: "unknown"

history: []