diff --git a/plots/sankey-basic/implementations/python/seaborn.py b/plots/sankey-basic/implementations/python/seaborn.py index b71d61978a..baac4ff6a5 100644 --- a/plots/sankey-basic/implementations/python/seaborn.py +++ b/plots/sankey-basic/implementations/python/seaborn.py @@ -1,215 +1,173 @@ -""" pyplots.ai +""" anyplot.ai sankey-basic: Basic Sankey Diagram -Library: seaborn 0.13.2 | Python 3.13.11 -Quality: 78/100 | Created: 2025-12-23 +Library: seaborn 0.13.2 | Python 3.13.13 +Quality: 88/100 | Updated: 2026-04-30 """ -import matplotlib.patches as patches +import os + +import matplotlib.patches as mpatches import matplotlib.pyplot as plt import numpy as np import pandas as pd import seaborn as sns -# Set seed for reproducibility -np.random.seed(42) - -# Apply seaborn styling -sns.set_theme(style="white", context="talk", font_scale=1.2) - -# Data - Energy flow from sources to sectors (in TWh) -flows_data = { - "source": ["Coal", "Coal", "Coal", "Gas", "Gas", "Gas", "Nuclear", "Nuclear", "Nuclear"], - "target": [ - "Residential", - "Commercial", - "Industrial", - "Residential", - "Commercial", - "Industrial", - "Residential", - "Commercial", - "Industrial", - ], - "value": [15, 12, 33, 20, 18, 22, 15, 15, 15], -} -df = pd.DataFrame(flows_data) - -# Create figure with seaborn styling -fig, ax = plt.subplots(figsize=(16, 9)) - -# Use seaborn color palettes - distinct colors for sources and targets -source_names = df["source"].unique() -target_names = df["target"].unique() -source_palette = sns.color_palette("husl", n_colors=len(source_names)) -target_palette = sns.color_palette("Set2", n_colors=len(target_names)) -source_colors = dict(zip(source_names, source_palette, strict=True)) -target_colors = dict(zip(target_names, target_palette, strict=True)) - -# Calculate node totals -sources = df.groupby("source")["value"].sum().sort_values(ascending=False) -targets = df.groupby("target")["value"].sum().sort_values(ascending=False) - -# Node dimensions and positions -node_width = 0.06 -x_source = 0.12 -x_target = 0.88 -gap = 0.025 -total_height = 0.60 # Reduced to leave room for legends at bottom - -# Calculate source node positions (left side) -total_source = sources.sum() -source_positions = {} -y_pos = 0.92 -for source, value in sources.items(): - height = (value / total_source) * total_height - source_positions[source] = {"y": y_pos - height, "height": height} - y_pos -= height + gap - -# Calculate target node positions (right side) -total_target = targets.sum() -target_positions = {} -y_pos = 0.92 -for target, value in targets.items(): - height = (value / total_target) * total_height - target_positions[target] = {"y": y_pos - height, "height": height} - y_pos -= height + gap - -# Track current position for stacking flows at each node -source_current_y = {s: source_positions[s]["y"] + source_positions[s]["height"] for s in sources.index} -target_current_y = {t: target_positions[t]["y"] + target_positions[t]["height"] for t in targets.index} - -# Bezier curve parameters -n_points = 100 -t = np.linspace(0, 1, n_points) - -# Sort flows by source then by value for consistent stacking -df_sorted = df.sort_values(["source", "value"], ascending=[True, False]) - -# Draw flows with widths proportional to values +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" + +OKABE_ITO = ["#009E73", "#D55E00", "#0072B2", "#CC79A7", "#E69F00", "#56B4E9"] + +sns.set_theme(style="white", rc={"figure.facecolor": PAGE_BG, "axes.facecolor": PAGE_BG, "text.color": INK}) + +# Data — energy flows in TWh (varied magnitudes for clear proportional scaling) +source_names = ["Gas", "Coal", "Nuclear"] +target_names = ["Residential", "Industrial", "Commercial"] +flows = [ + ("Gas", "Residential", 50), + ("Gas", "Industrial", 30), + ("Gas", "Commercial", 40), + ("Coal", "Industrial", 45), + ("Coal", "Residential", 20), + ("Coal", "Commercial", 15), + ("Nuclear", "Residential", 25), + ("Nuclear", "Industrial", 10), + ("Nuclear", "Commercial", 10), +] +df = pd.DataFrame(flows, columns=["source", "target", "value"]) + +source_colors = dict(zip(source_names, OKABE_ITO[:3], strict=True)) +target_colors = dict(zip(target_names, OKABE_ITO[3:6], strict=True)) + +sources = df.groupby("source")["value"].sum().loc[source_names] +targets = df.groupby("target")["value"].sum().loc[target_names] + +# Layout +NODE_W = 0.055 +X_LEFT, X_RIGHT = 0.13, 0.87 +GAP = 0.022 +TOTAL_H = 0.72 +Y_START = 0.85 + +source_pos = {} +y = Y_START +for name in source_names: + h = (sources[name] / sources.sum()) * TOTAL_H + source_pos[name] = {"y": y - h, "h": h} + y -= h + GAP + +target_pos = {} +y = Y_START +for name in target_names: + h = (targets[name] / targets.sum()) * TOTAL_H + target_pos[name] = {"y": y - h, "h": h} + y -= h + GAP + +src_y = {n: source_pos[n]["y"] + source_pos[n]["h"] for n in source_names} +tgt_y = {n: target_pos[n]["y"] + target_pos[n]["h"] for n in target_names} + +# Figure +fig, ax = plt.subplots(figsize=(16, 9), facecolor=PAGE_BG) +ax.set_facecolor(PAGE_BG) + +t = np.linspace(0, 1, 120) +s = t**2 * (3 - 2 * t) # smoothstep: zero tangents at both endpoints + +# Sort flows by source order then target order to minimise crossings +src_ord = {n: i for i, n in enumerate(source_names)} +tgt_ord = {n: i for i, n in enumerate(target_names)} +df["_si"] = df["source"].map(src_ord) +df["_ti"] = df["target"].map(tgt_ord) +df_sorted = df.sort_values(["_si", "_ti"]) + +# Draw flows for _, row in df_sorted.iterrows(): - source = row["source"] - target = row["target"] - value = row["value"] - color = source_colors[source] - - # Calculate band height proportional to flow value - source_band_height = (value / sources[source]) * source_positions[source]["height"] - target_band_height = (value / targets[target]) * target_positions[target]["height"] - - # Source side coordinates - y0_top = source_current_y[source] - y0_bot = y0_top - source_band_height - source_current_y[source] = y0_bot - - # Target side coordinates - y1_top = target_current_y[target] - y1_bot = y1_top - target_band_height - target_current_y[target] = y1_bot - - # Draw the flow band using cubic bezier curves - x0 = x_source + node_width - x1 = x_target - cx0 = x0 + (x1 - x0) * 0.35 - cx1 = x0 + (x1 - x0) * 0.65 - - # Generate bezier curve points for top and bottom edges - top_x = (1 - t) ** 3 * x0 + 3 * (1 - t) ** 2 * t * cx0 + 3 * (1 - t) * t**2 * cx1 + t**3 * x1 - top_y = (1 - t) ** 3 * y0_top + 3 * (1 - t) ** 2 * t * y0_top + 3 * (1 - t) * t**2 * y1_top + t**3 * y1_top - bot_y = (1 - t) ** 3 * y0_bot + 3 * (1 - t) ** 2 * t * y0_bot + 3 * (1 - t) * t**2 * y1_bot + t**3 * y1_bot - - # Draw flow band - ax.fill_between(top_x, bot_y, top_y, color=color, alpha=0.65, linewidth=0, edgecolor="none") - -# Draw source nodes (left) with seaborn colors -for source in sources.index: - pos = source_positions[source] - rect = patches.FancyBboxPatch( - (x_source, pos["y"]), - node_width, - pos["height"], - boxstyle="round,pad=0.005,rounding_size=0.015", - facecolor=source_colors[source], - edgecolor="white", - linewidth=2.5, + src, tgt, val = row["source"], row["target"], row["value"] + bh_src = (val / sources[src]) * source_pos[src]["h"] + bh_tgt = (val / targets[tgt]) * target_pos[tgt]["h"] + + y0t, y0b = src_y[src], src_y[src] - bh_src + src_y[src] = y0b + y1t, y1b = tgt_y[tgt], tgt_y[tgt] - bh_tgt + tgt_y[tgt] = y1b + + x0, x1 = X_LEFT + NODE_W, X_RIGHT + cx0, cx1 = x0 + (x1 - x0) * 0.35, x0 + (x1 - x0) * 0.65 + xs = (1 - t) ** 3 * x0 + 3 * (1 - t) ** 2 * t * cx0 + 3 * (1 - t) * t**2 * cx1 + t**3 * x1 + + # Gas (dominant source) rendered with heavier alpha for visual emphasis + flow_alpha = 0.68 if src == "Gas" else 0.44 + ax.fill_between( + xs, y0b + (y1b - y0b) * s, y0t + (y1t - y0t) * s, color=source_colors[src], alpha=flow_alpha, linewidth=0 + ) + +# Draw source nodes and labels +for name in source_names: + pos = source_pos[name] + ax.add_patch( + mpatches.FancyBboxPatch( + (X_LEFT, pos["y"]), + NODE_W, + pos["h"], + boxstyle="round,pad=0.005,rounding_size=0.015", + facecolor=source_colors[name], + edgecolor=PAGE_BG, + linewidth=2, + ) ) - ax.add_patch(rect) ax.text( - x_source - 0.015, - pos["y"] + pos["height"] / 2, - f"{source}\n{sources[source]:.0f} TWh", + X_LEFT - 0.015, + pos["y"] + pos["h"] / 2, + f"{name}\n{sources[name]:.0f} TWh", ha="right", va="center", - fontsize=18, + fontsize=20, fontweight="bold", - color="#2d2d2d", + color=INK, ) -# Draw target nodes (right) with distinct colors from Set2 palette -for target in targets.index: - pos = target_positions[target] - rect = patches.FancyBboxPatch( - (x_target, pos["y"]), - node_width, - pos["height"], - boxstyle="round,pad=0.005,rounding_size=0.015", - facecolor=target_colors[target], - edgecolor="white", - linewidth=2.5, +# Draw target nodes and labels +for name in target_names: + pos = target_pos[name] + ax.add_patch( + mpatches.FancyBboxPatch( + (X_RIGHT, pos["y"]), + NODE_W, + pos["h"], + boxstyle="round,pad=0.005,rounding_size=0.015", + facecolor=target_colors[name], + edgecolor=PAGE_BG, + linewidth=2, + ) ) - ax.add_patch(rect) ax.text( - x_target + node_width + 0.015, - pos["y"] + pos["height"] / 2, - f"{target}\n{targets[target]:.0f} TWh", + X_RIGHT + NODE_W + 0.015, + pos["y"] + pos["h"] / 2, + f"{name}\n{targets[name]:.0f} TWh", ha="left", va="center", - fontsize=18, + fontsize=20, fontweight="bold", - color="#2d2d2d", + color=INK, ) -# Create legend using simple patches for sources and targets -source_handles = [ - patches.Patch(facecolor=source_colors[s], edgecolor="white", linewidth=1.5, label=s) for s in source_names -] -target_handles = [ - patches.Patch(facecolor=target_colors[t], edgecolor="white", linewidth=1.5, label=t) for t in target_names -] - -# Add source legend on the left -source_legend = ax.legend( - handles=source_handles, - title="Energy Sources", - loc="lower left", - bbox_to_anchor=(0.02, 0.02), - fontsize=14, - title_fontsize=16, - frameon=True, - fancybox=True, - edgecolor="#cccccc", -) - -# Add target legend on the right -ax.add_artist(source_legend) -ax.legend( - handles=target_handles, - title="Sectors", - loc="lower right", - bbox_to_anchor=(0.98, 0.02), - fontsize=14, - title_fontsize=16, - frameon=True, - fancybox=True, - edgecolor="#cccccc", +ax.set_title("sankey-basic · seaborn · anyplot.ai", fontsize=24, fontweight="medium", color=INK, pad=20) +# Subtitle highlighting key insight: Gas is the dominant source (49% of total) +ax.text( + 0.5, + 0.93, + "Gas supplies 49 % of total energy — the dominant source", + ha="center", + va="center", + fontsize=16, + color=source_colors["Gas"], + fontstyle="italic", ) - -# Set title using the required format -ax.set_title("sankey-basic · seaborn · pyplots.ai", fontsize=26, fontweight="bold", pad=25) - -# Set axis limits and remove decorations ax.set_xlim(0, 1) ax.set_ylim(0, 1) ax.axis("off") -plt.savefig("plot.png", dpi=300, bbox_inches="tight", facecolor="white") +plt.tight_layout() +plt.savefig(f"plot-{THEME}.png", dpi=300, bbox_inches="tight", facecolor=PAGE_BG) diff --git a/plots/sankey-basic/metadata/python/seaborn.yaml b/plots/sankey-basic/metadata/python/seaborn.yaml index 422287c416..7f279e0236 100644 --- a/plots/sankey-basic/metadata/python/seaborn.yaml +++ b/plots/sankey-basic/metadata/python/seaborn.yaml @@ -1,221 +1,254 @@ library: seaborn +language: python specification_id: sankey-basic created: '2025-12-23T19:44:36Z' -updated: '2025-12-23T20:24:06Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20469994035 -issue: 0 -python_version: 3.13.11 +updated: '2026-04-30T09:13:36Z' +generated_by: claude-sonnet +workflow_run: 25156180870 +issue: 810 +python_version: 3.13.13 library_version: 0.13.2 -preview_url: https://storage.googleapis.com/anyplot-images/plots/sankey-basic/seaborn/plot.png -preview_html: null -quality_score: 78 -impl_tags: - dependencies: [] - techniques: - - bezier-curves - - custom-legend - patterns: - - groupby-aggregation - dataprep: [] - styling: - - alpha-blending - - minimal-chrome +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/sankey-basic/python/seaborn/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/sankey-basic/python/seaborn/plot-dark.png +preview_html_light: null +preview_html_dark: null +quality_score: 88 review: strengths: - - Excellent manual implementation of Sankey diagram using bezier curves for smooth - flow bands - - Good use of seaborn color palettes (HUSL for sources, Set2 for targets) creating - visual distinction - - Clear node labels with energy values in TWh units provide context - - Clean code structure following KISS principles - - Proper handling of flow stacking at nodes prevents overlapping bands + - Perfect spec compliance and data quality — energy flow scenario is ideal for a + Sankey + - Custom bezier-curve Sankey from first principles with smoothstep interpolation + and rounded FancyBboxPatch nodes is visually polished + - Okabe-Ito palette applied correctly to both source and target node groups in canonical + order + - Theme-adaptive chrome works correctly in both renders — INK token controls all + text colors with no dark-on-dark failures + - Differential alpha (Gas=0.68 vs Coal/Nuclear=0.44) creates genuine visual hierarchy + highlighting the dominant source + - Code is clean, deterministic, and well-structured for a complex custom diagram + — no functions or classes weaknesses: - - Dual legends are redundant since node labels already display names - consider - removing legends or integrating flow percentages - - Limited seaborn-specific features used (only styling/palettes) since seaborn lacks - native Sankey support - - Source totals are too uniform (Coal=Gas=60 TWh) - more variety in magnitudes would - better demonstrate proportional scaling - image_description: The plot displays a Sankey diagram showing energy flow from three - sources (Coal, Gas, Nuclear) on the left to three sectors (Industrial, Residential, - Commercial) on the right. The title "sankey-basic · seaborn · pyplots.ai" is displayed - at the top in bold black text. Source nodes are colored pink (Coal), green (Gas), - and blue (Nuclear), each with labels showing the energy source name and total - TWh value. Target nodes are colored in softer pastel tones - teal (Residential), - light purple (Industrial), and orange (Commercial). Flow bands connect sources - to targets with smooth bezier curves, colored by source with 65% opacity. Two - legends at the bottom identify "Energy Sources" (left) and "Sectors" (right). - Node labels include values in TWh units. The overall layout is clean with a white - background. + - Library Mastery is minimal — seaborn is used only for sns.set_theme(); the entire + visualization is matplotlib primitives with no seaborn statistical API leveraged + (LM-02=1) + - Subtitle text at y=0.93 in data coordinates sits in a very narrow strip above + the Sankey and is subtle at fontsize=16, reducing storytelling impact + - Low-alpha flows (0.44) for Coal and Nuclear become somewhat muted in the dark + render's crossing areas, slightly reducing flow distinguishability + image_description: |- + Light render (plot-light.png): + Background: Warm off-white (#FAF8F1) — correct theme surface, not pure white + Chrome: Title "sankey-basic · seaborn · anyplot.ai" at fontsize=24 in dark ink (#1A1A17) — clearly readable. Node labels in dark bold text (fontsize=20) on left and right. Italic green subtitle in narrow strip above Sankey — present but subtle at fontsize=16. + Data: Gas node (#009E73, brand green), Coal (#D55E00), Nuclear (#0072B2); target nodes Residential (#CC79A7), Industrial (#E69F00), Commercial (#56B4E9). Flows as smooth cubic Bezier fills with alpha=0.68 (Gas) and alpha=0.44 (Coal/Nuclear). Node values shown in TWh. + Legibility verdict: PASS — all text readable, no light-on-light failures + + Dark render (plot-dark.png): + Background: Near-black (#1A1A17) — correct dark theme surface, not pure black + Chrome: Title in near-white/light color — clearly readable against dark background. Node labels use INK token (#F0EFE8) — white text on dark background, readable. No dark-on-dark text failures observed. + Data: Data colors identical to light render — Gas=#009E73, Coal=#D55E00, Nuclear=#0072B2, targets same Okabe-Ito colors. Low-alpha Coal/Nuclear flows are somewhat muted in crossing areas on the dark background but still distinguishable. Gas flows at alpha=0.68 remain prominent. + Legibility verdict: PASS — all text readable, theme chrome correctly adapted criteria_checklist: visual_quality: - score: 33 - max: 40 + score: 29 + max: 30 items: - id: VQ-01 name: Text Legibility - score: 9 - max: 10 + score: 8 + max: 8 passed: true - comment: all text clearly readable, good font sizes (18pt labels, 26pt title) + comment: Title 24pt, node labels 20pt bold, subtitle 16pt — all explicitly + set and readable in both themes - id: VQ-02 name: No Overlap - score: 8 - max: 8 + score: 6 + max: 6 passed: true - comment: no overlapping text elements, labels positioned cleanly + comment: No text collisions; node labels cleanly positioned left/right of + Sankey - id: VQ-03 name: Element Visibility - score: 7 - max: 8 + score: 5 + max: 6 passed: true - comment: flow bands clearly visible with good opacity (0.65), nodes well-sized + comment: Flows visible; Gas prominent at alpha=0.68; Coal/Nuclear at alpha=0.44 + somewhat muted in dark crossing areas - id: VQ-04 name: Color Accessibility - score: 3 - max: 5 + score: 2 + max: 2 passed: true - comment: uses husl and Set2 palettes; pink/green/blue source distinction is - good but pink (Coal) and orange (Commercial) could be confused by some colorblind - users + comment: Okabe-Ito CVD-safe palette, sufficient luminance variation across + all 6 node colors - id: VQ-05 - name: Layout Balance + name: Layout & Canvas score: 4 - max: 5 + max: 4 + passed: true + comment: Sankey fills ~70% of canvas, margins balanced, nothing cut off + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 passed: true - comment: good use of canvas space, legends positioned at bottom, slight imbalance - with more whitespace at bottom + comment: Node labels include units (TWh); axis off appropriate for Sankey + type - id: VQ-07 - name: Grid & Legend + name: Palette Compliance score: 2 max: 2 passed: true - comment: two well-placed legends with clear formatting + comment: 'Gas=#009E73 (first source); full Okabe-Ito order; backgrounds #FAF8F1/#1A1A17; + INK token adapts text correctly' + design_excellence: + score: 15 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 6 + max: 8 + passed: true + comment: Hand-crafted Bezier curves, rounded FancyBboxPatch nodes with background-colored + borders, differential alpha for visual emphasis — clearly above library + defaults + - id: DE-02 + name: Visual Refinement + score: 5 + max: 6 + passed: true + comment: axis('off') for minimal chrome, smoothstep Bezier for ultra-smooth + flows, no grid, rounded corners, node edge styling — very polished + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: true + comment: Higher alpha on Gas creates visual hierarchy; italic subtitle names + the key insight; Gas at top draws the eye first spec_compliance: - score: 23 - max: 25 + score: 15 + max: 15 items: - id: SC-01 name: Plot Type - score: 8 - max: 8 - passed: true - comment: correct Sankey diagram implementation - - id: SC-02 - name: Data Mapping score: 5 max: 5 passed: true - comment: sources, targets, and values correctly mapped - - id: SC-03 + comment: Correct Sankey with source-to-target flows, widths proportional to + values + - id: SC-02 name: Required Features - score: 5 - max: 5 + score: 4 + max: 4 passed: true - comment: flow widths proportional to values, distinct colors for sources - - id: SC-04 - name: Data Range + comment: 9 flows (within 5-50 spec), no circular flows, node labels visible, + distinct source colors, link opacity + - id: SC-03 + name: Data Mapping score: 3 max: 3 passed: true - comment: all nodes and flows visible - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: legends correctly identify sources and sectors - - id: SC-06 - name: Title Format - score: 0 - max: 2 - passed: true - comment: uses correct format "sankey-basic · seaborn · pyplots.ai" ✓ (Actually - correct, updating to 2/2) - - id: SC-06 - name: Title Format - score: 2 - max: 2 + comment: Sources left, targets right, node heights and flow widths proportional + to values + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 passed: true - comment: correctly uses "{spec-id} · {library} · pyplots.ai" format + comment: Title format 'sankey-basic · seaborn · anyplot.ai' correct; node + placement serves as implicit legend data_quality: - score: 17 - max: 20 + score: 15 + max: 15 items: - id: DQ-01 name: Feature Coverage score: 6 - max: 8 + max: 6 passed: true - comment: shows multiple sources and targets with varying flow magnitudes, - but all sources have equal total (60, 60, 45 TWh) - could show more variation + comment: All sources connect to all targets (9 flows), varied magnitudes, + flow crossings demonstrate full Sankey capability - id: DQ-02 name: Realistic Context - score: 7 - max: 7 + score: 5 + max: 5 passed: true - comment: energy flow scenario is realistic and comprehensible (coal, gas, - nuclear to residential/commercial/industrial) + comment: Energy flow scenario (Gas/Coal/Nuclear to Residential/Industrial/Commercial) + is classic, neutral, real-world - id: DQ-03 name: Appropriate Scale score: 4 - max: 5 + max: 4 passed: true - comment: TWh values are reasonable for energy flows, though relative proportions - are somewhat uniform + comment: Gas 120 TWh (49%), Coal 80 TWh (33%), Nuclear 45 TWh (18%); plausible + proportions for mixed-grid energy system code_quality: - score: 7 + score: 10 max: 10 items: - id: CQ-01 name: KISS Structure - score: 1 + score: 3 max: 3 - passed: false - comment: code is longer than typical KISS style but maintains linear structure - without classes; uses procedural approach which is acceptable + passed: true + comment: 'Linear flow: imports, tokens, data, layout geometry, figure, draw + flows, draw nodes, save — no functions or classes' - id: CQ-02 name: Reproducibility - score: 3 - max: 3 + score: 2 + max: 2 passed: true - comment: np.random.seed(42) set + comment: Fully deterministic — hardcoded data, no random values - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: all imports used + comment: 'All 6 imports used: os, mpatches, plt, np, pd, sns' - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 - passed: true - comment: no deprecated functions - - id: CQ-05 - name: Output Correct - score: 0 - max: 1 + name: Code Elegance + score: 2 + max: 2 passed: true - comment: saves as 'plot.png' ✓ (Actually correct) + comment: Pythonic, appropriate complexity for custom Sankey; smoothstep formula + is recognized technique; no fake UI - id: CQ-05 - name: Output Correct + name: Output & API score: 1 max: 1 passed: true - comment: correctly saves as 'plot.png' - library_features: - score: 0 - max: 5 + comment: Saves as plot-{THEME}.png with current API + library_mastery: + score: 4 + max: 10 items: - - id: LF-01 - name: Uses distinctive library features - score: 0 + - id: LM-01 + name: Idiomatic Usage + score: 3 + max: 5 + passed: true + comment: seaborn used correctly for sns.set_theme() theming; Sankey itself + is matplotlib primitives — seaborn statistical API unused + - id: LM-02 + name: Distinctive Features + score: 1 max: 5 passed: false - comment: seaborn is only used for styling (sns.set_theme, color_palette) but - no seaborn plot functions are called. The actual Sankey is built with matplotlib - patches and fill_between. This is a significant weakness as seaborn doesn't - natively support Sankey diagrams. + comment: seaborn used only for sns.set_theme(); no seaborn-specific visualization + features; could be replicated with plain matplotlib verdict: APPROVED +impl_tags: + dependencies: [] + techniques: + - bezier-curves + - patches + - annotations + patterns: + - groupby-aggregation + - iteration-over-groups + - explicit-figure + dataprep: [] + styling: + - minimal-chrome + - alpha-blending