From 17096807d6551e5233b8faa607ade34c4a9d1100 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Apr 2026 09:02:38 +0000 Subject: [PATCH 1/2] chore(altair): add metadata for network-force-directed --- .../implementations/python/altair.py | 149 ++++++------ .../metadata/python/altair.yaml | 229 ++---------------- 2 files changed, 86 insertions(+), 292 deletions(-) diff --git a/plots/network-force-directed/implementations/python/altair.py b/plots/network-force-directed/implementations/python/altair.py index c6d1f567ce..bf298f3e89 100644 --- a/plots/network-force-directed/implementations/python/altair.py +++ b/plots/network-force-directed/implementations/python/altair.py @@ -1,103 +1,88 @@ -""" pyplots.ai +"""anyplot.ai network-force-directed: Force-Directed Graph -Library: altair 6.0.0 | Python 3.13.11 -Quality: 91/100 | Created: 2025-12-23 +Library: altair | Python 3.13 +Quality: 91/100 | Updated: 2026-04-26 """ +import os + import altair as alt import numpy as np import pandas as pd -# Set seed for reproducibility -np.random.seed(42) +# Theme tokens +THEME = os.getenv("ANYPLOT_THEME", "light") +PAGE_BG = "#FAF8F1" if THEME == "light" else "#1A1A17" +ELEVATED_BG = "#FFFDF6" if THEME == "light" else "#242420" +INK = "#1A1A17" if THEME == "light" else "#F0EFE8" +INK_SOFT = "#4A4A44" if THEME == "light" else "#B8B7B0" +EDGE_COLOR = "#6B6A63" if THEME == "light" else "#A8A79F" -# Data: A social network with 50 nodes in 3 communities -# Demonstrates force-directed layout with clear community structure -nodes = [] -edges = [] +# Okabe-Ito categorical palette (first series is always #009E73) +OKABE_ITO = ["#009E73", "#D55E00", "#0072B2"] + +# Data: a 50-node organisational network with three communities +np.random.seed(42) -# Create 3 communities -community_sizes = [18, 17, 15] # Total: 50 nodes +community_sizes = [18, 17, 15] community_names = ["Engineering", "Marketing", "Sales"] -node_id = 0 +nodes = [] +node_id = 0 for comm_idx, size in enumerate(community_sizes): for _ in range(size): nodes.append({"id": node_id, "community": community_names[comm_idx]}) node_id += 1 -# Intra-community edges (dense connections within communities) -# Engineering: nodes 0-17 -for i in range(18): - for j in range(i + 1, 18): - if np.random.random() < 0.3: - edges.append((i, j)) - -# Marketing: nodes 18-34 -for i in range(18, 35): - for j in range(i + 1, 35): - if np.random.random() < 0.3: - edges.append((i, j)) - -# Sales: nodes 35-49 -for i in range(35, 50): - for j in range(i + 1, 50): - if np.random.random() < 0.3: - edges.append((i, j)) - -# Inter-community edges (sparse bridges between communities) -bridge_edges = [(0, 18), (5, 20), (10, 25), (18, 35), (22, 40), (30, 45), (8, 38), (15, 48)] -edges.extend(bridge_edges) - -# Force-directed layout algorithm (Fruchterman-Reingold) -n = len(nodes) -positions = np.random.rand(n, 2) * 2 - 1 # Initial random positions +edges = [] +# Intra-community edges (dense) +for start, end in [(0, 18), (18, 35), (35, 50)]: + for i in range(start, end): + for j in range(i + 1, end): + if np.random.random() < 0.3: + edges.append((i, j)) -# Optimal distance parameter +# Inter-community bridges (sparse) +edges.extend([(0, 18), (5, 20), (10, 25), (18, 35), (22, 40), (30, 45), (8, 38), (15, 48)]) + +# Fruchterman-Reingold force-directed layout +n = len(nodes) +positions = np.random.rand(n, 2) * 2 - 1 k = 0.5 iterations = 200 for iteration in range(iterations): displacement = np.zeros((n, 2)) - - # Repulsive forces between all node pairs (nodes push apart) for i in range(n): for j in range(i + 1, n): diff = positions[i] - positions[j] dist = max(np.linalg.norm(diff), 0.01) - repulsive_force = (k * k / dist) * (diff / dist) - displacement[i] += repulsive_force - displacement[j] -= repulsive_force - - # Attractive forces along edges (connected nodes pull together) + repulsive = (k * k / dist) * (diff / dist) + displacement[i] += repulsive + displacement[j] -= repulsive for src, tgt in edges: diff = positions[src] - positions[tgt] dist = max(np.linalg.norm(diff), 0.01) - attractive_force = (dist * dist / k) * (diff / dist) - displacement[src] -= attractive_force - displacement[tgt] += attractive_force - - # Apply displacement with cooling (decreasing temperature) + attractive = (dist * dist / k) * (diff / dist) + displacement[src] -= attractive + displacement[tgt] += attractive temperature = 1 - iteration / iterations for i in range(n): disp_norm = np.linalg.norm(displacement[i]) if disp_norm > 0: - # Limit movement by temperature positions[i] += (displacement[i] / disp_norm) * min(disp_norm, 0.15 * temperature) -# Normalize positions to [0.05, 0.95] range pos_min = positions.min(axis=0) pos_max = positions.max(axis=0) positions = (positions - pos_min) / (pos_max - pos_min + 1e-6) * 0.9 + 0.05 -# Calculate node degrees (number of connections) +# Node-level summary degrees = {node["id"]: 0 for node in nodes} for src, tgt in edges: degrees[src] += 1 degrees[tgt] += 1 -# Create node dataframe with positions and attributes node_df = pd.DataFrame( { "id": [node["id"] for node in nodes], @@ -107,67 +92,71 @@ "degree": [degrees[node["id"]] for node in nodes], } ) - -# Scale node size by degree for visualization node_df["size"] = node_df["degree"] * 30 + 200 -# Create edge dataframe for line segments +# Edge segments (long-form, two rows per edge) edge_data = [] for src, tgt in edges: edge_data.append({"edge_id": f"{src}-{tgt}", "x": positions[src][0], "y": positions[src][1], "order": 0}) edge_data.append({"edge_id": f"{src}-{tgt}", "x": positions[tgt][0], "y": positions[tgt][1], "order": 1}) edge_df = pd.DataFrame(edge_data) -# Community color mapping (Python Blue, Python Yellow, and colorblind-safe coral) -community_colors = ["#306998", "#FFD43B", "#FF6B6B"] +# Label only the four most-connected nodes to avoid clutter +hub_df = node_df.nlargest(4, "degree").copy() +hub_df["label"] = "Hub " + hub_df["id"].astype(str) -# Create edges layer +# Edges layer edges_chart = ( alt.Chart(edge_df) - .mark_line(strokeWidth=1.5, opacity=0.4) + .mark_line(strokeWidth=1.4, opacity=0.55) .encode( x=alt.X("x:Q", axis=None), y=alt.Y("y:Q", axis=None), detail="edge_id:N", order="order:O", - color=alt.value("#AAAAAA"), + color=alt.value(EDGE_COLOR), ) ) -# Create nodes layer +# Nodes layer nodes_chart = ( alt.Chart(node_df) - .mark_circle(stroke="#333333", strokeWidth=1.5, opacity=0.85) + .mark_circle(stroke=PAGE_BG, strokeWidth=1.5, opacity=0.95) .encode( x=alt.X("x:Q", axis=None), y=alt.Y("y:Q", axis=None), - size=alt.Size("size:Q", legend=None, scale=alt.Scale(range=[200, 800])), + size=alt.Size("size:Q", legend=None, scale=alt.Scale(range=[200, 900])), color=alt.Color( "community:N", - scale=alt.Scale(domain=["Engineering", "Marketing", "Sales"], range=community_colors), - legend=alt.Legend(title="Teams", titleFontSize=18, labelFontSize=16, symbolSize=400), + scale=alt.Scale(domain=community_names, range=OKABE_ITO), + legend=alt.Legend(title="Team", titleFontSize=18, labelFontSize=16, symbolSize=400), ), - tooltip=["community:N", "degree:Q"], + tooltip=[alt.Tooltip("community:N", title="Team"), alt.Tooltip("degree:Q", title="Connections")], ) ) -# Label high-degree nodes (hubs) -hub_df = node_df[node_df["degree"] >= 7].copy() -hub_df["label"] = "Hub" - +# Hub labels hub_labels = ( alt.Chart(hub_df) - .mark_text(fontSize=14, fontWeight="bold", color="#333333", dy=-15) + .mark_text(fontSize=15, fontWeight="bold", color=INK, dy=-22) .encode(x=alt.X("x:Q", axis=None), y=alt.Y("y:Q", axis=None), text="label:N") ) -# Combine all layers chart = ( (edges_chart + nodes_chart + hub_labels) - .properties(width=1600, height=900, title=alt.Title("network-force-directed · altair · pyplots.ai", fontSize=28)) - .configure_view(strokeWidth=0) + .properties( + width=1600, + height=900, + background=PAGE_BG, + title=alt.Title( + "network-force-directed · altair · anyplot.ai", fontSize=28, color=INK, anchor="start", offset=20 + ), + ) + .configure_view(fill=PAGE_BG, strokeWidth=0) + .configure_legend( + fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK, padding=12, cornerRadius=4 + ) ) -# Save as PNG (4800x2700 at scale_factor=3) and HTML -chart.save("plot.png", scale_factor=3.0) -chart.save("plot.html") +chart.save(f"plot-{THEME}.png", scale_factor=3.0) +chart.save(f"plot-{THEME}.html") diff --git a/plots/network-force-directed/metadata/python/altair.yaml b/plots/network-force-directed/metadata/python/altair.yaml index f5120196b4..81c6ce9e16 100644 --- a/plots/network-force-directed/metadata/python/altair.yaml +++ b/plots/network-force-directed/metadata/python/altair.yaml @@ -1,216 +1,21 @@ +# Per-library metadata for altair implementation of network-force-directed +# Auto-generated by impl-generate.yml + library: altair +language: python specification_id: network-force-directed created: '2025-12-23T15:14:29Z' -updated: '2025-12-23T15:22:20Z' -generated_by: claude-opus-4-5-20251101 -workflow_run: 20464358277 -issue: 0 -python_version: 3.13.11 -library_version: 6.0.0 -preview_url: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/altair/plot.png -preview_html: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/altair/plot.html -quality_score: 91 -impl_tags: - dependencies: [] - techniques: - - layer-composition - - hover-tooltips - - html-export - patterns: - - data-generation - dataprep: [] - styling: [] +updated: '2026-04-26T09:02:37Z' +generated_by: claude-opus +workflow_run: 24952745058 +issue: 990 +python_version: 3.14.4 +library_version: 6.1.0 +preview_url_light: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-light.png +preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-dark.png +preview_html_light: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-light.html +preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-dark.html +quality_score: null review: - strengths: - - Excellent implementation of Fruchterman-Reingold force-directed layout algorithm - with proper attractive/repulsive forces and cooling - - Clear community structure visualization with three well-separated clusters - - Good use of node size encoding to show degree (connection count) - - Colorblind-safe palette with strong differentiation between Engineering (blue), - Marketing (yellow), and Sales (coral) - - Proper use of Altair layered composition to combine edges, nodes, and labels - - Tooltips provide useful community and degree information on hover - weaknesses: - - Some Hub labels overlap in dense areas (particularly in the Engineering cluster) - - Edge opacity is quite low (0.4) making some connections difficult to trace visually - - Generates unnecessary plot.html file in addition to plot.png - image_description: 'The plot displays a force-directed network graph with 50 nodes - arranged in three distinct community clusters. The nodes are colored by team membership: - blue (Engineering) on the right side, yellow (Marketing) in the lower-left area, - and coral/salmon red (Sales) in the upper-left area. Gray edges connect nodes - within and between communities. Node sizes vary based on degree (number of connections), - with larger nodes having more connections. High-degree nodes are labeled "Hub" - in dark text above them. The title "network-force-directed · altair · pyplots.ai" - appears at the top. A legend labeled "Teams" appears in the upper-right corner - with color-coded entries for each community. The layout clearly reveals the three-community - structure with visible bridge connections between clusters.' - criteria_checklist: - visual_quality: - score: 36 - max: 40 - items: - - id: VQ-01 - name: Text Legibility - score: 9 - max: 10 - passed: true - comment: Title is large and readable (~28pt), legend labels are clear. Hub - labels are slightly small but legible. - - id: VQ-02 - name: No Overlap - score: 7 - max: 8 - passed: true - comment: Minor overlap of some Hub labels in the dense Engineering cluster - on the right side. - - id: VQ-03 - name: Element Visibility - score: 8 - max: 8 - passed: true - comment: Node sizes are well-adapted with visible variation by degree. Alpha - 0.85 provides good visibility with subtle transparency. - - id: VQ-04 - name: Color Accessibility - score: 5 - max: 5 - passed: true - comment: Blue, yellow, and coral are colorblind-safe and highly distinguishable. - Excellent contrast. - - id: VQ-05 - name: Layout Balance - score: 5 - max: 5 - passed: true - comment: Good canvas utilization, three clusters well-distributed across the - space. Plot fills ~60% of canvas. - - id: VQ-06 - name: Axis Labels - score: 2 - max: 2 - passed: true - comment: N/A for network graph (axes hidden appropriately). - - id: VQ-07 - name: Grid & Legend - score: 0 - max: 2 - passed: true - comment: 'Legend placed well in upper-right, but no grid needed for network - graphs. However, the edge color is very light (gray #AAAAAA with 0.4 opacity) - making some edges hard to trace.' - spec_compliance: - score: 25 - max: 25 - items: - - id: SC-01 - name: Plot Type - score: 8 - max: 8 - passed: true - comment: Correct force-directed graph visualization. - - id: SC-02 - name: Data Mapping - score: 5 - max: 5 - passed: true - comment: Node positions correctly computed via Fruchterman-Reingold algorithm. - - id: SC-03 - name: Required Features - score: 5 - max: 5 - passed: true - comment: Has nodes, edges, community coloring, node size by degree, hub labels. - - id: SC-04 - name: Data Range - score: 3 - max: 3 - passed: true - comment: All 50 nodes and edges visible within the canvas. - - id: SC-05 - name: Legend Accuracy - score: 2 - max: 2 - passed: true - comment: Legend correctly shows Engineering, Marketing, Sales with matching - colors. - - id: SC-06 - name: Title Format - score: 2 - max: 2 - passed: true - comment: Correctly uses "network-force-directed · altair · pyplots.ai" format. - data_quality: - score: 18 - max: 20 - items: - - id: DQ-01 - name: Feature Coverage - score: 7 - max: 8 - passed: true - comment: Shows communities, hub nodes, varying degrees, bridge connections. - Could have shown weighted edges for complete coverage. - - id: DQ-02 - name: Realistic Context - score: 7 - max: 7 - passed: true - comment: Social network with Engineering, Marketing, Sales teams is a realistic - organizational scenario. - - id: DQ-03 - name: Appropriate Scale - score: 4 - max: 5 - passed: true - comment: 50 nodes is appropriate. Intra-community edge probability of 0.3 - is reasonable. Bridge edges are manually defined but sparse, which is realistic. - code_quality: - score: 9 - max: 10 - items: - - id: CQ-01 - name: KISS Structure - score: 3 - max: 3 - passed: true - comment: 'Linear flow: imports → seed → data generation → force layout → Altair - chart → save.' - - id: CQ-02 - name: Reproducibility - score: 3 - max: 3 - passed: true - comment: Uses `np.random.seed(42)`. - - id: CQ-03 - name: Clean Imports - score: 2 - max: 2 - passed: true - comment: Only uses altair, numpy, pandas - all necessary. - - id: CQ-04 - name: No Deprecated API - score: 1 - max: 1 - passed: true - comment: Uses current Altair API. - - id: CQ-05 - name: Output Correct - score: 0 - max: 1 - passed: true - comment: Saves as plot.png (correct) but also saves plot.html. Minor deduction - for unnecessary output. - library_features: - score: 3 - max: 5 - items: - - id: LF-01 - name: Uses distinctive library features - score: 3 - max: 5 - passed: true - comment: Uses Altair's layered composition (edges + nodes + labels), mark_circle - with encoding, Color scale with domain/range, tooltips. However, doesn't - use Altair's more distinctive features like selections for interactivity - or compound marks. - verdict: APPROVED + strengths: [] + weaknesses: [] From 64c902d6b4c381bf8049679623f15d873da2bd9d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 26 Apr 2026 09:08:40 +0000 Subject: [PATCH 2/2] chore(altair): update quality score 85 and review feedback for network-force-directed --- .../implementations/python/altair.py | 6 +- .../metadata/python/altair.yaml | 240 +++++++++++++++++- 2 files changed, 236 insertions(+), 10 deletions(-) diff --git a/plots/network-force-directed/implementations/python/altair.py b/plots/network-force-directed/implementations/python/altair.py index bf298f3e89..e701365ceb 100644 --- a/plots/network-force-directed/implementations/python/altair.py +++ b/plots/network-force-directed/implementations/python/altair.py @@ -1,7 +1,7 @@ -"""anyplot.ai +""" anyplot.ai network-force-directed: Force-Directed Graph -Library: altair | Python 3.13 -Quality: 91/100 | Updated: 2026-04-26 +Library: altair 6.1.0 | Python 3.14.4 +Quality: 85/100 | Updated: 2026-04-26 """ import os diff --git a/plots/network-force-directed/metadata/python/altair.yaml b/plots/network-force-directed/metadata/python/altair.yaml index 81c6ce9e16..c43bb5d548 100644 --- a/plots/network-force-directed/metadata/python/altair.yaml +++ b/plots/network-force-directed/metadata/python/altair.yaml @@ -1,11 +1,8 @@ -# Per-library metadata for altair implementation of network-force-directed -# Auto-generated by impl-generate.yml - library: altair language: python specification_id: network-force-directed created: '2025-12-23T15:14:29Z' -updated: '2026-04-26T09:02:37Z' +updated: '2026-04-26T09:08:40Z' generated_by: claude-opus workflow_run: 24952745058 issue: 990 @@ -15,7 +12,236 @@ preview_url_light: https://storage.googleapis.com/anyplot-images/plots/network-f preview_url_dark: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-dark.png preview_html_light: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-light.html preview_html_dark: https://storage.googleapis.com/anyplot-images/plots/network-force-directed/python/altair/plot-dark.html -quality_score: null +quality_score: 85 review: - strengths: [] - weaknesses: [] + strengths: + - Implements complete Fruchterman-Reingold algorithm from scratch with temperature + cooling and 200 iterations + - 'Perfect spec compliance: 50-node organizational network, degree-scaled nodes, + hub labels, bridge edges' + - Both themes fully adaptive with all chrome elements switching correctly between + light and dark + - Idiomatic Altair layer composition using detail and order encodings for edge rendering + weaknesses: + - Hub label fontSize=15 is below the 18px minimum guideline for 4800x2700px output + - Hub 0, Hub 14, Hub 10 labels cluster in a tight area causing mild visual congestion + - Design storytelling lacks a clear focal point or emphasized insight beyond showing + community structure + - Altair interactive selection API unused - community click-highlight would strengthen + library-distinctive showcase + image_description: |- + Light render (plot-light.png): + Background: Warm off-white #FAF8F1 — correct theme surface + Chrome: Title "network-force-directed · altair · anyplot.ai" in dark #1A1A17 text at top-left (28px, readable); legend box in elevated #FFFDF6 background with INK_SOFT border; hub labels in dark INK color (15px, slightly small but readable) + Data: Three communities — Engineering #009E73 (teal), Marketing #D55E00 (vermillion), Sales #0072B2 (blue); nodes sized by degree (200-900 range); edges in #6B6A63 at 55% opacity; node strokes in page background color for definition + Legibility verdict: PASS (all text readable, hub labels slightly small at 15px) + + Dark render (plot-dark.png): + Background: Warm near-black #1A1A17 — correct dark theme surface + Chrome: Title in light #F0EFE8 text (readable against dark background); legend box in elevated #242420 background with INK_SOFT border; hub labels in #F0EFE8 (theme-adaptive INK for dark) — all readable; no dark-on-dark issues detected + Data: Colors identical to light render — Engineering #009E73, Marketing #D55E00, Sales #0072B2; edges in #A8A79F (lighter gray for dark theme); node strokes flip to dark page background + Legibility verdict: PASS (all text readable against dark surface; hub labels remain legible with correct INK token) + criteria_checklist: + visual_quality: + score: 26 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 6 + max: 8 + passed: true + comment: Title 28px and legend 16-18px explicitly set; hub annotation labels + at 15px below 18px guideline + - id: VQ-02 + name: No Overlap + score: 5 + max: 6 + passed: true + comment: Hub 0, Hub 14, Hub 10 labels cluster in tight area on right side; + readable but slightly crowded + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Nodes sized by degree with 200-900 scale range; edges at 55% opacity; + node strokes for definition; all elements clearly visible + - id: VQ-04 + name: Color Accessibility + score: 2 + max: 2 + passed: true + comment: Okabe-Ito palette, CVD-safe, three communities distinguishable without + relying on hue alone + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: true + comment: 4800x2700px canvas used well; FR physics places communities toward + edges leaving some central void + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Title format correct; no axis labels appropriate for network graph; + legend titled Team + - id: VQ-07 + name: Palette Compliance + score: 2 + max: 2 + passed: true + comment: 'Engineering #009E73 first series; Marketing #D55E00, Sales #0072B2 + follow Okabe-Ito order; backgrounds correct; both renders fully theme-correct' + design_excellence: + score: 12 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: true + comment: 'Above default: degree-based node sizing, page-bg node strokes, hub + labels, edge opacity; not at FiveThirtyEight publication level' + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Axes fully hidden, no unnecessary chrome, legend with rounded corners + and padding, edge opacity adds subtlety + - id: DE-03 + name: Data Storytelling + score: 3 + max: 6 + passed: true + comment: Community color-coding and hub labels direct attention but no singular + focal point or guiding narrative emphasis + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Fruchterman-Reingold physics simulation with repulsive/attractive + forces and temperature cooling + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: Node size by degree, community structure, bridge edges, hub labels + for top-4 nodes + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X/Y from FR layout, color encodes community, size encodes degree; + all 50 nodes visible + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct; legend labels match data communities + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows community structure, hub centrality, inter-community bridges, + degree variation, connection density differences + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Organizational network Engineering/Marketing/Sales - realistic, neutral, + comprehensible business scenario + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 50 nodes, ~140 intra-community edges at 30% density plus 8 bridge + edges; realistic for org/social network + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Flat script: imports -> data -> FR algorithm -> chart layers -> + save; no functions or classes' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: os, altair, numpy, pandas - all actively used + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean Pythonic code; FR algorithm inline with appropriate complexity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves plot-{THEME}.png and plot-{THEME}.html correctly + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: detail=edge_id:N for edge grouping, order=order:O for line direction, + layer composition via + operator - idiomatic Altair grammar-of-graphics + patterns + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: true + comment: detail encoding and declarative layer composition are library-specific; + tooltips and HTML export leverage interactivity; Altair selection API unused + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - layer-composition + - hover-tooltips + - html-export + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: [] + styling: + - alpha-blending + - minimal-chrome