Skip to content

Commit 6857727

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

2 files changed

Lines changed: 224 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
""" pyplots.ai
2+
icicle-basic: Basic Icicle Chart
3+
Library: bokeh 3.8.1 | Python 3.13.11
4+
Quality: 92/100 | Created: 2025-12-30
5+
"""
6+
7+
from bokeh.io import export_png, output_file, save
8+
from bokeh.models import ColumnDataSource, Label
9+
from bokeh.plotting import figure
10+
11+
12+
# Data - File system hierarchy with nested folders and file sizes
13+
nodes = [
14+
{"name": "Root", "parent": None, "value": 0},
15+
{"name": "Documents", "parent": "Root", "value": 0},
16+
{"name": "Media", "parent": "Root", "value": 0},
17+
{"name": "Code", "parent": "Root", "value": 0},
18+
{"name": "Reports", "parent": "Documents", "value": 350},
19+
{"name": "Contracts", "parent": "Documents", "value": 250},
20+
{"name": "Notes", "parent": "Documents", "value": 150},
21+
{"name": "Images", "parent": "Media", "value": 500},
22+
{"name": "Videos", "parent": "Media", "value": 800},
23+
{"name": "Audio", "parent": "Media", "value": 300},
24+
{"name": "Python", "parent": "Code", "value": 400},
25+
{"name": "JavaScript", "parent": "Code", "value": 350},
26+
{"name": "Data", "parent": "Code", "value": 200},
27+
]
28+
29+
# Build tree structure
30+
node_dict = {n["name"]: n for n in nodes}
31+
children = {n["name"]: [] for n in nodes}
32+
for n in nodes:
33+
if n["parent"]:
34+
children[n["parent"]].append(n["name"])
35+
36+
37+
# Calculate leaf values for parent nodes (sum of children)
38+
def calc_value(name):
39+
if children[name]:
40+
return sum(calc_value(c) for c in children[name])
41+
return node_dict[name]["value"]
42+
43+
44+
for n in nodes:
45+
n["computed_value"] = calc_value(n["name"])
46+
47+
48+
# Assign levels (depth in tree)
49+
def assign_level(name, level):
50+
node_dict[name]["level"] = level
51+
for c in children[name]:
52+
assign_level(c, level + 1)
53+
54+
55+
assign_level("Root", 0)
56+
max_level = max(n["level"] for n in nodes)
57+
58+
# Colors by level - Python Blue to Yellow gradient
59+
level_colors = ["#306998", "#4A7DAA", "#6591BC", "#FFD43B"]
60+
61+
62+
# Calculate positions for icicle layout (horizontal, top-down)
63+
# Each level is a row, width proportional to value
64+
def layout_icicle(name, x_start, x_end, rects):
65+
node = node_dict[name]
66+
level = node["level"]
67+
68+
# Rectangle for this node
69+
rect = {
70+
"name": name,
71+
"level": level,
72+
"x_start": x_start,
73+
"x_end": x_end,
74+
"y_start": max_level - level,
75+
"y_end": max_level - level + 1,
76+
"value": node["computed_value"],
77+
"color": level_colors[min(level, len(level_colors) - 1)],
78+
}
79+
rects.append(rect)
80+
81+
# Layout children
82+
if children[name]:
83+
total_child_value = sum(node_dict[c]["computed_value"] for c in children[name])
84+
current_x = x_start
85+
for c in children[name]:
86+
child_value = node_dict[c]["computed_value"]
87+
child_width = (x_end - x_start) * (child_value / total_child_value)
88+
layout_icicle(c, current_x, current_x + child_width, rects)
89+
current_x += child_width
90+
91+
92+
rects = []
93+
layout_icicle("Root", 0, 100, rects)
94+
95+
# Prepare data for Bokeh
96+
x_centers = [(r["x_start"] + r["x_end"]) / 2 for r in rects]
97+
y_centers = [(r["y_start"] + r["y_end"]) / 2 for r in rects]
98+
widths = [r["x_end"] - r["x_start"] for r in rects]
99+
heights = [0.95 for r in rects] # Slight gap between levels
100+
colors = [r["color"] for r in rects]
101+
names = [r["name"] for r in rects]
102+
values = [r["value"] for r in rects]
103+
104+
source = ColumnDataSource(
105+
data={
106+
"x": x_centers,
107+
"y": y_centers,
108+
"width": widths,
109+
"height": heights,
110+
"color": colors,
111+
"name": names,
112+
"value": values,
113+
}
114+
)
115+
116+
# Create figure
117+
p = figure(
118+
width=4800,
119+
height=2700,
120+
title="File System Structure · icicle-basic · bokeh · pyplots.ai",
121+
x_range=(-12, 102),
122+
y_range=(-0.3, max_level + 0.8),
123+
tools="",
124+
toolbar_location=None,
125+
)
126+
127+
# Draw rectangles
128+
p.rect(
129+
x="x",
130+
y="y",
131+
width="width",
132+
height="height",
133+
source=source,
134+
fill_color="color",
135+
line_color="white",
136+
line_width=3,
137+
fill_alpha=0.9,
138+
)
139+
140+
# Add labels for rectangles with sufficient width
141+
for r in rects:
142+
rect_width = r["x_end"] - r["x_start"]
143+
x_center = (r["x_start"] + r["x_end"]) / 2
144+
y_center = (r["y_start"] + r["y_end"]) / 2
145+
146+
# Only label if rectangle is wide enough
147+
if rect_width > 4:
148+
font_size = "24pt" if rect_width > 20 else ("20pt" if rect_width > 10 else "16pt")
149+
label_text = r["name"]
150+
if r["level"] > 0 and rect_width > 6:
151+
label_text = f"{r['name']} ({r['value']} MB)"
152+
153+
label = Label(
154+
x=x_center,
155+
y=y_center,
156+
text=label_text,
157+
text_align="center",
158+
text_baseline="middle",
159+
text_font_size=font_size,
160+
text_color="white" if r["level"] < 3 else "#1a1a1a",
161+
)
162+
p.add_layout(label)
163+
164+
# Styling
165+
p.title.text_font_size = "36pt"
166+
p.title.align = "center"
167+
168+
# Hide axes for cleaner look
169+
p.xaxis.visible = False
170+
p.yaxis.visible = False
171+
p.xgrid.visible = False
172+
p.ygrid.visible = False
173+
174+
# Remove outline
175+
p.outline_line_color = None
176+
177+
# Background
178+
p.background_fill_color = "#fafafa"
179+
180+
# Add level labels on the left
181+
level_labels = ["Root", "Categories", "Subcategories"]
182+
for i, label_text in enumerate(level_labels[: max_level + 1]):
183+
label = Label(
184+
x=-1,
185+
y=max_level - i + 0.5,
186+
text=label_text,
187+
text_align="right",
188+
text_baseline="middle",
189+
text_font_size="20pt",
190+
text_color="#666666",
191+
)
192+
p.add_layout(label)
193+
194+
# Save as PNG and HTML
195+
export_png(p, filename="plot.png")
196+
output_file("plot.html", title="icicle-basic · bokeh · pyplots.ai")
197+
save(p)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
library: bokeh
2+
specification_id: icicle-basic
3+
created: '2025-12-30T00:06:12Z'
4+
updated: '2025-12-30T00:09:11Z'
5+
generated_by: claude-opus-4-5-20251101
6+
workflow_run: 20585400055
7+
issue: 0
8+
python_version: 3.13.11
9+
library_version: 3.8.1
10+
preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/bokeh/plot.png
11+
preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/bokeh/plot_thumb.png
12+
preview_html: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/bokeh/plot.html
13+
quality_score: 92
14+
review:
15+
strengths:
16+
- Excellent hierarchical visualization with clear parent-child relationships through
17+
spatial adjacency
18+
- Smart conditional labeling that hides text for small rectangles and includes values
19+
for larger ones
20+
- Beautiful color gradient by hierarchy level (Python Blue theme)
21+
- Level labels on the left margin provide clear context for each row
22+
- Proportional sizing accurately represents values across the hierarchy
23+
- Clean white borders and light background enhance readability
24+
weaknesses:
25+
- Code uses helper functions (calc_value, assign_level, layout_icicle) instead of
26+
KISS linear script style
27+
- Could leverage Bokeh HoverTool for interactive tooltips showing node details

0 commit comments

Comments
 (0)