Skip to content

Commit eb93993

Browse files
feat(altair): implement icicle-basic (#2517)
## Implementation: `icicle-basic` - altair Implements the **altair** version of `icicle-basic`. **File:** `plots/icicle-basic/implementations/altair.py` --- :robot: *[impl-generate workflow](https://github.com/MarkusNeusinger/pyplots/actions/runs/20585400003)* --------- 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 c66242f commit eb93993

2 files changed

Lines changed: 203 additions & 0 deletions

File tree

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
""" pyplots.ai
2+
icicle-basic: Basic Icicle Chart
3+
Library: altair 6.0.0 | Python 3.13.11
4+
Quality: 91/100 | Created: 2025-12-30
5+
"""
6+
7+
import altair as alt
8+
import pandas as pd
9+
10+
11+
# Data: File system hierarchy with sizes (in MB)
12+
# Structure designed with balanced folder sizes for better visibility
13+
data = [
14+
# Root
15+
{"name": "root", "parent": None, "value": 0},
16+
# Level 1: Main folders
17+
{"name": "Documents", "parent": "root", "value": 0},
18+
{"name": "Media", "parent": "root", "value": 0},
19+
{"name": "Projects", "parent": "root", "value": 0},
20+
# Level 2: Documents subfolders
21+
{"name": "Reports", "parent": "Documents", "value": 0},
22+
{"name": "Presentations", "parent": "Documents", "value": 0},
23+
# Level 2: Media subfolders
24+
{"name": "Images", "parent": "Media", "value": 0},
25+
{"name": "Videos", "parent": "Media", "value": 0},
26+
# Level 2: Projects subfolders
27+
{"name": "WebApp", "parent": "Projects", "value": 0},
28+
{"name": "DataScience", "parent": "Projects", "value": 0},
29+
# Level 3: Leaf nodes with sizes balanced for better visual representation
30+
{"name": "Q1_Report.pdf", "parent": "Reports", "value": 120},
31+
{"name": "Q2_Report.pdf", "parent": "Reports", "value": 95},
32+
{"name": "Annual_Review.pdf", "parent": "Reports", "value": 150},
33+
{"name": "Sales_Deck.pptx", "parent": "Presentations", "value": 85},
34+
{"name": "Strategy.pptx", "parent": "Presentations", "value": 110},
35+
{"name": "photo_album.jpg", "parent": "Images", "value": 180},
36+
{"name": "banner.png", "parent": "Images", "value": 75},
37+
{"name": "tutorial.mp4", "parent": "Videos", "value": 350},
38+
{"name": "demo.mp4", "parent": "Videos", "value": 280},
39+
{"name": "frontend.js", "parent": "WebApp", "value": 65},
40+
{"name": "backend.py", "parent": "WebApp", "value": 120},
41+
{"name": "styles.css", "parent": "WebApp", "value": 45},
42+
{"name": "analysis.ipynb", "parent": "DataScience", "value": 95},
43+
{"name": "model.pkl", "parent": "DataScience", "value": 180},
44+
]
45+
46+
df = pd.DataFrame(data)
47+
48+
# Build tree structure using iterative approach (KISS - no helper functions)
49+
name_to_idx = {row["name"]: i for i, row in enumerate(data)}
50+
children = {row["name"]: [] for row in data}
51+
for row in data:
52+
if row["parent"]:
53+
children[row["parent"]].append(row["name"])
54+
55+
# Calculate levels (depth) iteratively
56+
levels = {"root": 0}
57+
queue = ["root"]
58+
while queue:
59+
current = queue.pop(0)
60+
for child in children[current]:
61+
levels[child] = levels[current] + 1
62+
queue.append(child)
63+
64+
for row in data:
65+
row["level"] = levels[row["name"]]
66+
67+
# Calculate cumulative values bottom-up (leaf to root)
68+
max_level = max(levels.values())
69+
for level in range(max_level, -1, -1):
70+
for row in data:
71+
if row["level"] == level:
72+
if children[row["name"]]:
73+
row["total_value"] = sum(data[name_to_idx[c]]["total_value"] for c in children[row["name"]])
74+
else:
75+
row["total_value"] = row["value"]
76+
77+
# Calculate x positions iteratively (horizontal placement based on value)
78+
positions = {"root": (0, 1)}
79+
queue = ["root"]
80+
while queue:
81+
current = queue.pop(0)
82+
x_start, x_end = positions[current]
83+
child_list = children[current]
84+
if child_list:
85+
total = sum(data[name_to_idx[c]]["total_value"] for c in child_list)
86+
if total > 0:
87+
current_x = x_start
88+
for child in child_list:
89+
child_val = data[name_to_idx[child]]["total_value"]
90+
child_width = (x_end - x_start) * child_val / total
91+
positions[child] = (current_x, current_x + child_width)
92+
current_x += child_width
93+
queue.append(child)
94+
95+
for row in data:
96+
row["x_start"], row["x_end"] = positions[row["name"]]
97+
98+
# Prepare data for Altair rectangles
99+
rect_data = []
100+
for row in data:
101+
if row["total_value"] > 0:
102+
rect_data.append(
103+
{
104+
"name": row["name"],
105+
"x_start": row["x_start"],
106+
"x_end": row["x_end"],
107+
"y_start": row["level"],
108+
"y_end": row["level"] + 1,
109+
"level": row["level"],
110+
"value": row["total_value"],
111+
"parent": row["parent"] if row["parent"] else "None",
112+
}
113+
)
114+
115+
rect_df = pd.DataFrame(rect_data)
116+
117+
# Color scale with stronger contrast between adjacent levels
118+
# Using distinct hues for better visual separation
119+
level_colors = ["#1a5276", "#f39c12", "#27ae60", "#8e44ad", "#e74c3c"]
120+
121+
# Create icicle chart with mark_rect
122+
chart = (
123+
alt.Chart(rect_df)
124+
.mark_rect(stroke="white", strokeWidth=2)
125+
.encode(
126+
x=alt.X("x_start:Q", axis=None, scale=alt.Scale(domain=[0, 1])),
127+
x2=alt.X2("x_end:Q"),
128+
y=alt.Y(
129+
"y_start:Q",
130+
axis=alt.Axis(
131+
title="Hierarchy Level",
132+
labelFontSize=18,
133+
titleFontSize=22,
134+
values=list(range(max_level + 2)),
135+
format="d",
136+
),
137+
scale=alt.Scale(domain=[0, max_level + 1]),
138+
),
139+
y2=alt.Y2("y_end:Q"),
140+
color=alt.Color(
141+
"level:O",
142+
scale=alt.Scale(domain=list(range(max_level + 1)), range=level_colors),
143+
legend=alt.Legend(title="Level", labelFontSize=16, titleFontSize=18, orient="right"),
144+
),
145+
tooltip=["name:N", "value:Q", "parent:N", "level:O"],
146+
)
147+
)
148+
149+
# Add text labels for larger rectangles
150+
text = (
151+
alt.Chart(rect_df)
152+
.mark_text(fontSize=14, color="white", fontWeight="bold", align="center")
153+
.encode(
154+
x=alt.X("x_mid:Q", scale=alt.Scale(domain=[0, 1])),
155+
y=alt.Y("y_mid:Q", scale=alt.Scale(domain=[0, max_level + 1])),
156+
text=alt.Text("label:N"),
157+
)
158+
.transform_calculate(
159+
x_mid="(datum.x_start + datum.x_end) / 2",
160+
y_mid="(datum.y_start + datum.y_end) / 2",
161+
width="datum.x_end - datum.x_start",
162+
label="datum.width > 0.05 ? datum.name : ''",
163+
)
164+
)
165+
166+
# Combine chart and text
167+
icicle = (
168+
(chart + text)
169+
.properties(width=1600, height=900, title="icicle-basic · altair · pyplots.ai")
170+
.configure_title(fontSize=28, anchor="middle")
171+
.configure_view(strokeWidth=0)
172+
)
173+
174+
# Save as PNG and HTML
175+
icicle.save("plot.png", scale_factor=3.0)
176+
icicle.save("plot.html")
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: altair
2+
specification_id: icicle-basic
3+
created: '2025-12-30T00:03:43Z'
4+
updated: '2025-12-30T00:22:46Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20585400003
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 6.0.0
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/altair/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/altair/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/altair/plot.html
13+
quality_score: 91
14+
review:
15+
strengths:
16+
- Excellent hierarchical visualization with clear parent-child relationships visible
17+
through spatial adjacency
18+
- Smart label hiding for narrow rectangles prevents text overlap
19+
- Good use of Altair declarative syntax with mark_rect and transform_calculate
20+
- Interactive tooltips enhance usability
21+
- File system example matches specification suggested application perfectly
22+
- Clean code structure following KISS principles with iterative tree building
23+
weaknesses:
24+
- Color palette uses only blue hues which may reduce accessibility for some colorblind
25+
users; consider using more distinct hues across levels
26+
- Some leaf nodes appear very narrow making the hierarchy harder to read in that
27+
section

0 commit comments

Comments
 (0)