diff --git a/plots/icicle-basic/implementations/plotnine.py b/plots/icicle-basic/implementations/plotnine.py new file mode 100644 index 0000000000..b8d6828777 --- /dev/null +++ b/plots/icicle-basic/implementations/plotnine.py @@ -0,0 +1,160 @@ +""" pyplots.ai +icicle-basic: Basic Icicle Chart +Library: plotnine 0.15.2 | Python 3.13.11 +Quality: 91/100 | Created: 2025-12-30 +""" + +import pandas as pd +from plotnine import aes, element_text, geom_rect, geom_text, ggplot, labs, scale_fill_manual, theme, theme_void + + +# Data: File system hierarchy with sizes (MB) +# Increased small node values to ensure visibility +data = [ + {"name": "root", "parent": "", "value": 0}, + {"name": "Documents", "parent": "root", "value": 0}, + {"name": "Photos", "parent": "root", "value": 0}, + {"name": "Projects", "parent": "root", "value": 0}, + {"name": "Reports", "parent": "Documents", "value": 450}, + {"name": "Invoices", "parent": "Documents", "value": 280}, + {"name": "Notes", "parent": "Documents", "value": 180}, + {"name": "Vacation", "parent": "Photos", "value": 680}, + {"name": "Family", "parent": "Photos", "value": 520}, + {"name": "Events", "parent": "Photos", "value": 340}, + {"name": "WebApp", "parent": "Projects", "value": 0}, + {"name": "DataSci", "parent": "Projects", "value": 0}, + {"name": "Mobile", "parent": "Projects", "value": 380}, + {"name": "Frontend", "parent": "WebApp", "value": 320}, + {"name": "Backend", "parent": "WebApp", "value": 420}, + {"name": "Config", "parent": "WebApp", "value": 200}, + {"name": "Models", "parent": "DataSci", "value": 520}, + {"name": "Scripts", "parent": "DataSci", "value": 300}, +] + +df = pd.DataFrame(data) + +# Build lookup tables +name_to_idx = {row["name"]: idx for idx, row in df.iterrows()} +children_map = {name: df[df["parent"] == name]["name"].tolist() for name in df["name"]} + +# Calculate values for non-leaf nodes (bottom-up aggregation) +# Process nodes from leaves up using iterative approach +processed = set() +while len(processed) < len(df): + for _, row in df.iterrows(): + name = row["name"] + if name in processed: + continue + kids = children_map[name] + if len(kids) == 0: + processed.add(name) + elif all(k in processed for k in kids): + total = sum(df.loc[name_to_idx[k], "value"] for k in kids) + df.loc[name_to_idx[name], "value"] = total + processed.add(name) + +# Calculate depths (distance from root) +depths = {"root": 0} +queue = ["root"] +while queue: + current = queue.pop(0) + for child in children_map[current]: + depths[child] = depths[current] + 1 + queue.append(child) + +max_depth = max(depths.values()) + +# Build icicle rectangles using iterative BFS +rects = [] +# Queue: (name, x_start, x_end) +layout_queue = [("root", 0.0, 1.0)] + +while layout_queue: + name, x_start, x_end = layout_queue.pop(0) + depth = depths[name] + y_top = max_depth - depth + 1 + y_bottom = max_depth - depth + value = df.loc[name_to_idx[name], "value"] + + rects.append( + {"name": name, "xmin": x_start, "xmax": x_end, "ymin": y_bottom, "ymax": y_top, "depth": depth, "value": value} + ) + + # Queue children proportionally + kids = children_map[name] + if kids: + kid_values = [(k, df.loc[name_to_idx[k], "value"]) for k in kids] + kid_values.sort(key=lambda x: -x[1]) # Sort by value descending + total_value = sum(v for _, v in kid_values) + if total_value > 0: + curr_x = x_start + for kid, val in kid_values: + width = (val / total_value) * (x_end - x_start) + layout_queue.append((kid, curr_x, curr_x + width)) + curr_x += width + +rect_df = pd.DataFrame(rects) + +# Color palette by depth - using distinct colors (fixed yellow similarity issue) +colors = { + 0: "#306998", # Python Blue - root + 1: "#4B8BBE", # Lighter blue - level 1 + 2: "#FFD43B", # Python Yellow - level 2 + 3: "#8B4513", # SaddleBrown - level 3 (distinct from yellow) + 4: "#90B4CE", # Light steel blue - level 4 +} +rect_df["fill_color"] = rect_df["depth"].map(colors) + +# Calculate label positions and widths +rect_df["width"] = rect_df["xmax"] - rect_df["xmin"] +rect_df["x_center"] = (rect_df["xmin"] + rect_df["xmax"]) / 2 +rect_df["y_center"] = (rect_df["ymin"] + rect_df["ymax"]) / 2 + +# Labels: show name + value for wide rectangles, name only for medium, hide for very narrow +# Lowered threshold to ensure more labels show value (fix for truncated labels) +rect_df["label"] = rect_df.apply( + lambda r: f"{r['name']}\n({int(r['value'])} MB)" if r["width"] > 0.05 else (r["name"] if r["width"] > 0.02 else ""), + axis=1, +) + +# Convert depth to categorical with proper labels for legend +level_labels = {0: "Level 0 (Root)", 1: "Level 1", 2: "Level 2", 3: "Level 3", 4: "Level 4 (Leaf)"} +rect_df["depth_label"] = pd.Categorical( + rect_df["depth"].map(level_labels), categories=list(level_labels.values()), ordered=True +) + +# Also update dark_bg and light_bg with labels +dark_bg = rect_df[rect_df["depth"].isin([0, 1, 3])] +light_bg = rect_df[rect_df["depth"].isin([2, 4])] + +# Create plot using plotnine grammar of graphics +plot = ( + ggplot(rect_df) + + geom_rect(aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax", fill="depth_label"), color="white", size=1.5) + + geom_text(aes(x="x_center", y="y_center", label="label"), data=dark_bg, size=11, color="white", fontweight="bold") + + geom_text( + aes(x="x_center", y="y_center", label="label"), data=light_bg, size=11, color="black", fontweight="bold" + ) + + scale_fill_manual( + values={ + "Level 0 (Root)": "#306998", + "Level 1": "#4B8BBE", + "Level 2": "#FFD43B", + "Level 3": "#8B4513", + "Level 4 (Leaf)": "#90B4CE", + }, + name="Hierarchy Level", + ) + + labs(title="icicle-basic · plotnine · pyplots.ai") + + theme_void() + + theme( + figure_size=(16, 9), + plot_title=element_text(size=28, ha="center", weight="bold"), + legend_position="right", + legend_title=element_text(size=18), + legend_text=element_text(size=14), + ) +) + +# Save +plot.save("plot.png", dpi=300, verbose=False) diff --git a/plots/icicle-basic/metadata/plotnine.yaml b/plots/icicle-basic/metadata/plotnine.yaml new file mode 100644 index 0000000000..6b7024676d --- /dev/null +++ b/plots/icicle-basic/metadata/plotnine.yaml @@ -0,0 +1,27 @@ +library: plotnine +specification_id: icicle-basic +created: '2025-12-30T21:52:31Z' +updated: '2025-12-30T22:06:37Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 20606631356 +issue: 0 +python_version: 3.13.11 +library_version: 0.15.2 +preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/plotnine/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/plotnine/plot_thumb.png +preview_html: null +quality_score: 91 +review: + strengths: + - Excellent hierarchical visualization with clear parent-child relationships visible + through spatial adjacency + - 'Smart label handling: shows name+value for wide rectangles, name-only for medium, + hidden for narrow' + - Good color contrast between hierarchy levels with appropriate text colors (white + on dark, black on light) + - Proper bottom-up value aggregation for non-leaf nodes + - Clean theme_void usage appropriate for this chart type + - Well-implemented BFS algorithm for layout calculation + weaknesses: + - Legend shows Hierarchy Level with just numbers 0-3 instead of the more descriptive + labels defined in the code