From b88e9dbd1434c60a1237c61337e7de2b2e845c25 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 20:47:55 +0000 Subject: [PATCH 1/9] feat(plotnine): implement bode-basic --- plots/bode-basic/implementations/plotnine.py | 217 +++++++++++++++++++ 1 file changed, 217 insertions(+) create mode 100644 plots/bode-basic/implementations/plotnine.py diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py new file mode 100644 index 0000000000..df0ce3f4ec --- /dev/null +++ b/plots/bode-basic/implementations/plotnine.py @@ -0,0 +1,217 @@ +"""pyplots.ai +bode-basic: Bode Plot for Frequency Response +Library: plotnine | Python 3.13 +Quality: pending | Created: 2026-03-21 +""" + +import numpy as np +import pandas as pd +from plotnine import ( + aes, + element_blank, + element_line, + element_rect, + element_text, + facet_wrap, + geom_hline, + geom_line, + geom_point, + geom_text, + ggplot, + labs, + scale_x_log10, + theme, + theme_minimal, +) + + +# Data - Third-order open-loop transfer function: +# G(s) = 5 / [(s+1)(0.5s+1)(0.2s+1)] +# Poles at s = -1, -2, -5 — stable system with clear gain and phase margins +frequency_hz = np.logspace(-2, 2, 500) +omega = 2 * np.pi * frequency_hz +jw = 1j * omega +K = 5.0 +G = K / ((jw + 1) * (0.5 * jw + 1) * (0.2 * jw + 1)) + +magnitude_db = 20 * np.log10(np.abs(G)) +phase_deg = np.degrees(np.angle(G)) +phase_deg = np.unwrap(np.radians(phase_deg)) +phase_deg = np.degrees(phase_deg) + +# Gain crossover: where magnitude crosses 0 dB +gc_idx = np.argmin(np.abs(magnitude_db)) +gc_freq = frequency_hz[gc_idx] +phase_at_gc = phase_deg[gc_idx] +phase_margin = 180 + phase_at_gc + +# Phase crossover: where phase crosses -180 degrees +pc_idx = np.argmin(np.abs(phase_deg + 180)) +pc_freq = frequency_hz[pc_idx] +mag_at_pc = magnitude_db[pc_idx] +gain_margin = -mag_at_pc + +# Build long-format DataFrame for faceting +mag_df = pd.DataFrame({"frequency_hz": frequency_hz, "value": magnitude_db, "panel": "Magnitude (dB)"}) +phase_df = pd.DataFrame({"frequency_hz": frequency_hz, "value": phase_deg, "panel": "Phase (degrees)"}) +df = pd.concat([mag_df, phase_df], ignore_index=True) +df["panel"] = pd.Categorical(df["panel"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True) + +# Reference lines +ref_lines = pd.DataFrame( + { + "panel": pd.Categorical( + ["Magnitude (dB)", "Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True + ), + "yintercept": [0.0, -180.0], + } +) + +# Gain margin vertical segment at phase crossover on magnitude panel +gm_seg = pd.DataFrame( + { + "frequency_hz": [pc_freq, pc_freq], + "value": [0.0, mag_at_pc], + "panel": pd.Categorical( + ["Magnitude (dB)", "Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True + ), + } +) + +# Phase margin vertical segment at gain crossover on phase panel +pm_seg = pd.DataFrame( + { + "frequency_hz": [gc_freq, gc_freq], + "value": [-180.0, phase_at_gc], + "panel": pd.Categorical( + ["Phase (degrees)", "Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True + ), + } +) + +# Crossover marker points +gc_pt = pd.DataFrame( + { + "frequency_hz": [gc_freq], + "value": [0.0], + "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) +pm_pt = pd.DataFrame( + { + "frequency_hz": [gc_freq], + "value": [phase_at_gc], + "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) +pc_mag_pt = pd.DataFrame( + { + "frequency_hz": [pc_freq], + "value": [mag_at_pc], + "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) +pc_phase_pt = pd.DataFrame( + { + "frequency_hz": [pc_freq], + "value": [-180.0], + "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) + +# Colors +PYTHON_BLUE = "#306998" +GAIN_MARGIN_COLOR = "#E8833A" +PHASE_MARGIN_COLOR = "#9467BD" + +# Annotation labels (panel-specific to avoid appearing in both facets) +gm_label = pd.DataFrame( + { + "frequency_hz": [pc_freq * 3], + "value": [mag_at_pc / 2 - 5], + "label": [f"GM = {gain_margin:.1f} dB"], + "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) +pm_label = pd.DataFrame( + { + "frequency_hz": [gc_freq * 3], + "value": [(phase_at_gc + (-180)) / 2], + "label": [f"PM = {phase_margin:.0f}\u00b0"], + "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + } +) + +# Plot +plot = ( + ggplot(df, aes(x="frequency_hz", y="value")) + + geom_line(size=1.5, color=PYTHON_BLUE) + # Reference lines (0 dB and -180°) + + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#999999", size=0.7, alpha=0.7) + # Gain margin segment + + geom_line(gm_seg, aes(x="frequency_hz", y="value"), color=GAIN_MARGIN_COLOR, size=1.8, linetype="solid") + # Phase margin segment + + geom_line(pm_seg, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=1.8, linetype="solid") + # Gain crossover marker on magnitude panel + + geom_point(gc_pt, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=4.5, shape="o", stroke=1.8) + # Phase at gain crossover marker + + geom_point(pm_pt, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=4.5, shape="o", stroke=1.8) + # Phase crossover marker on magnitude panel + + geom_point( + pc_mag_pt, + aes(x="frequency_hz", y="value"), + color=GAIN_MARGIN_COLOR, + size=4.5, + shape="s", + stroke=1.8, + fill=GAIN_MARGIN_COLOR, + ) + # Phase crossover marker on phase panel + + geom_point( + pc_phase_pt, + aes(x="frequency_hz", y="value"), + color=GAIN_MARGIN_COLOR, + size=4.5, + shape="s", + stroke=1.8, + fill=GAIN_MARGIN_COLOR, + ) + # Gain margin annotation (magnitude panel only) + + geom_text( + gm_label, + aes(x="frequency_hz", y="value", label="label"), + color=GAIN_MARGIN_COLOR, + size=13, + fontweight="bold", + ha="left", + ) + # Phase margin annotation (phase panel only) + + geom_text( + pm_label, + aes(x="frequency_hz", y="value", label="label"), + color=PHASE_MARGIN_COLOR, + size=13, + fontweight="bold", + ha="left", + ) + + facet_wrap("~panel", ncol=1, scales="free_y") + + scale_x_log10() + + labs(x="Frequency (Hz)", y="", title="bode-basic · plotnine · pyplots.ai") + + theme_minimal() + + theme( + figure_size=(16, 10), + text=element_text(size=14, color="#2C3E50"), + axis_title=element_text(size=20), + axis_text=element_text(size=16, color="#546E7A"), + plot_title=element_text(size=24, weight="bold", ha="center"), + strip_text=element_text(size=20, weight="bold", color="#263238"), + strip_background=element_rect(fill="#F5F5F5", color="none"), + panel_grid_major=element_line(color="#ECEFF1", size=0.4), + panel_grid_minor=element_blank(), + panel_spacing_y=0.15, + plot_background=element_rect(fill="white", color="white"), + ) +) + +# Save +plot.save("plot.png", dpi=300, verbose=False) From e69d405d402e01e45e78d717e07c6af10397ad46 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 20:48:03 +0000 Subject: [PATCH 2/9] chore(plotnine): add metadata for bode-basic --- plots/bode-basic/metadata/plotnine.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 plots/bode-basic/metadata/plotnine.yaml diff --git a/plots/bode-basic/metadata/plotnine.yaml b/plots/bode-basic/metadata/plotnine.yaml new file mode 100644 index 0000000000..37d83e9b6d --- /dev/null +++ b/plots/bode-basic/metadata/plotnine.yaml @@ -0,0 +1,19 @@ +# Per-library metadata for plotnine implementation of bode-basic +# Auto-generated by impl-generate.yml + +library: plotnine +specification_id: bode-basic +created: '2026-03-21T20:48:02Z' +updated: '2026-03-21T20:48:02Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 23388395508 +issue: 4411 +python_version: 3.14.3 +library_version: 0.15.3 +preview_url: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot_thumb.png +preview_html: null +quality_score: null +review: + strengths: [] + weaknesses: [] From 9d9bce2504699a67bd888007dbbfea0c4696a4c5 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 20:52:39 +0000 Subject: [PATCH 3/9] chore(plotnine): update quality score 85 and review feedback for bode-basic --- plots/bode-basic/implementations/plotnine.py | 6 +- plots/bode-basic/metadata/plotnine.yaml | 210 ++++++++++++++++++- 2 files changed, 206 insertions(+), 10 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index df0ce3f4ec..876a11165a 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai bode-basic: Bode Plot for Frequency Response -Library: plotnine | Python 3.13 -Quality: pending | Created: 2026-03-21 +Library: plotnine 0.15.3 | Python 3.14.3 +Quality: 85/100 | Created: 2026-03-21 """ import numpy as np diff --git a/plots/bode-basic/metadata/plotnine.yaml b/plots/bode-basic/metadata/plotnine.yaml index 37d83e9b6d..814e543269 100644 --- a/plots/bode-basic/metadata/plotnine.yaml +++ b/plots/bode-basic/metadata/plotnine.yaml @@ -1,10 +1,7 @@ -# Per-library metadata for plotnine implementation of bode-basic -# Auto-generated by impl-generate.yml - library: plotnine specification_id: bode-basic created: '2026-03-21T20:48:02Z' -updated: '2026-03-21T20:48:02Z' +updated: '2026-03-21T20:52:39Z' generated_by: claude-opus-4-5-20251101 workflow_run: 23388395508 issue: 4411 @@ -13,7 +10,206 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot_thumb.png preview_html: null -quality_score: null +quality_score: 85 review: - strengths: [] - weaknesses: [] + strengths: + - Full spec compliance with all required Bode plot features (dual panels, margins, + reference lines, log scale) + - Effective color-coded annotation system distinguishing gain margin (orange) and + phase margin (purple) + - Deterministic analytical data from a realistic third-order transfer function + - Idiomatic plotnine usage with facet_wrap for dual-panel layout + - Explicit font sizing for all text elements + weaknesses: + - Gain margin and phase margin vertical segments are barely visible as margin indicators + - Magnitude panel has excessive unused vertical space below the curve + - Transfer function does not show resonance peak mentioned in spec + - Many small DataFrames for annotations add code verbosity + image_description: The plot displays a two-panel Bode diagram for a third-order + transfer function. The top panel shows Magnitude (dB) vs Frequency (Hz) on a logarithmic + x-axis, with the blue response curve starting at ~14 dB and rolling off to about + -175 dB. The bottom panel shows Phase (degrees) vs Frequency (Hz), with the phase + starting near 0° and decreasing to approximately -270°. Dashed gray reference + lines are drawn at 0 dB and -180°. Orange square markers indicate the phase crossover + point, and purple circle markers indicate the gain crossover point. Annotations + read "GM = 8.1 dB" in orange and "PM = 32°" in purple. The title reads "bode-basic + · plotnine · pyplots.ai" centered at the top. The background is white with subtle + light gray major grid lines. The facet strip labels "Magnitude (dB)" and "Phase + (degrees)" have a light gray background. + criteria_checklist: + visual_quality: + score: 27 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title 24pt, axis_title 20pt, axis_text + 16pt, strip_text 20pt' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text elements + - id: VQ-03 + name: Element Visibility + score: 4 + max: 6 + passed: false + comment: Gain/phase margin vertical segments barely visible + - id: VQ-04 + name: Color Accessibility + score: 4 + max: 4 + passed: true + comment: Blue, orange, purple palette is colorblind-safe + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: false + comment: Magnitude panel has excessive unused vertical space + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Frequency (Hz) with units, facet strips provide y-labels + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: false + comment: Custom palette and typography, above defaults but not publication-level + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: theme_minimal, subtle grid, custom strip styling + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: false + comment: Color-coded margin annotations create visual hierarchy + spec_compliance: + score: 15 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct dual-panel Bode plot + - id: SC-02 + name: Required Features + score: 4 + max: 4 + passed: true + comment: 'All features present: dual panels, margins, reference lines, log + scale, grid' + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: Correct frequency vs magnitude/phase mapping + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct, no legend needed + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 5 + max: 6 + passed: false + comment: Shows margins and rolloff but no resonance peak + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Realistic third-order control system transfer function + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Realistic stability margins and frequency range + code_quality: + score: 9 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Linear imports-data-plot-save structure + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic analytical computation + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports used + - id: CQ-04 + name: Code Elegance + score: 1 + max: 2 + passed: false + comment: Verbose with many small DataFrames for panel-specific annotations + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot.png with dpi=300 + library_mastery: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: false + comment: Good grammar of graphics usage with facet_wrap for dual panels + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: facet_wrap with free_y scales and Categorical ordering are plotnine-distinctive + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - faceting + - annotations + - layer-composition + patterns: + - data-generation + dataprep: [] + styling: + - grid-styling From dd8f388a6d1680d99713023a8d9046f7ecb1cff1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:05:15 +0000 Subject: [PATCH 4/9] fix(plotnine): address review feedback for bode-basic Attempt 1/3 - fixes based on AI review --- plots/bode-basic/implementations/plotnine.py | 208 ++++++++----------- 1 file changed, 89 insertions(+), 119 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index 876a11165a..6fad041701 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 Quality: 85/100 | Created: 2026-03-21 @@ -16,7 +16,9 @@ geom_hline, geom_line, geom_point, + geom_segment, geom_text, + geom_vline, ggplot, labs, scale_x_log10, @@ -28,16 +30,13 @@ # Data - Third-order open-loop transfer function: # G(s) = 5 / [(s+1)(0.5s+1)(0.2s+1)] # Poles at s = -1, -2, -5 — stable system with clear gain and phase margins -frequency_hz = np.logspace(-2, 2, 500) +frequency_hz = np.logspace(-1, 1, 500) omega = 2 * np.pi * frequency_hz jw = 1j * omega -K = 5.0 -G = K / ((jw + 1) * (0.5 * jw + 1) * (0.2 * jw + 1)) +G = 5.0 / ((jw + 1) * (0.5 * jw + 1) * (0.2 * jw + 1)) magnitude_db = 20 * np.log10(np.abs(G)) -phase_deg = np.degrees(np.angle(G)) -phase_deg = np.unwrap(np.radians(phase_deg)) -phase_deg = np.degrees(phase_deg) +phase_deg = np.degrees(np.unwrap(np.angle(G))) # Gain crossover: where magnitude crosses 0 dB gc_idx = np.argmin(np.abs(magnitude_db)) @@ -51,165 +50,136 @@ mag_at_pc = magnitude_db[pc_idx] gain_margin = -mag_at_pc -# Build long-format DataFrame for faceting -mag_df = pd.DataFrame({"frequency_hz": frequency_hz, "value": magnitude_db, "panel": "Magnitude (dB)"}) -phase_df = pd.DataFrame({"frequency_hz": frequency_hz, "value": phase_deg, "panel": "Phase (degrees)"}) -df = pd.concat([mag_df, phase_df], ignore_index=True) -df["panel"] = pd.Categorical(df["panel"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True) +# Panel categories +panels = ["Magnitude (dB)", "Phase (degrees)"] +panel_cat = pd.CategoricalDtype(categories=panels, ordered=True) -# Reference lines -ref_lines = pd.DataFrame( - { - "panel": pd.Categorical( - ["Magnitude (dB)", "Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True - ), - "yintercept": [0.0, -180.0], - } +# Long-format data for faceted plot +df = pd.concat( + [ + pd.DataFrame({"freq": frequency_hz, "value": magnitude_db, "panel": "Magnitude (dB)"}), + pd.DataFrame({"freq": frequency_hz, "value": phase_deg, "panel": "Phase (degrees)"}), + ], + ignore_index=True, ) +df["panel"] = df["panel"].astype(panel_cat) -# Gain margin vertical segment at phase crossover on magnitude panel +# Reference lines: 0 dB and -180° +ref_lines = pd.DataFrame({"panel": pd.Categorical(panels, dtype=panel_cat), "yintercept": [0.0, -180.0]}) + +# Margin segments and crossover markers gm_seg = pd.DataFrame( + {"x": [pc_freq], "ymin": [mag_at_pc], "ymax": [0.0], "panel": pd.Categorical(["Magnitude (dB)"], dtype=panel_cat)} +) +pm_seg = pd.DataFrame( { - "frequency_hz": [pc_freq, pc_freq], - "value": [0.0, mag_at_pc], - "panel": pd.Categorical( - ["Magnitude (dB)", "Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True - ), + "x": [gc_freq], + "ymin": [-180.0], + "ymax": [phase_at_gc], + "panel": pd.Categorical(["Phase (degrees)"], dtype=panel_cat), } ) -# Phase margin vertical segment at gain crossover on phase panel -pm_seg = pd.DataFrame( +markers = pd.DataFrame( { - "frequency_hz": [gc_freq, gc_freq], - "value": [-180.0, phase_at_gc], + "freq": [gc_freq, gc_freq, pc_freq, pc_freq], + "value": [0.0, phase_at_gc, mag_at_pc, -180.0], "panel": pd.Categorical( - ["Phase (degrees)", "Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True + ["Magnitude (dB)", "Phase (degrees)", "Magnitude (dB)", "Phase (degrees)"], dtype=panel_cat ), + "mtype": ["gc", "gc", "pc", "pc"], } ) -# Crossover marker points -gc_pt = pd.DataFrame( - { - "frequency_hz": [gc_freq], - "value": [0.0], - "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), - } -) -pm_pt = pd.DataFrame( - { - "frequency_hz": [gc_freq], - "value": [phase_at_gc], - "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), - } -) -pc_mag_pt = pd.DataFrame( +# Annotation labels positioned to the right of margin segments +gm_label = pd.DataFrame( { - "frequency_hz": [pc_freq], - "value": [mag_at_pc], - "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + "freq": [pc_freq * 2.2], + "value": [mag_at_pc / 2], + "label": [f"GM = {gain_margin:.1f} dB"], + "panel": pd.Categorical(["Magnitude (dB)"], dtype=panel_cat), } ) -pc_phase_pt = pd.DataFrame( +pm_label = pd.DataFrame( { - "frequency_hz": [pc_freq], - "value": [-180.0], - "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + "freq": [gc_freq * 2.2], + "value": [(phase_at_gc - 180) / 2], + "label": [f"PM = {phase_margin:.0f}°"], + "panel": pd.Categorical(["Phase (degrees)"], dtype=panel_cat), } ) # Colors PYTHON_BLUE = "#306998" -GAIN_MARGIN_COLOR = "#E8833A" -PHASE_MARGIN_COLOR = "#9467BD" +GM_COLOR = "#D35400" +PM_COLOR = "#7D3C98" -# Annotation labels (panel-specific to avoid appearing in both facets) -gm_label = pd.DataFrame( - { - "frequency_hz": [pc_freq * 3], - "value": [mag_at_pc / 2 - 5], - "label": [f"GM = {gain_margin:.1f} dB"], - "panel": pd.Categorical(["Magnitude (dB)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), - } -) -pm_label = pd.DataFrame( +# Subtle vertical guides at crossover frequencies +guides = pd.DataFrame( { - "frequency_hz": [gc_freq * 3], - "value": [(phase_at_gc + (-180)) / 2], - "label": [f"PM = {phase_margin:.0f}\u00b0"], - "panel": pd.Categorical(["Phase (degrees)"], categories=["Magnitude (dB)", "Phase (degrees)"], ordered=True), + "xintercept": [gc_freq, gc_freq, pc_freq, pc_freq], + "panel": pd.Categorical( + ["Magnitude (dB)", "Phase (degrees)", "Magnitude (dB)", "Phase (degrees)"], dtype=panel_cat + ), } ) # Plot plot = ( - ggplot(df, aes(x="frequency_hz", y="value")) - + geom_line(size=1.5, color=PYTHON_BLUE) - # Reference lines (0 dB and -180°) - + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#999999", size=0.7, alpha=0.7) + ggplot(df, aes(x="freq", y="value")) + + geom_line(size=2.2, color=PYTHON_BLUE, alpha=0.9) + # Reference lines + + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#90A4AE", size=0.8) + # Crossover guide lines + + geom_vline(guides, aes(xintercept="xintercept"), linetype="dotted", color="#B0BEC5", size=0.5) # Gain margin segment - + geom_line(gm_seg, aes(x="frequency_hz", y="value"), color=GAIN_MARGIN_COLOR, size=1.8, linetype="solid") + + geom_segment(gm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=GM_COLOR, size=4.0, alpha=0.85) # Phase margin segment - + geom_line(pm_seg, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=1.8, linetype="solid") - # Gain crossover marker on magnitude panel - + geom_point(gc_pt, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=4.5, shape="o", stroke=1.8) - # Phase at gain crossover marker - + geom_point(pm_pt, aes(x="frequency_hz", y="value"), color=PHASE_MARGIN_COLOR, size=4.5, shape="o", stroke=1.8) - # Phase crossover marker on magnitude panel + + geom_segment(pm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=PM_COLOR, size=4.0, alpha=0.85) + # Gain crossover markers (purple circles) + geom_point( - pc_mag_pt, - aes(x="frequency_hz", y="value"), - color=GAIN_MARGIN_COLOR, - size=4.5, - shape="s", - stroke=1.8, - fill=GAIN_MARGIN_COLOR, + markers[markers["mtype"] == "gc"], + aes(x="freq", y="value"), + color=PM_COLOR, + fill=PM_COLOR, + size=6, + shape="o", + stroke=2, ) - # Phase crossover marker on phase panel + # Phase crossover markers (orange squares) + geom_point( - pc_phase_pt, - aes(x="frequency_hz", y="value"), - color=GAIN_MARGIN_COLOR, - size=4.5, + markers[markers["mtype"] == "pc"], + aes(x="freq", y="value"), + color=GM_COLOR, + fill=GM_COLOR, + size=6, shape="s", - stroke=1.8, - fill=GAIN_MARGIN_COLOR, + stroke=2, ) - # Gain margin annotation (magnitude panel only) + # Annotations + geom_text( - gm_label, - aes(x="frequency_hz", y="value", label="label"), - color=GAIN_MARGIN_COLOR, - size=13, - fontweight="bold", - ha="left", + gm_label, aes(x="freq", y="value", label="label"), color=GM_COLOR, size=14, fontweight="bold", ha="left" ) - # Phase margin annotation (phase panel only) + geom_text( - pm_label, - aes(x="frequency_hz", y="value", label="label"), - color=PHASE_MARGIN_COLOR, - size=13, - fontweight="bold", - ha="left", + pm_label, aes(x="freq", y="value", label="label"), color=PM_COLOR, size=14, fontweight="bold", ha="left" ) + facet_wrap("~panel", ncol=1, scales="free_y") - + scale_x_log10() + + scale_x_log10(breaks=[0.1, 1, 10], labels=["0.1", "1", "10"]) + labs(x="Frequency (Hz)", y="", title="bode-basic · plotnine · pyplots.ai") + theme_minimal() + theme( - figure_size=(16, 10), - text=element_text(size=14, color="#2C3E50"), - axis_title=element_text(size=20), + figure_size=(12, 12), + text=element_text(size=14, color="#263238"), + axis_title=element_text(size=20, color="#37474F"), axis_text=element_text(size=16, color="#546E7A"), - plot_title=element_text(size=24, weight="bold", ha="center"), - strip_text=element_text(size=20, weight="bold", color="#263238"), - strip_background=element_rect(fill="#F5F5F5", color="none"), - panel_grid_major=element_line(color="#ECEFF1", size=0.4), + plot_title=element_text(size=24, weight="bold", ha="center", color="#1A237E"), + strip_text=element_text(size=20, weight="bold", color="#1A237E"), + strip_background=element_rect(fill="#E8EAF6", color="none"), + panel_grid_major=element_line(color="#E0E0E0", size=0.3), panel_grid_minor=element_blank(), - panel_spacing_y=0.15, - plot_background=element_rect(fill="white", color="white"), + panel_spacing_y=0.3, + plot_background=element_rect(fill="#FAFAFA", color="#FAFAFA"), + panel_background=element_rect(fill="white", color="none"), ) ) From 483d2b64baf8e78401ea0893c4ddb4622ecbaf29 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:10:00 +0000 Subject: [PATCH 5/9] chore(plotnine): update quality score 86 and review feedback for bode-basic --- plots/bode-basic/implementations/plotnine.py | 4 +- plots/bode-basic/metadata/plotnine.yaml | 153 +++++++++++-------- 2 files changed, 92 insertions(+), 65 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index 6fad041701..fecb10bcb7 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 85/100 | Created: 2026-03-21 +Quality: 86/100 | Created: 2026-03-21 """ import numpy as np diff --git a/plots/bode-basic/metadata/plotnine.yaml b/plots/bode-basic/metadata/plotnine.yaml index 814e543269..aaceaa15ab 100644 --- a/plots/bode-basic/metadata/plotnine.yaml +++ b/plots/bode-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: bode-basic created: '2026-03-21T20:48:02Z' -updated: '2026-03-21T20:52:39Z' +updated: '2026-03-21T21:09:59Z' generated_by: claude-opus-4-5-20251101 workflow_run: 23388395508 issue: 4411 @@ -10,32 +10,38 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 85 +quality_score: 86 review: strengths: - - Full spec compliance with all required Bode plot features (dual panels, margins, - reference lines, log scale) - - Effective color-coded annotation system distinguishing gain margin (orange) and - phase margin (purple) - - Deterministic analytical data from a realistic third-order transfer function - - Idiomatic plotnine usage with facet_wrap for dual-panel layout - - Explicit font sizing for all text elements + - Excellent spec compliance — all required features (dual panels, margin annotations, + reference lines, log axis, grid) present and correct + - Clean, well-organized code with deterministic data from a realistic transfer function + - Good use of plotnine grammar of graphics with facet_wrap for dual-panel layout + - Effective color-coded annotations (orange for gain margin, purple for phase margin) + with distinct marker shapes + - Thoughtful theme customization with custom text color hierarchy and subtle grid weaknesses: - - Gain margin and phase margin vertical segments are barely visible as margin indicators - - Magnitude panel has excessive unused vertical space below the curve - - Transfer function does not show resonance peak mentioned in spec - - Many small DataFrames for annotations add code verbosity - image_description: The plot displays a two-panel Bode diagram for a third-order - transfer function. The top panel shows Magnitude (dB) vs Frequency (Hz) on a logarithmic - x-axis, with the blue response curve starting at ~14 dB and rolling off to about - -175 dB. The bottom panel shows Phase (degrees) vs Frequency (Hz), with the phase - starting near 0° and decreasing to approximately -270°. Dashed gray reference - lines are drawn at 0 dB and -180°. Orange square markers indicate the phase crossover - point, and purple circle markers indicate the gain crossover point. Annotations - read "GM = 8.1 dB" in orange and "PM = 32°" in purple. The title reads "bode-basic - · plotnine · pyplots.ai" centered at the top. The background is white with subtle - light gray major grid lines. The facet strip labels "Magnitude (dB)" and "Phase - (degrees)" have a light gray background. + - Annotation text (geom_text size=14) slightly small relative to axis text (16) + — should be at least as large + - Magnitude panel has excessive vertical range extending to -70 dB when interesting + region is above -20 dB + - Could use wider frequency range to show full passband and asymptotic rolloff + - Not leveraging scale_y_continuous for custom axis formatting + image_description: The plot displays a Bode plot with two vertically stacked panels + on a light gray (#FAFAFA) background. The top panel is labeled "Magnitude (dB)" + with a light blue/purple strip header and shows a blue (#306998) curve starting + at ~+10 dB at 0.1 Hz, crossing 0 dB around 0.4 Hz, and declining to ~-70 dB at + 10 Hz. A thick orange (#D35400) vertical segment marks the gain margin between + the magnitude at phase crossover (~-8 dB) and the 0 dB reference line, with an + orange "GM = 8.1 dB" label. A purple circle marks the gain crossover point, and + an orange square marks the phase crossover point on this panel. The bottom panel + labeled "Phase (degrees)" shows the phase curve starting at ~-55° and declining + to ~-260°. A thick purple (#7D3C98) vertical segment marks the phase margin between + the phase at gain crossover (~-148°) and the -180° reference line, with a purple + "PM = 32°" label. Dashed horizontal reference lines appear at 0 dB and -180°. + Dotted vertical guide lines mark both crossover frequencies across both panels. + The x-axis is logarithmic labeled "Frequency (Hz)" with ticks at 0.1, 1, and 10. + Title reads "bode-basic · plotnine · pyplots.ai" in dark navy bold text. criteria_checklist: visual_quality: score: 27 @@ -43,41 +49,47 @@ review: items: - id: VQ-01 name: Text Legibility - score: 8 + score: 7 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis_title 20pt, axis_text - 16pt, strip_text 20pt' + comment: All font sizes explicitly set (title=24, axis_title=20, axis_text=16, + strip_text=20). Annotation text at size=14 is slightly small relative to + other elements. - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No overlapping text elements + comment: No text collisions. Annotations positioned cleanly to the right of + margin segments. - id: VQ-03 name: Element Visibility - score: 4 + score: 5 max: 6 - passed: false - comment: Gain/phase margin vertical segments barely visible + passed: true + comment: Main line well-sized (size=2.2). Markers visible (size=6). Gain margin + segment is quite short making it less prominent. - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Blue, orange, purple palette is colorblind-safe + comment: Blue, orange, and purple are distinguishable for all forms of color + vision deficiency. Good contrast. - id: VQ-05 name: Layout & Canvas score: 3 max: 4 - passed: false - comment: Magnitude panel has excessive unused vertical space + passed: true + comment: Square 3600x3600 format appropriate. Magnitude panel has significant + unused vertical space extending to -70 dB. - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Frequency (Hz) with units, facet strips provide y-labels + comment: Frequency (Hz) with units, strip text serves as y-axis labels with + units. design_excellence: score: 13 max: 20 @@ -86,20 +98,24 @@ review: name: Aesthetic Sophistication score: 5 max: 8 - passed: false - comment: Custom palette and typography, above defaults but not publication-level + passed: true + comment: Custom color palette, custom text color hierarchy, custom strip backgrounds. + Above defaults but not publication-showcase level. - id: DE-02 name: Visual Refinement score: 4 max: 6 - passed: false - comment: theme_minimal, subtle grid, custom strip styling + passed: true + comment: theme_minimal removes spines, subtle major grid, minor grid removed, + custom strip styling, panel spacing configured. - id: DE-03 name: Data Storytelling score: 4 max: 6 - passed: false - comment: Color-coded margin annotations create visual hierarchy + passed: true + comment: Gain and phase margins visually highlighted with colored segments + and bold labels creating clear focal points. Different marker shapes distinguish + crossover types. spec_compliance: score: 15 max: 15 @@ -109,26 +125,28 @@ review: score: 5 max: 5 passed: true - comment: Correct dual-panel Bode plot + comment: Correct dual-panel Bode plot with magnitude on top, phase on bottom. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All features present: dual panels, margins, reference lines, log - scale, grid' + comment: 'All spec features present: dual-panel, gain/phase margin annotations, + reference lines, log axis, grid.' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Correct frequency vs magnitude/phase mapping + comment: Frequency on log x-axis, magnitude in dB on top, phase in degrees + on bottom. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct, no legend needed + comment: Title follows required format. No legend needed for single-series + with annotations. data_quality: score: 14 max: 15 @@ -137,22 +155,26 @@ review: name: Feature Coverage score: 5 max: 6 - passed: false - comment: Shows margins and rolloff but no resonance peak + passed: true + comment: Shows third-order system with gain/phase margins and crossover points. + Frequency range captures dynamics but is narrower than ideal for showing + full passband. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Realistic third-order control system transfer function + comment: Realistic control systems transfer function with plausible stability + margins. - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Realistic stability margins and frequency range + comment: Values physically appropriate. GM=8.1 dB and PM=32 degrees are realistic + engineering values. code_quality: - score: 9 + score: 10 max: 10 items: - id: CQ-01 @@ -160,31 +182,33 @@ review: score: 3 max: 3 passed: true - comment: Linear imports-data-plot-save structure + comment: 'Linear flow: imports, data generation, DataFrame construction, plot, + save.' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic analytical computation + comment: Fully deterministic analytic transfer function. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used + comment: All imports used in implementation. - id: CQ-04 name: Code Elegance - score: 1 + score: 2 max: 2 - passed: false - comment: Verbose with many small DataFrames for panel-specific annotations + passed: true + comment: Clean, well-organized. Multiple DataFrames necessary for plotnine + grammar. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with dpi=300 + comment: Saves as plot.png with dpi=300 using current API. library_mastery: score: 7 max: 10 @@ -193,23 +217,26 @@ review: name: Idiomatic Usage score: 4 max: 5 - passed: false - comment: Good grammar of graphics usage with facet_wrap for dual panels + passed: true + comment: 'Good ggplot grammar usage: aes, geom layers, facet_wrap with free_y, + scale_x_log10, CategoricalDtype for panel ordering.' - id: LM-02 name: Distinctive Features score: 3 max: 5 - passed: false - comment: facet_wrap with free_y scales and Categorical ordering are plotnine-distinctive + passed: true + comment: facet_wrap with free_y for dual panels is distinctively ggplot. Long-format + data with ordered categoricals is idiomatic. verdict: REJECTED impl_tags: dependencies: [] techniques: - - faceting - annotations + - faceting - layer-composition patterns: - data-generation dataprep: [] styling: - grid-styling + - alpha-blending From c189d005c33c090d95868ad271bac7fc3fdae63a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:17:11 +0000 Subject: [PATCH 6/9] fix(plotnine): address review feedback for bode-basic Attempt 2/3 - fixes based on AI review --- plots/bode-basic/implementations/plotnine.py | 58 +++++++++++--------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index fecb10bcb7..f3adf05fdf 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,14 +1,12 @@ -""" pyplots.ai +"""pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 86/100 | Created: 2026-03-21 """ import numpy as np import pandas as pd from plotnine import ( aes, - element_blank, element_line, element_rect, element_text, @@ -22,6 +20,7 @@ ggplot, labs, scale_x_log10, + scale_y_continuous, theme, theme_minimal, ) @@ -30,7 +29,7 @@ # Data - Third-order open-loop transfer function: # G(s) = 5 / [(s+1)(0.5s+1)(0.2s+1)] # Poles at s = -1, -2, -5 — stable system with clear gain and phase margins -frequency_hz = np.logspace(-1, 1, 500) +frequency_hz = np.logspace(-1.5, 1.5, 600) omega = 2 * np.pi * frequency_hz jw = 1j * omega G = 5.0 / ((jw + 1) * (0.5 * jw + 1) * (0.2 * jw + 1)) @@ -50,6 +49,9 @@ mag_at_pc = magnitude_db[pc_idx] gain_margin = -mag_at_pc +# Clip magnitude for display: show passband and rolloff without excessive range +mag_display = np.clip(magnitude_db, -30, None) + # Panel categories panels = ["Magnitude (dB)", "Phase (degrees)"] panel_cat = pd.CategoricalDtype(categories=panels, ordered=True) @@ -57,7 +59,7 @@ # Long-format data for faceted plot df = pd.concat( [ - pd.DataFrame({"freq": frequency_hz, "value": magnitude_db, "panel": "Magnitude (dB)"}), + pd.DataFrame({"freq": frequency_hz, "value": mag_display, "panel": "Magnitude (dB)"}), pd.DataFrame({"freq": frequency_hz, "value": phase_deg, "panel": "Phase (degrees)"}), ], ignore_index=True, @@ -94,7 +96,7 @@ # Annotation labels positioned to the right of margin segments gm_label = pd.DataFrame( { - "freq": [pc_freq * 2.2], + "freq": [pc_freq * 2.0], "value": [mag_at_pc / 2], "label": [f"GM = {gain_margin:.1f} dB"], "panel": pd.Categorical(["Magnitude (dB)"], dtype=panel_cat), @@ -102,7 +104,7 @@ ) pm_label = pd.DataFrame( { - "freq": [gc_freq * 2.2], + "freq": [gc_freq * 2.0], "value": [(phase_at_gc - 180) / 2], "label": [f"PM = {phase_margin:.0f}°"], "panel": pd.Categorical(["Phase (degrees)"], dtype=panel_cat), @@ -113,6 +115,9 @@ PYTHON_BLUE = "#306998" GM_COLOR = "#D35400" PM_COLOR = "#7D3C98" +DARK_TEXT = "#1A237E" +MID_TEXT = "#37474F" +LIGHT_TEXT = "#546E7A" # Subtle vertical guides at crossover frequencies guides = pd.DataFrame( @@ -127,24 +132,24 @@ # Plot plot = ( ggplot(df, aes(x="freq", y="value")) - + geom_line(size=2.2, color=PYTHON_BLUE, alpha=0.9) + + geom_line(size=2.5, color=PYTHON_BLUE, alpha=0.92) # Reference lines - + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#90A4AE", size=0.8) + + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#78909C", size=1.0) # Crossover guide lines - + geom_vline(guides, aes(xintercept="xintercept"), linetype="dotted", color="#B0BEC5", size=0.5) + + geom_vline(guides, aes(xintercept="xintercept"), linetype="dotted", color="#B0BEC5", size=0.6) # Gain margin segment - + geom_segment(gm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=GM_COLOR, size=4.0, alpha=0.85) + + geom_segment(gm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=GM_COLOR, size=5.0, alpha=0.9) # Phase margin segment - + geom_segment(pm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=PM_COLOR, size=4.0, alpha=0.85) + + geom_segment(pm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=PM_COLOR, size=5.0, alpha=0.9) # Gain crossover markers (purple circles) + geom_point( markers[markers["mtype"] == "gc"], aes(x="freq", y="value"), color=PM_COLOR, fill=PM_COLOR, - size=6, + size=7, shape="o", - stroke=2, + stroke=2.5, ) # Phase crossover markers (orange squares) + geom_point( @@ -152,31 +157,34 @@ aes(x="freq", y="value"), color=GM_COLOR, fill=GM_COLOR, - size=6, + size=7, shape="s", - stroke=2, + stroke=2.5, ) # Annotations + geom_text( - gm_label, aes(x="freq", y="value", label="label"), color=GM_COLOR, size=14, fontweight="bold", ha="left" + gm_label, aes(x="freq", y="value", label="label"), color=GM_COLOR, size=18, fontweight="bold", ha="left" ) + geom_text( - pm_label, aes(x="freq", y="value", label="label"), color=PM_COLOR, size=14, fontweight="bold", ha="left" + pm_label, aes(x="freq", y="value", label="label"), color=PM_COLOR, size=18, fontweight="bold", ha="left" ) + facet_wrap("~panel", ncol=1, scales="free_y") - + scale_x_log10(breaks=[0.1, 1, 10], labels=["0.1", "1", "10"]) + + scale_x_log10( + breaks=[0.1, 1, 10], labels=["0.1", "1", "10"], minor_breaks=[0.03, 0.05, 0.2, 0.3, 0.5, 2, 3, 5, 20, 30] + ) + + scale_y_continuous(labels=lambda lst: [f"{v:.0f}" for v in lst]) + labs(x="Frequency (Hz)", y="", title="bode-basic · plotnine · pyplots.ai") + theme_minimal() + theme( figure_size=(12, 12), - text=element_text(size=14, color="#263238"), - axis_title=element_text(size=20, color="#37474F"), - axis_text=element_text(size=16, color="#546E7A"), - plot_title=element_text(size=24, weight="bold", ha="center", color="#1A237E"), - strip_text=element_text(size=20, weight="bold", color="#1A237E"), + text=element_text(size=14, color=MID_TEXT), + axis_title=element_text(size=20, color=MID_TEXT), + axis_text=element_text(size=16, color=LIGHT_TEXT), + plot_title=element_text(size=24, weight="bold", ha="center", color=DARK_TEXT), + strip_text=element_text(size=20, weight="bold", color=DARK_TEXT), strip_background=element_rect(fill="#E8EAF6", color="none"), panel_grid_major=element_line(color="#E0E0E0", size=0.3), - panel_grid_minor=element_blank(), + panel_grid_minor=element_line(color="#F0F0F0", size=0.15), panel_spacing_y=0.3, plot_background=element_rect(fill="#FAFAFA", color="#FAFAFA"), panel_background=element_rect(fill="white", color="none"), From 06b80a20bc1e17d723ee9266556df7c0773fafac Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:22:26 +0000 Subject: [PATCH 7/9] chore(plotnine): update quality score 89 and review feedback for bode-basic --- plots/bode-basic/implementations/plotnine.py | 3 +- plots/bode-basic/metadata/plotnine.yaml | 145 +++++++++---------- 2 files changed, 67 insertions(+), 81 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index f3adf05fdf..4b9f8cff96 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,6 +1,7 @@ -"""pyplots.ai +""" pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 +Quality: 89/100 | Created: 2026-03-21 """ import numpy as np diff --git a/plots/bode-basic/metadata/plotnine.yaml b/plots/bode-basic/metadata/plotnine.yaml index aaceaa15ab..26d6acdfc5 100644 --- a/plots/bode-basic/metadata/plotnine.yaml +++ b/plots/bode-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: bode-basic created: '2026-03-21T20:48:02Z' -updated: '2026-03-21T21:09:59Z' +updated: '2026-03-21T21:22:25Z' generated_by: claude-opus-4-5-20251101 workflow_run: 23388395508 issue: 4411 @@ -10,112 +10,104 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 86 +quality_score: 89 review: strengths: - - Excellent spec compliance — all required features (dual panels, margin annotations, - reference lines, log axis, grid) present and correct - - Clean, well-organized code with deterministic data from a realistic transfer function - - Good use of plotnine grammar of graphics with facet_wrap for dual-panel layout - - Effective color-coded annotations (orange for gain margin, purple for phase margin) - with distinct marker shapes - - Thoughtful theme customization with custom text color hierarchy and subtle grid + - Full spec compliance with all required Bode plot features (dual panel, GM/PM, + reference lines, log scale) + - 'Excellent color coding system: Python Blue for response curve, orange for gain + margin, purple for phase margin' + - Strong text legibility with all font sizes explicitly set + - Clean, deterministic code with realistic transfer function data + - Good use of plotnine faceting for dual-panel layout weaknesses: - - Annotation text (geom_text size=14) slightly small relative to axis text (16) - — should be at least as large - - Magnitude panel has excessive vertical range extending to -70 dB when interesting - region is above -20 dB - - Could use wider frequency range to show full passband and asymptotic rolloff - - Not leveraging scale_y_continuous for custom axis formatting - image_description: The plot displays a Bode plot with two vertically stacked panels - on a light gray (#FAFAFA) background. The top panel is labeled "Magnitude (dB)" - with a light blue/purple strip header and shows a blue (#306998) curve starting - at ~+10 dB at 0.1 Hz, crossing 0 dB around 0.4 Hz, and declining to ~-70 dB at - 10 Hz. A thick orange (#D35400) vertical segment marks the gain margin between - the magnitude at phase crossover (~-8 dB) and the 0 dB reference line, with an - orange "GM = 8.1 dB" label. A purple circle marks the gain crossover point, and - an orange square marks the phase crossover point on this panel. The bottom panel - labeled "Phase (degrees)" shows the phase curve starting at ~-55° and declining - to ~-260°. A thick purple (#7D3C98) vertical segment marks the phase margin between - the phase at gain crossover (~-148°) and the -180° reference line, with a purple - "PM = 32°" label. Dashed horizontal reference lines appear at 0 dB and -180°. - Dotted vertical guide lines mark both crossover frequencies across both panels. - The x-axis is logarithmic labeled "Frequency (Hz)" with ticks at 0.1, 1, and 10. - Title reads "bode-basic · plotnine · pyplots.ai" in dark navy bold text. + - Magnitude clipped at -30 dB creates an artificial flat line at bottom of top panel + - Square figure format (12x12) does not optimally serve the logarithmic frequency + axis + - 'Visual refinement good but not fully polished (DE-02: 4/6)' + image_description: The plot displays a dual-panel Bode plot with "bode-basic · plotnine + · pyplots.ai" as the title in dark navy bold text. The top panel is labeled "Magnitude + (dB)" with a light indigo strip background, showing a blue frequency response + curve starting at ~14 dB at 0.03 Hz, rolling off through 0 dB and flattening at + -30 dB (clipped). The bottom panel is labeled "Phase (degrees)" with the same + strip styling, showing phase rolling from ~-40° down past -250°. Both panels share + a logarithmic frequency axis labeled "Frequency (Hz)" with ticks at 0.1, 1, and + 10. A dashed gray reference line marks 0 dB in the magnitude panel and -180° in + the phase panel. Dotted vertical guide lines mark the gain and phase crossover + frequencies. An orange vertical segment shows the gain margin (~8 dB) with a bold + orange "GM = 8.0 dB" label. A purple vertical segment shows the phase margin (~33°) + with a bold purple "PM = 33°" label. Purple circles mark gain crossover points, + orange squares mark phase crossover points. The background is light gray (#FAFAFA) + with white panel interiors and subtle two-level grid lines. criteria_checklist: visual_quality: - score: 27 + score: 29 max: 30 items: - id: VQ-01 name: Text Legibility - score: 7 + score: 8 max: 8 passed: true - comment: All font sizes explicitly set (title=24, axis_title=20, axis_text=16, - strip_text=20). Annotation text at size=14 is slightly small relative to - other elements. + comment: 'All font sizes explicitly set: title 24pt, axis_title 20pt, axis_text + 16pt, strip_text 20pt' - id: VQ-02 name: No Overlap score: 6 max: 6 passed: true - comment: No text collisions. Annotations positioned cleanly to the right of - margin segments. + comment: All text and markers clearly separated and readable - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Main line well-sized (size=2.2). Markers visible (size=6). Gain margin - segment is quite short making it less prominent. + comment: Line thickness 2.5, markers size 7, margin segments size 5.0 — all + well-adapted - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Blue, orange, and purple are distinguishable for all forms of color - vision deficiency. Good contrast. + comment: Blue/orange/purple palette is colorblind-safe with strong contrast - id: VQ-05 name: Layout & Canvas score: 3 max: 4 passed: true - comment: Square 3600x3600 format appropriate. Magnitude panel has significant - unused vertical space extending to -70 dB. + comment: Square format works but landscape would better serve log frequency + axis; magnitude clipped at -30 dB - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Frequency (Hz) with units, strip text serves as y-axis labels with - units. + comment: Frequency (Hz) with units; panel strips serve as y-labels with units design_excellence: - score: 13 + score: 14 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 passed: true - comment: Custom color palette, custom text color hierarchy, custom strip backgrounds. - Above defaults but not publication-showcase level. + comment: Custom color palette, custom backgrounds, intentional color coding, + good typography hierarchy - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: theme_minimal removes spines, subtle major grid, minor grid removed, - custom strip styling, panel spacing configured. + comment: theme_minimal, subtle two-level grid, custom backgrounds, generous + panel spacing — good but not every detail polished - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Gain and phase margins visually highlighted with colored segments - and bold labels creating clear focal points. Different marker shapes distinguish - crossover types. + comment: Color-coded GM/PM with distinct marker shapes create clear focal + points and visual hierarchy spec_compliance: score: 15 max: 15 @@ -125,28 +117,26 @@ review: score: 5 max: 5 passed: true - comment: Correct dual-panel Bode plot with magnitude on top, phase on bottom. + comment: Correct dual-panel Bode plot with magnitude on top and phase on bottom - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All spec features present: dual-panel, gain/phase margin annotations, - reference lines, log axis, grid.' + comment: 'All features present: dual-panel, GM/PM annotations, reference lines, + log scale, grid' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: Frequency on log x-axis, magnitude in dB on top, phase in degrees - on bottom. + comment: Frequency on log x, magnitude in dB, phase in degrees - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title follows required format. No legend needed for single-series - with annotations. + comment: Title format correct; no legend needed for single-series with annotations data_quality: score: 14 max: 15 @@ -156,23 +146,20 @@ review: score: 5 max: 6 passed: true - comment: Shows third-order system with gain/phase margins and crossover points. - Frequency range captures dynamics but is narrower than ideal for showing - full passband. + comment: Shows gain/phase rolloff, margins, crossover frequencies; could show + more prominent resonance peak - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Realistic control systems transfer function with plausible stability - margins. + comment: Third-order transfer function is a realistic control systems scenario - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Values physically appropriate. GM=8.1 dB and PM=32 degrees are realistic - engineering values. + comment: Realistic frequency range, magnitude, and phase values code_quality: score: 10 max: 10 @@ -182,33 +169,31 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports, data generation, DataFrame construction, plot, - save.' + comment: 'Linear flow: imports → data → plot → save' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic analytic transfer function. + comment: Fully deterministic transfer function computation - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used in implementation. + comment: All imports used - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean, well-organized. Multiple DataFrames necessary for plotnine - grammar. + comment: Clean, well-structured code with appropriate complexity - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with dpi=300 using current API. + comment: Saves as plot.png with dpi=300 library_mastery: score: 7 max: 10 @@ -218,15 +203,15 @@ review: score: 4 max: 5 passed: true - comment: 'Good ggplot grammar usage: aes, geom layers, facet_wrap with free_y, - scale_x_log10, CategoricalDtype for panel ordering.' + comment: 'Good use of ggplot grammar: aes, geom layers, facet_wrap, scales, + theme' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: facet_wrap with free_y for dual panels is distinctively ggplot. Long-format - data with ordered categoricals is idiomatic. + comment: facet_wrap with free_y scales, CategoricalDtype ordering, layer composition + — distinctive to grammar-of-graphics verdict: REJECTED impl_tags: dependencies: [] From a89d0d38c139c13e801b65e42ebac83afd52328f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:32:47 +0000 Subject: [PATCH 8/9] fix(plotnine): address review feedback for bode-basic Attempt 3/3 - fixes based on AI review --- plots/bode-basic/implementations/plotnine.py | 25 +++++++++++--------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index 4b9f8cff96..335f4aad7e 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 Quality: 89/100 | Created: 2026-03-21 @@ -50,8 +50,10 @@ mag_at_pc = magnitude_db[pc_idx] gain_margin = -mag_at_pc -# Clip magnitude for display: show passband and rolloff without excessive range -mag_display = np.clip(magnitude_db, -30, None) +# Limit magnitude display to relevant range (above -50 dB) to avoid +# compressing the interesting region around 0 dB +freq_mag = frequency_hz[magnitude_db >= -50] +mag_display = magnitude_db[magnitude_db >= -50] # Panel categories panels = ["Magnitude (dB)", "Phase (degrees)"] @@ -60,7 +62,7 @@ # Long-format data for faceted plot df = pd.concat( [ - pd.DataFrame({"freq": frequency_hz, "value": mag_display, "panel": "Magnitude (dB)"}), + pd.DataFrame({"freq": freq_mag, "value": mag_display, "panel": "Magnitude (dB)"}), pd.DataFrame({"freq": frequency_hz, "value": phase_deg, "panel": "Phase (degrees)"}), ], ignore_index=True, @@ -130,14 +132,14 @@ } ) -# Plot +# Plot — landscape format for optimal log-frequency axis display plot = ( ggplot(df, aes(x="freq", y="value")) + geom_line(size=2.5, color=PYTHON_BLUE, alpha=0.92) # Reference lines - + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#78909C", size=1.0) + + geom_hline(ref_lines, aes(yintercept="yintercept"), linetype="dashed", color="#90A4AE", size=0.8) # Crossover guide lines - + geom_vline(guides, aes(xintercept="xintercept"), linetype="dotted", color="#B0BEC5", size=0.6) + + geom_vline(guides, aes(xintercept="xintercept"), linetype="dotted", color="#B0BEC5", size=0.5) # Gain margin segment + geom_segment(gm_seg, aes(x="x", xend="x", y="ymin", yend="ymax"), color=GM_COLOR, size=5.0, alpha=0.9) # Phase margin segment @@ -177,16 +179,17 @@ + labs(x="Frequency (Hz)", y="", title="bode-basic · plotnine · pyplots.ai") + theme_minimal() + theme( - figure_size=(12, 12), + figure_size=(16, 9), text=element_text(size=14, color=MID_TEXT), axis_title=element_text(size=20, color=MID_TEXT), axis_text=element_text(size=16, color=LIGHT_TEXT), + axis_ticks=element_line(color="#CFD8DC", size=0.4), plot_title=element_text(size=24, weight="bold", ha="center", color=DARK_TEXT), strip_text=element_text(size=20, weight="bold", color=DARK_TEXT), strip_background=element_rect(fill="#E8EAF6", color="none"), - panel_grid_major=element_line(color="#E0E0E0", size=0.3), - panel_grid_minor=element_line(color="#F0F0F0", size=0.15), - panel_spacing_y=0.3, + panel_grid_major=element_line(color="#E0E0E0", size=0.25), + panel_grid_minor=element_line(color="#F5F5F5", size=0.12), + panel_spacing_y=0.35, plot_background=element_rect(fill="#FAFAFA", color="#FAFAFA"), panel_background=element_rect(fill="white", color="none"), ) From 5048448ed49f0c0a5f507ad06bb3d3a47f338efd Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 21 Mar 2026 21:37:56 +0000 Subject: [PATCH 9/9] chore(plotnine): update quality score 81 and review feedback for bode-basic --- plots/bode-basic/implementations/plotnine.py | 4 +- plots/bode-basic/metadata/plotnine.yaml | 147 ++++++++++--------- 2 files changed, 81 insertions(+), 70 deletions(-) diff --git a/plots/bode-basic/implementations/plotnine.py b/plots/bode-basic/implementations/plotnine.py index 335f4aad7e..9c9a9d7aef 100644 --- a/plots/bode-basic/implementations/plotnine.py +++ b/plots/bode-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai bode-basic: Bode Plot for Frequency Response Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 89/100 | Created: 2026-03-21 +Quality: 81/100 | Created: 2026-03-21 """ import numpy as np diff --git a/plots/bode-basic/metadata/plotnine.yaml b/plots/bode-basic/metadata/plotnine.yaml index 26d6acdfc5..83b4f4ab71 100644 --- a/plots/bode-basic/metadata/plotnine.yaml +++ b/plots/bode-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: bode-basic created: '2026-03-21T20:48:02Z' -updated: '2026-03-21T21:22:25Z' +updated: '2026-03-21T21:37:55Z' generated_by: claude-opus-4-5-20251101 workflow_run: 23388395508 issue: 4411 @@ -10,79 +10,86 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/bode-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 89 +quality_score: 81 review: strengths: - - Full spec compliance with all required Bode plot features (dual panel, GM/PM, - reference lines, log scale) - - 'Excellent color coding system: Python Blue for response curve, orange for gain - margin, purple for phase margin' - - Strong text legibility with all font sizes explicitly set - - Clean, deterministic code with realistic transfer function data - - Good use of plotnine faceting for dual-panel layout + - Excellent data storytelling with clearly annotated gain and phase margins using + distinct colors and marker shapes + - Strong aesthetic sophistication with custom color palette, themed backgrounds, + and refined grid styling + - Correct title format and complete spec compliance with all required features + - Clean, well-structured code with deterministic data generation + - Good use of plotnine grammar of graphics with faceted layout and layer composition weaknesses: - - Magnitude clipped at -30 dB creates an artificial flat line at bottom of top panel - - Square figure format (12x12) does not optimally serve the logarithmic frequency - axis - - 'Visual refinement good but not fully polished (DE-02: 4/6)' - image_description: The plot displays a dual-panel Bode plot with "bode-basic · plotnine - · pyplots.ai" as the title in dark navy bold text. The top panel is labeled "Magnitude - (dB)" with a light indigo strip background, showing a blue frequency response - curve starting at ~14 dB at 0.03 Hz, rolling off through 0 dB and flattening at - -30 dB (clipped). The bottom panel is labeled "Phase (degrees)" with the same - strip styling, showing phase rolling from ~-40° down past -250°. Both panels share - a logarithmic frequency axis labeled "Frequency (Hz)" with ticks at 0.1, 1, and - 10. A dashed gray reference line marks 0 dB in the magnitude panel and -180° in - the phase panel. Dotted vertical guide lines mark the gain and phase crossover - frequencies. An orange vertical segment shows the gain margin (~8 dB) with a bold - orange "GM = 8.0 dB" label. A purple vertical segment shows the phase margin (~33°) - with a bold purple "PM = 33°" label. Purple circles mark gain crossover points, - orange squares mark phase crossover points. The background is light gray (#FAFAFA) - with white panel interiors and subtle two-level grid lines. + - Severe layout imbalance between magnitude and phase panels — magnitude panel has + massive empty white space while phase panel is extremely compressed + - Phase panel crowding causes markers, annotations, and reference lines to cluster + together + - The facet_wrap equal-height allocation does not suit this plot where panels need + different vertical proportions + image_description: The plot shows a dual-panel Bode plot with the title "bode-basic + · plotnine · pyplots.ai" centered at the top in dark blue bold text. The top panel + is labeled "Magnitude (dB)" with a light lavender strip background, showing a + blue line (#306998) starting at approximately +14 dB at low frequencies, staying + relatively flat until about 0.3 Hz, then rolling off steeply to about -50 dB at + the right edge. The bottom panel is labeled "Phase (degrees)" with the same strip + styling, showing the phase curve starting near -30° and rolling off to approximately + -270°. Reference lines are drawn as dashed light gray at 0 dB and -180°. An orange + vertical segment marks the gain margin (GM = 8.0 dB) at the phase crossover frequency + (~0.5 Hz) with orange square markers, and a purple vertical segment marks the + phase margin (PM = 33°) at the gain crossover frequency with purple circle markers. + Dotted vertical guide lines indicate crossover frequencies in both panels. The + background is light gray (#FAFAFA) with white panel backgrounds and subtle grid + lines. The major layout issue is that the magnitude panel occupies roughly 65% + of the figure height with vast empty white space below the data, while the phase + panel is extremely compressed at the bottom, making its elements crowded and hard + to distinguish. criteria_checklist: visual_quality: - score: 29 + score: 22 max: 30 items: - id: VQ-01 name: Text Legibility - score: 8 + score: 7 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis_title 20pt, axis_text - 16pt, strip_text 20pt' + comment: All font sizes explicitly set (title=24, axis_title=20, axis_text=16, + strip=20, annotations=18). Readable throughout. - id: VQ-02 name: No Overlap - score: 6 + score: 4 max: 6 passed: true - comment: All text and markers clearly separated and readable + comment: Minor crowding in phase panel where tick labels and PM annotation + are close together. - id: VQ-03 name: Element Visibility - score: 6 + score: 4 max: 6 passed: true - comment: Line thickness 2.5, markers size 7, margin segments size 5.0 — all - well-adapted + comment: Lines visible but phase panel compression makes margin segment and + markers hard to distinguish. - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Blue/orange/purple palette is colorblind-safe with strong contrast + comment: 'Colorblind-safe: blue, orange, purple are clearly distinguishable.' - id: VQ-05 name: Layout & Canvas - score: 3 + score: 1 max: 4 - passed: true - comment: Square format works but landscape would better serve log frequency - axis; magnitude clipped at -30 dB + passed: false + comment: 'Severe layout imbalance: magnitude panel has massive empty space, + phase panel extremely compressed.' - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Frequency (Hz) with units; panel strips serve as y-labels with units + comment: Frequency (Hz) with units, strip labels serve as y-axis labels with + units. design_excellence: score: 14 max: 20 @@ -92,24 +99,24 @@ review: score: 6 max: 8 passed: true - comment: Custom color palette, custom backgrounds, intentional color coding, - good typography hierarchy + comment: Custom color palette with intentional hierarchy, styled backgrounds, + above defaults. - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: theme_minimal, subtle two-level grid, custom backgrounds, generous - panel spacing — good but not every detail polished + comment: theme_minimal, custom grid colors, subtle grid weights, custom tick + colors. - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: Color-coded GM/PM with distinct marker shapes create clear focal - points and visual hierarchy + comment: GM/PM annotations with distinct colors and marker shapes create visual + hierarchy. spec_compliance: - score: 15 + score: 14 max: 15 items: - id: SC-01 @@ -117,26 +124,28 @@ review: score: 5 max: 5 passed: true - comment: Correct dual-panel Bode plot with magnitude on top and phase on bottom + comment: Correct dual-panel Bode plot with magnitude and phase sharing log + frequency axis. - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All features present: dual-panel, GM/PM annotations, reference lines, - log scale, grid' + comment: 'All features present: GM/PM annotations, reference lines, log axis, + grid.' - id: SC-03 name: Data Mapping - score: 3 + score: 2 max: 3 passed: true - comment: Frequency on log x, magnitude in dB, phase in degrees + comment: Correct mapping but phase panel compression makes it difficult to + read precise values. - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct; no legend needed for single-series with annotations + comment: Title format correct. No legend needed for single-system plot. data_quality: score: 14 max: 15 @@ -146,20 +155,22 @@ review: score: 5 max: 6 passed: true - comment: Shows gain/phase rolloff, margins, crossover frequencies; could show - more prominent resonance peak + comment: Shows 3rd-order TF with clear GM and PM. Both crossover frequencies + visible. No resonance peak. - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Third-order transfer function is a realistic control systems scenario + comment: 'Realistic control systems TF: G(s)=5/[(s+1)(0.5s+1)(0.2s+1)]. GM=8dB, + PM=33° are realistic.' - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Realistic frequency range, magnitude, and phase values + comment: Frequency range 0.03-30 Hz appropriate. Magnitude and phase values + physically correct. code_quality: score: 10 max: 10 @@ -169,31 +180,31 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports → data → plot → save' + comment: 'Clean linear flow: imports, data, margins, DataFrame, plot, save.' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic transfer function computation + comment: Fully deterministic analytical transfer function. - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used + comment: All imports used. - id: CQ-04 name: Code Elegance score: 2 max: 2 passed: true - comment: Clean, well-structured code with appropriate complexity + comment: Well-structured, appropriate complexity, no fake UI. - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with dpi=300 + comment: Saves as plot.png with dpi=300, current API. library_mastery: score: 7 max: 10 @@ -203,15 +214,15 @@ review: score: 4 max: 5 passed: true - comment: 'Good use of ggplot grammar: aes, geom layers, facet_wrap, scales, - theme' + comment: 'Good grammar of graphics: facet_wrap, aes mappings, layer composition, + CategoricalDtype.' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: facet_wrap with free_y scales, CategoricalDtype ordering, layer composition - — distinctive to grammar-of-graphics + comment: facet_wrap with free_y, CategoricalDtype for panel ordering, ggplot2-style + layer composition. verdict: REJECTED impl_tags: dependencies: []