From 11c48c1e61f552d159d11a6edea9ff1bf095bd33 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Dec 2025 21:52:13 +0000 Subject: [PATCH 1/5] feat(pygal): implement icicle-basic --- plots/icicle-basic/implementations/pygal.py | 286 ++++++++++++++++++++ 1 file changed, 286 insertions(+) create mode 100644 plots/icicle-basic/implementations/pygal.py diff --git a/plots/icicle-basic/implementations/pygal.py b/plots/icicle-basic/implementations/pygal.py new file mode 100644 index 0000000000..106ec25776 --- /dev/null +++ b/plots/icicle-basic/implementations/pygal.py @@ -0,0 +1,286 @@ +"""pyplots.ai +icicle-basic: Basic Icicle Chart +Library: pygal | Python 3.13 +Quality: pending | Created: 2025-12-30 +""" + +import xml.etree.ElementTree as ET + +import cairosvg +import pygal +from pygal.style import Style + + +# Data: File system structure with folders and files +# Format: (name, parent, value) - leaf nodes have values, internal nodes computed +hierarchy_data = [ + ("Root", None, 0), + ("Documents", "Root", 0), + ("Pictures", "Root", 0), + ("Music", "Root", 0), + ("Reports", "Documents", 0), + ("Letters", "Documents", 0), + ("Spreadsheets", "Documents", 0), + ("Photos", "Pictures", 0), + ("Screenshots", "Pictures", 0), + ("Icons", "Pictures", 0), + ("Albums", "Music", 0), + ("Playlists", "Music", 0), + ("Podcasts", "Music", 0), + ("Q1_Report", "Reports", 45), + ("Q2_Report", "Reports", 55), + ("Q3_Report", "Reports", 50), + ("Cover_Letter", "Letters", 25), + ("Resume", "Letters", 35), + ("Thank_You", "Letters", 20), + ("Budget", "Spreadsheets", 60), + ("Forecast", "Spreadsheets", 40), + ("Analysis", "Spreadsheets", 20), + ("Photo_1", "Photos", 65), + ("Photo_2", "Photos", 75), + ("Photo_3", "Photos", 60), + ("Screen_1", "Screenshots", 25), + ("Screen_2", "Screenshots", 25), + ("Icon_1", "Icons", 15), + ("Icon_2", "Icons", 15), + ("Rock", "Albums", 60), + ("Jazz", "Albums", 55), + ("Pop", "Albums", 65), + ("Favorites", "Playlists", 40), + ("Podcast_1", "Podcasts", 45), + ("Podcast_2", "Podcasts", 45), +] + +# Build tree structure +nodes = {} +children = {} + +for name, parent, value in hierarchy_data: + nodes[name] = {"name": name, "parent": parent, "value": value} + if parent is not None: + if parent not in children: + children[parent] = [] + children[parent].append(name) + +# Calculate total values for all nodes (bottom-up traversal) +# Get nodes in depth order using BFS +node_depths = {"Root": 0} +queue = ["Root"] +depth_order = [] +while queue: + current = queue.pop(0) + depth_order.append(current) + if current in children: + for child in children[current]: + node_depths[child] = node_depths[current] + 1 + queue.append(child) + +# Calculate values bottom-up +node_values = {} +for node_name in reversed(depth_order): + if node_name not in children: + node_values[node_name] = nodes[node_name]["value"] + else: + node_values[node_name] = sum(node_values[child] for child in children[node_name]) + +# Calculate positions for icicle chart (top-to-bottom layout) +positions = {} +positions["Root"] = {"x_start": 0, "x_end": 1, "depth": 0, "value": node_values["Root"]} + +# Process nodes level by level +for node_name in depth_order: + if node_name in children: + pos = positions[node_name] + current_x = pos["x_start"] + total_value = node_values[node_name] + for child in children[node_name]: + child_value = node_values[child] + child_width = (child_value / total_value) * (pos["x_end"] - pos["x_start"]) + positions[child] = { + "x_start": current_x, + "x_end": current_x + child_width, + "depth": pos["depth"] + 1, + "value": child_value, + } + current_x += child_width + +# Find max depth +max_depth = max(pos["depth"] for pos in positions.values()) + +# Chart dimensions (landscape format for icicle chart) +WIDTH = 4800 +HEIGHT = 2700 +MARGIN_TOP = 120 +MARGIN_BOTTOM = 100 +MARGIN_LEFT = 50 +MARGIN_RIGHT = 200 # Space for level labels +PLOT_WIDTH = WIDTH - MARGIN_LEFT - MARGIN_RIGHT +PLOT_HEIGHT = HEIGHT - MARGIN_TOP - MARGIN_BOTTOM + +# Color palette by depth level (colorblind-safe) +DEPTH_COLORS = [ + "#306998", # Python Blue - Level 0 + "#FFD43B", # Python Yellow - Level 1 + "#4ECDC4", # Teal - Level 2 + "#FF6B6B", # Coral - Level 3 + "#95E1D3", # Light teal - Level 4 +] + +# Text colors for each depth (white on dark, black on light) +TEXT_COLORS = ["white", "#333333", "#333333", "white", "#333333"] + +# Use pygal Style for consistent theming +custom_style = Style( + background="white", + plot_background="white", + foreground="#333", + foreground_strong="#333", + foreground_subtle="#666", + colors=DEPTH_COLORS, + title_font_size=72, + label_font_size=42, + major_label_font_size=36, + legend_font_size=36, + font_family="sans-serif", +) + +# Create base pygal config (used for style extraction) +config = pygal.Config() +config.width = WIDTH +config.height = HEIGHT +config.style = custom_style + +# Build SVG using standard library +svg_ns = "http://www.w3.org/2000/svg" +ET.register_namespace("", svg_ns) + +svg_root = ET.Element("svg", xmlns=svg_ns, width=str(WIDTH), height=str(HEIGHT), viewBox=f"0 0 {WIDTH} {HEIGHT}") +svg_root.set("style", f"background-color: {custom_style.background};") + +# Add title +title_elem = ET.SubElement(svg_root, "text") +title_elem.set("x", str(WIDTH / 2)) +title_elem.set("y", "70") +title_elem.set("text-anchor", "middle") +title_elem.set("fill", custom_style.foreground_strong) +title_elem.set("font-size", str(custom_style.title_font_size)) +title_elem.set("font-family", custom_style.font_family) +title_elem.set("font-weight", "bold") +title_elem.text = "icicle-basic · pygal · pyplots.ai" + +# Create main group for rectangles +g = ET.SubElement(svg_root, "g") +g.set("class", "icicle-chart") + +# Draw rectangles +row_height = PLOT_HEIGHT / (max_depth + 1) +gap = 3 # Small gap between rectangles + +for node_name, pos in positions.items(): + depth = pos["depth"] + x_start = pos["x_start"] + x_end = pos["x_end"] + width = x_end - x_start + + # Calculate pixel positions + px_x = MARGIN_LEFT + x_start * PLOT_WIDTH + px_width = width * PLOT_WIDTH - gap + px_y = MARGIN_TOP + depth * row_height + px_height = row_height - gap + + # Get color based on depth + color = DEPTH_COLORS[depth % len(DEPTH_COLORS)] + + # Create rectangle element + rect = ET.SubElement(g, "rect") + rect.set("x", f"{px_x:.1f}") + rect.set("y", f"{px_y:.1f}") + rect.set("width", f"{max(0, px_width):.1f}") + rect.set("height", f"{px_height:.1f}") + rect.set("fill", color) + rect.set("fill-opacity", "0.85") + rect.set("stroke", "white") + rect.set("stroke-width", "2") + + # Add tooltip + title = ET.SubElement(rect, "title") + title.text = f"{node_name.replace('_', ' ')}: {pos['value']}" + + # Add label if rectangle is wide enough + if px_width > 60: + label = node_name.replace("_", " ") + # Calculate max characters based on width + max_chars = max(3, int(px_width / 22)) + if len(label) > max_chars: + label = label[: max_chars - 2] + ".." + + # Calculate font size based on width + fontsize = min(36, max(18, int(px_width / 6))) + + text = ET.SubElement(g, "text") + text.set("x", f"{px_x + px_width / 2:.1f}") + text.set("y", f"{px_y + px_height / 2 + fontsize / 3:.1f}") + text.set("text-anchor", "middle") + text.set("fill", TEXT_COLORS[depth % len(TEXT_COLORS)]) + text.set("font-size", str(fontsize)) + text.set("font-family", custom_style.font_family) + text.set("font-weight", "bold") + text.text = label + +# Add depth level labels on the right +level_labels = ["Root", "Category", "Subcategory", "Item", "Detail"] +labels_g = ET.SubElement(svg_root, "g") +labels_g.set("class", "level-labels") + +for depth in range(max_depth + 1): + y_pos = MARGIN_TOP + depth * row_height + row_height / 2 + level_label = level_labels[depth] if depth < len(level_labels) else f"Level {depth}" + + text = ET.SubElement(labels_g, "text") + text.set("x", str(MARGIN_LEFT + PLOT_WIDTH + 25)) + text.set("y", f"{y_pos + 10:.1f}") + text.set("fill", custom_style.foreground_strong) + text.set("font-size", str(custom_style.major_label_font_size)) + text.set("font-family", custom_style.font_family) + text.text = level_label + +# Add legend at bottom +legend_y = HEIGHT - 50 +legend_items = [ + ("Root", DEPTH_COLORS[0]), + ("Category", DEPTH_COLORS[1]), + ("Subcategory", DEPTH_COLORS[2]), + ("Item", DEPTH_COLORS[3]), +] +legend_x_start = WIDTH / 2 - 400 + +legend_g = ET.SubElement(svg_root, "g") +legend_g.set("class", "legend") + +for i, (label, color) in enumerate(legend_items): + x = legend_x_start + i * 220 + # Rectangle marker + marker = ET.SubElement(legend_g, "rect") + marker.set("x", str(x)) + marker.set("y", str(legend_y - 15)) + marker.set("width", "30") + marker.set("height", "30") + marker.set("fill", color) + marker.set("stroke", "#444") + marker.set("stroke-width", "1") + # Label + lbl = ET.SubElement(legend_g, "text") + lbl.set("x", str(x + 40)) + lbl.set("y", str(legend_y + 6)) + lbl.set("fill", custom_style.foreground_strong) + lbl.set("font-size", str(custom_style.legend_font_size)) + lbl.set("font-family", custom_style.font_family) + lbl.text = label + +# Write SVG to file (pygal convention for interactive output) +svg_output = ET.tostring(svg_root, encoding="unicode") +with open("plot.html", "w") as f: + f.write(svg_output) + +# Render to PNG via cairosvg +cairosvg.svg2png(bytestring=svg_output.encode("utf-8"), write_to="plot.png") From ba8d7a402fcf92f210a1bf9194fd1e191795df9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Dec 2025 21:52:27 +0000 Subject: [PATCH 2/5] chore(pygal): add metadata for icicle-basic --- plots/icicle-basic/metadata/pygal.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 plots/icicle-basic/metadata/pygal.yaml diff --git a/plots/icicle-basic/metadata/pygal.yaml b/plots/icicle-basic/metadata/pygal.yaml new file mode 100644 index 0000000000..c9f3f99da6 --- /dev/null +++ b/plots/icicle-basic/metadata/pygal.yaml @@ -0,0 +1,19 @@ +# Per-library metadata for pygal implementation of icicle-basic +# Auto-generated by impl-generate.yml + +library: pygal +specification_id: icicle-basic +created: '2025-12-30T21:52:27Z' +updated: '2025-12-30T21:52:27Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 20606632269 +issue: 0 +python_version: 3.13.11 +library_version: 3.1.0 +preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot_thumb.png +preview_html: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.html +quality_score: null +review: + strengths: [] + weaknesses: [] From 3ea6e4573851da66e44b04b663681dc57bf828c9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Dec 2025 21:54:38 +0000 Subject: [PATCH 3/5] chore(pygal): update quality score 88 and review feedback for icicle-basic --- plots/icicle-basic/implementations/pygal.py | 6 ++--- plots/icicle-basic/metadata/pygal.yaml | 25 +++++++++++++++------ 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/plots/icicle-basic/implementations/pygal.py b/plots/icicle-basic/implementations/pygal.py index 106ec25776..ea49f25a7b 100644 --- a/plots/icicle-basic/implementations/pygal.py +++ b/plots/icicle-basic/implementations/pygal.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai icicle-basic: Basic Icicle Chart -Library: pygal | Python 3.13 -Quality: pending | Created: 2025-12-30 +Library: pygal 3.1.0 | Python 3.13.11 +Quality: 88/100 | Created: 2025-12-30 """ import xml.etree.ElementTree as ET diff --git a/plots/icicle-basic/metadata/pygal.yaml b/plots/icicle-basic/metadata/pygal.yaml index c9f3f99da6..39eeb5e505 100644 --- a/plots/icicle-basic/metadata/pygal.yaml +++ b/plots/icicle-basic/metadata/pygal.yaml @@ -1,10 +1,7 @@ -# Per-library metadata for pygal implementation of icicle-basic -# Auto-generated by impl-generate.yml - library: pygal specification_id: icicle-basic created: '2025-12-30T21:52:27Z' -updated: '2025-12-30T21:52:27Z' +updated: '2025-12-30T21:54:38Z' generated_by: claude-opus-4-5-20251101 workflow_run: 20606632269 issue: 0 @@ -13,7 +10,21 @@ library_version: 3.1.0 preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.html -quality_score: null +quality_score: 88 review: - strengths: [] - weaknesses: [] + strengths: + - Excellent hierarchical visualization with clear parent-child relationships through + spatial adjacency + - Smart label truncation that prevents overlap while keeping cells identifiable + - Colorblind-safe palette with distinct colors for each hierarchy level + - File system example provides intuitive, realistic context that demonstrates the + chart type well + - Proper top-to-bottom icicle layout matching the specification + - Good use of tooltips for full node information + - Level labels on the right side provide helpful context + weaknesses: + - Legend at bottom has spacing issue causing Subcategory and Item labels to appear + merged together + - Heavy reliance on manual SVG construction (xml.etree.ElementTree) rather than + pygal chart classes, since pygal lacks native icicle support + - Some very narrow cells (like Icon_1, Icon_2) are barely visible From dc041612e68923cedf98ab52f8dee1cd3b8735f9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Dec 2025 21:57:41 +0000 Subject: [PATCH 4/5] fix(pygal): address review feedback for icicle-basic - Increased legend item spacing from 220px to 300px to prevent Subcategory and Item labels from appearing merged together - Increased Icon_1 and Icon_2 values from 15 to 35 to make these narrow cells more visible in the icicle chart Attempt 1/3 - fixes based on AI review --- plots/icicle-basic/implementations/pygal.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/plots/icicle-basic/implementations/pygal.py b/plots/icicle-basic/implementations/pygal.py index ea49f25a7b..75c5b850dc 100644 --- a/plots/icicle-basic/implementations/pygal.py +++ b/plots/icicle-basic/implementations/pygal.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai icicle-basic: Basic Icicle Chart Library: pygal 3.1.0 | Python 3.13.11 Quality: 88/100 | Created: 2025-12-30 @@ -41,8 +41,8 @@ ("Photo_3", "Photos", 60), ("Screen_1", "Screenshots", 25), ("Screen_2", "Screenshots", 25), - ("Icon_1", "Icons", 15), - ("Icon_2", "Icons", 15), + ("Icon_1", "Icons", 35), + ("Icon_2", "Icons", 35), ("Rock", "Albums", 60), ("Jazz", "Albums", 55), ("Pop", "Albums", 65), @@ -252,13 +252,13 @@ ("Subcategory", DEPTH_COLORS[2]), ("Item", DEPTH_COLORS[3]), ] -legend_x_start = WIDTH / 2 - 400 +legend_x_start = WIDTH / 2 - 550 legend_g = ET.SubElement(svg_root, "g") legend_g.set("class", "legend") for i, (label, color) in enumerate(legend_items): - x = legend_x_start + i * 220 + x = legend_x_start + i * 300 # Rectangle marker marker = ET.SubElement(legend_g, "rect") marker.set("x", str(x)) From f0f9915b2189e1cdce1e3b3ab132b9a7c5b1ed32 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 30 Dec 2025 22:00:15 +0000 Subject: [PATCH 5/5] chore(pygal): update quality score 91 and review feedback for icicle-basic --- plots/icicle-basic/implementations/pygal.py | 4 ++-- plots/icicle-basic/metadata/pygal.yaml | 25 +++++++++------------ 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/plots/icicle-basic/implementations/pygal.py b/plots/icicle-basic/implementations/pygal.py index 75c5b850dc..c1cbf9e23c 100644 --- a/plots/icicle-basic/implementations/pygal.py +++ b/plots/icicle-basic/implementations/pygal.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai icicle-basic: Basic Icicle Chart Library: pygal 3.1.0 | Python 3.13.11 -Quality: 88/100 | Created: 2025-12-30 +Quality: 91/100 | Created: 2025-12-30 """ import xml.etree.ElementTree as ET diff --git a/plots/icicle-basic/metadata/pygal.yaml b/plots/icicle-basic/metadata/pygal.yaml index 39eeb5e505..41c3c0bcb0 100644 --- a/plots/icicle-basic/metadata/pygal.yaml +++ b/plots/icicle-basic/metadata/pygal.yaml @@ -1,7 +1,7 @@ library: pygal specification_id: icicle-basic created: '2025-12-30T21:52:27Z' -updated: '2025-12-30T21:54:38Z' +updated: '2025-12-30T22:00:15Z' generated_by: claude-opus-4-5-20251101 workflow_run: 20606632269 issue: 0 @@ -10,21 +10,18 @@ library_version: 3.1.0 preview_url: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot_thumb.png preview_html: https://storage.googleapis.com/pyplots-images/plots/icicle-basic/pygal/plot.html -quality_score: 88 +quality_score: 91 review: strengths: - Excellent hierarchical visualization with clear parent-child relationships through spatial adjacency - - Smart label truncation that prevents overlap while keeping cells identifiable - - Colorblind-safe palette with distinct colors for each hierarchy level - - File system example provides intuitive, realistic context that demonstrates the - chart type well - - Proper top-to-bottom icicle layout matching the specification - - Good use of tooltips for full node information - - Level labels on the right side provide helpful context + - Smart color scheme differentiating hierarchy levels with colorblind-safe palette + - Good use of file system metaphor making the data immediately understandable + - Adaptive label truncation prevents overlap while maintaining readability + - Level labels on right side provide helpful context + - Clean legend placement at bottom weaknesses: - - Legend at bottom has spacing issue causing Subcategory and Item labels to appear - merged together - - Heavy reliance on manual SVG construction (xml.etree.ElementTree) rather than - pygal chart classes, since pygal lacks native icicle support - - Some very narrow cells (like Icon_1, Icon_2) are barely visible + - Manual SVG construction is necessary but makes code more complex than typical + pygal implementations + - Some leaf nodes have very narrow rectangles making labels hard to read (e.g., + Cov.., Th..)