Skip to content

Commit a95dc24

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

2 files changed

Lines changed: 97 additions & 56 deletions

File tree

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

77
import altair as alt
88
import numpy as np
99
import pandas as pd
1010

1111

12-
# Data - Department budget allocation
12+
# Data - Department budget allocation with group clusters
1313
np.random.seed(42)
14-
data = {
14+
departments = {
1515
"label": [
1616
"Engineering",
17+
"R&D",
18+
"Data Science",
19+
"QA",
1720
"Marketing",
1821
"Sales",
19-
"Operations",
20-
"HR",
21-
"Finance",
22-
"R&D",
2322
"Support",
23+
"Finance",
24+
"HR",
2425
"Legal",
26+
"Operations",
2527
"IT",
28+
"Security",
2629
"Design",
2730
"Product",
28-
"Data Science",
29-
"Security",
30-
"QA",
3131
],
32-
"value": [850, 420, 680, 320, 180, 290, 750, 210, 150, 380, 240, 550, 460, 170, 195],
32+
"value": [850, 750, 460, 195, 420, 680, 210, 290, 180, 150, 320, 380, 170, 240, 550],
33+
"group": [
34+
"Technology",
35+
"Technology",
36+
"Technology",
37+
"Technology",
38+
"Revenue",
39+
"Revenue",
40+
"Revenue",
41+
"Corporate",
42+
"Corporate",
43+
"Corporate",
44+
"Operations",
45+
"Operations",
46+
"Operations",
47+
"Product",
48+
"Product",
49+
],
3350
}
3451

35-
labels = data["label"]
36-
values = data["value"]
52+
labels = departments["label"]
53+
values = departments["value"]
54+
groups = departments["group"]
3755
n = len(labels)
3856

39-
# Scale values to radius (using sqrt for area-proportional sizing)
57+
# Scale values to radius (sqrt for area-proportional sizing)
4058
min_radius = 30
4159
max_radius = 120
4260
values_array = np.array(values)
@@ -49,39 +67,30 @@
4967
radii = radii[order]
5068
labels = [labels[i] for i in order]
5169
values = [values[i] for i in order]
70+
groups = [groups[i] for i in order]
5271

5372
# Circle packing - place circles one by one, finding best position
5473
x_pos = np.zeros(n)
5574
y_pos = np.zeros(n)
5675

57-
# Place first circle at center
58-
x_pos[0] = 0
59-
y_pos[0] = 0
60-
61-
# Place remaining circles
6276
for i in range(1, n):
63-
best_x, best_y = 0, 0
77+
best_x, best_y = 0.0, 0.0
6478
best_dist = float("inf")
6579

66-
# Try positions around existing circles
6780
for j in range(i):
6881
for angle in np.linspace(0, 2 * np.pi, 36, endpoint=False):
69-
# Position touching circle j
7082
test_x = x_pos[j] + (radii[j] + radii[i] + 2) * np.cos(angle)
7183
test_y = y_pos[j] + (radii[j] + radii[i] + 2) * np.sin(angle)
7284

73-
# Check for overlaps with all placed circles
7485
valid = True
7586
for k in range(i):
7687
dx = test_x - x_pos[k]
7788
dy = test_y - y_pos[k]
78-
dist = np.sqrt(dx**2 + dy**2)
79-
if dist < radii[i] + radii[k] + 1:
89+
if np.sqrt(dx**2 + dy**2) < radii[i] + radii[k] + 1:
8090
valid = False
8191
break
8292

8393
if valid:
84-
# Prefer positions closer to center
8594
center_dist = np.sqrt(test_x**2 + test_y**2)
8695
if center_dist < best_dist:
8796
best_dist = center_dist
@@ -90,14 +99,10 @@
9099
x_pos[i] = best_x
91100
y_pos[i] = best_y
92101

93-
# Fine-tune with physics simulation
102+
# Physics simulation for tighter packing
94103
for _ in range(200):
95104
for i in range(n):
96-
fx, fy = 0, 0
97-
# Gentle centering force
98-
fx -= x_pos[i] * 0.01
99-
fy -= y_pos[i] * 0.01
100-
# Repulsion from overlapping circles
105+
fx, fy = -x_pos[i] * 0.01, -y_pos[i] * 0.01
101106
for j in range(n):
102107
if i != j:
103108
dx = x_pos[i] - x_pos[j]
@@ -111,44 +116,80 @@
111116
x_pos[i] += fx
112117
y_pos[i] += fy
113118

114-
# Color palette - colorblind-safe colors with Python Blue and Yellow as primary
115-
colors_list = ["#306998", "#FFD43B", "#4A90A4", "#7B9E89", "#E07A5F"]
116-
colors = [colors_list[i % len(colors_list)] for i in range(n)]
119+
# Center the layout
120+
x_center = (x_pos.min() + x_pos.max()) / 2
121+
y_center = (y_pos.min() + y_pos.max()) / 2
122+
x_pos -= x_center
123+
y_pos -= y_center
124+
125+
# Group color palette - colorblind-safe
126+
group_colors = {
127+
"Technology": "#306998",
128+
"Revenue": "#E07A5F",
129+
"Corporate": "#7B9E89",
130+
"Operations": "#4A90A4",
131+
"Product": "#FFD43B",
132+
}
117133

118-
# Create DataFrame with computed positions
119134
df = pd.DataFrame(
120135
{
121136
"label": labels,
122137
"value": values,
138+
"group": groups,
123139
"x": x_pos,
124140
"y": y_pos,
125141
"radius": radii,
126-
"color": colors,
127-
"formatted_value": [f"${v}K" for v in values],
142+
"budget": [f"${v}K" for v in values],
128143
}
129144
)
130145

131-
# Create circles using mark_circle with computed positions
146+
# Group ordering for consistent legend
147+
group_order = ["Technology", "Revenue", "Operations", "Corporate", "Product"]
148+
149+
# Circles layer
132150
circles = (
133151
alt.Chart(df)
134152
.mark_circle(opacity=0.85, stroke="white", strokeWidth=2)
135153
.encode(
136-
x=alt.X("x:Q", axis=None),
137-
y=alt.Y("y:Q", axis=None),
154+
x=alt.X("x:Q", axis=None, scale=alt.Scale(padding=max_radius * 1.4)),
155+
y=alt.Y("y:Q", axis=None, scale=alt.Scale(padding=max_radius * 1.4)),
138156
size=alt.Size("radius:Q", scale=alt.Scale(range=[min_radius**2 * 3, max_radius**2 * 3]), legend=None),
139-
color=alt.Color("color:N", scale=None, legend=None),
140-
tooltip=[alt.Tooltip("label:N", title="Department"), alt.Tooltip("formatted_value:N", title="Budget")],
157+
color=alt.Color(
158+
"group:N",
159+
scale=alt.Scale(domain=group_order, range=[group_colors[g] for g in group_order]),
160+
legend=alt.Legend(
161+
title="Division",
162+
titleFontSize=18,
163+
titleFontWeight="bold",
164+
labelFontSize=15,
165+
symbolSize=300,
166+
orient="none",
167+
legendX=1400,
168+
legendY=680,
169+
direction="vertical",
170+
),
171+
),
172+
tooltip=[
173+
alt.Tooltip("label:N", title="Department"),
174+
alt.Tooltip("budget:N", title="Budget"),
175+
alt.Tooltip("group:N", title="Division"),
176+
],
141177
)
142178
)
143179

144-
# Create labels for larger bubbles
145-
df_large = df[df["radius"] > 55].copy()
146-
df_large["display_text"] = df_large["label"] + "\n" + df_large["formatted_value"]
180+
# Labels inside larger bubbles
181+
df_large = df[df["radius"] > 50].copy()
182+
df_large["display_text"] = df_large["label"] + "\n" + df_large["budget"]
147183

148184
labels_layer = (
149185
alt.Chart(df_large)
150-
.mark_text(color="white", fontWeight="bold", fontSize=14, lineBreak="\n")
151-
.encode(x=alt.X("x:Q"), y=alt.Y("y:Q"), text="display_text:N")
186+
.mark_text(fontWeight="bold", fontSize=13, lineBreak="\n")
187+
.encode(
188+
x=alt.X("x:Q"),
189+
y=alt.Y("y:Q"),
190+
text="display_text:N",
191+
color=alt.condition(alt.datum.group == "Product", alt.value("#333333"), alt.value("white")),
192+
)
152193
)
153194

154195
# Combine layers
@@ -167,6 +208,6 @@
167208
.configure_view(strokeWidth=0)
168209
)
169210

170-
# Save outputs
211+
# Save
171212
chart.save("plot.png", scale_factor=3.0)
172213
chart.save("plot.html")

plots/bubble-packed/metadata/altair.yaml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
library: altair
22
specification_id: bubble-packed
33
created: '2025-12-23T09:16:07Z'
4-
updated: '2025-12-23T09:18:59Z'
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: 20456560761
77
issue: 0
8-
python_version: 3.13.11
9-
library_version: 6.0.0
8+
python_version: "3.14.3"
9+
library_version: "6.0.0"
1010
preview_url: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/altair/plot.png
1111
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/altair/plot_thumb.png
1212
preview_html: https://storage.googleapis.com/pyplots-images/plots/bubble-packed/altair/plot.html
13-
quality_score: 91
13+
quality_score: null
1414
impl_tags:
1515
dependencies: []
1616
techniques:

0 commit comments

Comments
 (0)