|
| 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) |
0 commit comments