From 4352937ddc00665bb1d3e968c43bc738ca65954d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:07:41 +0000 Subject: [PATCH 1/9] feat(matplotlib): implement campbell-basic --- .../implementations/matplotlib.py | 110 ++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 plots/campbell-basic/implementations/matplotlib.py diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py new file mode 100644 index 0000000000..7d61161ccf --- /dev/null +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -0,0 +1,110 @@ +"""pyplots.ai +campbell-basic: Campbell Diagram +Library: matplotlib | Python 3.13 +Quality: pending | Created: 2026-02-15 +""" + +import matplotlib.pyplot as plt +import numpy as np + + +# Data +speed_rpm = np.linspace(0, 6000, 80) +speed_hz = speed_rpm / 60 + +# Natural frequency modes (Hz) - slight variation with speed due to gyroscopic effects +mode_1_bending = 18 + 0.0008 * speed_rpm +mode_2_bending = 45 - 0.0005 * speed_rpm +mode_1_torsional = 62 + 0.0003 * speed_rpm +mode_axial = 78 - 0.0006 * speed_rpm +mode_3_bending = 95 + 0.0004 * speed_rpm + +modes = [mode_1_bending, mode_2_bending, mode_1_torsional, mode_axial, mode_3_bending] +mode_labels = ["1st Bending", "2nd Bending", "1st Torsional", "Axial", "3rd Bending"] +mode_colors = ["#306998", "#E8833A", "#4DAF4A", "#984EA3", "#A65628"] + +# Engine order lines +engine_orders = [1, 2, 3] +eo_freq = {eo: eo * speed_hz for eo in engine_orders} + +# Find critical speed intersections (where EO lines cross mode curves) +critical_speeds = [] +critical_freqs = [] +for mode in modes: + for eo in engine_orders: + eo_freqs = eo * speed_hz + diff = mode - eo_freqs + sign_changes = np.where(np.diff(np.sign(diff)))[0] + for idx in sign_changes: + frac = abs(diff[idx]) / (abs(diff[idx]) + abs(diff[idx + 1])) + rpm_interp = speed_rpm[idx] + frac * (speed_rpm[idx + 1] - speed_rpm[idx]) + freq_interp = mode[idx] + frac * (mode[idx + 1] - mode[idx]) + if 100 < rpm_interp < 5900: + critical_speeds.append(rpm_interp) + critical_freqs.append(freq_interp) + +# Plot +fig, ax = plt.subplots(figsize=(16, 9)) +y_max = 110 + +for mode, label, color in zip(modes, mode_labels, mode_colors, strict=True): + ax.plot(speed_rpm, mode, linewidth=3, color=color, label=label, zorder=3) + +for eo in engine_orders: + eo_line = eo_freq[eo] + visible = eo_line <= y_max + ax.plot( + speed_rpm[visible], + eo_line[visible], + linewidth=2, + color="#888888", + linestyle="--", + alpha=0.7, + label=f"{eo}x EO", + zorder=2, + ) + +ax.scatter( + critical_speeds, + critical_freqs, + s=250, + color="#D62728", + edgecolors="white", + linewidth=1.5, + zorder=5, + label="Critical Speeds", +) + +# Engine order line labels inside the plot area +for eo in engine_orders: + eo_line = eo * speed_hz + mask = eo_line <= y_max * 0.95 + if np.any(mask): + last_idx = np.where(mask)[0][-1] + label_rpm = speed_rpm[last_idx] + label_freq = eo_line[last_idx] + ax.text( + label_rpm, + label_freq + 2, + f"{eo}x", + fontsize=15, + color="#666666", + va="bottom", + ha="center", + fontweight="bold", + ) + +# Style +ax.set_xlabel("Rotational Speed (RPM)", fontsize=20) +ax.set_ylabel("Frequency (Hz)", fontsize=20) +ax.set_title("campbell-basic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium") +ax.tick_params(axis="both", labelsize=16) +ax.spines["top"].set_visible(False) +ax.spines["right"].set_visible(False) +ax.set_xlim(0, 6000) +ax.set_ylim(0, y_max) +ax.legend(fontsize=14, loc="upper left", framealpha=0.9, edgecolor="none") +ax.yaxis.grid(True, alpha=0.2, linewidth=0.8) + +plt.tight_layout() +plt.savefig("plot.png", dpi=300, bbox_inches="tight") From 450e0b823d1bb2c00f8ffc872e7de37d27efa415 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:08:00 +0000 Subject: [PATCH 2/9] chore(matplotlib): add metadata for campbell-basic --- plots/campbell-basic/metadata/matplotlib.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 plots/campbell-basic/metadata/matplotlib.yaml diff --git a/plots/campbell-basic/metadata/matplotlib.yaml b/plots/campbell-basic/metadata/matplotlib.yaml new file mode 100644 index 0000000000..cfe1852031 --- /dev/null +++ b/plots/campbell-basic/metadata/matplotlib.yaml @@ -0,0 +1,19 @@ +# Per-library metadata for matplotlib implementation of campbell-basic +# Auto-generated by impl-generate.yml + +library: matplotlib +specification_id: campbell-basic +created: '2026-02-15T21:08:00Z' +updated: '2026-02-15T21:08:00Z' +generated_by: claude-opus-4-5-20251101 +workflow_run: 22043026882 +issue: 4241 +python_version: 3.14.3 +library_version: 3.10.8 +preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot_thumb.png +preview_html: null +quality_score: null +review: + strengths: [] + weaknesses: [] From e7f0653ca2d934ab06d675d29cfb3aae2428b82c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:11:38 +0000 Subject: [PATCH 3/9] chore(matplotlib): update quality score 79 and review feedback for campbell-basic --- .../implementations/matplotlib.py | 6 +- plots/campbell-basic/metadata/matplotlib.yaml | 228 +++++++++++++++++- 2 files changed, 224 insertions(+), 10 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index 7d61161ccf..5831da5e80 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram -Library: matplotlib | Python 3.13 -Quality: pending | Created: 2026-02-15 +Library: matplotlib 3.10.8 | Python 3.14.3 +Quality: 79/100 | Created: 2026-02-15 """ import matplotlib.pyplot as plt diff --git a/plots/campbell-basic/metadata/matplotlib.yaml b/plots/campbell-basic/metadata/matplotlib.yaml index cfe1852031..960065e550 100644 --- a/plots/campbell-basic/metadata/matplotlib.yaml +++ b/plots/campbell-basic/metadata/matplotlib.yaml @@ -1,10 +1,7 @@ -# Per-library metadata for matplotlib implementation of campbell-basic -# Auto-generated by impl-generate.yml - library: matplotlib specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:08:00Z' +updated: '2026-02-15T21:11:37Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026882 issue: 4241 @@ -13,7 +10,224 @@ library_version: 3.10.8 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot_thumb.png preview_html: null -quality_score: null +quality_score: 79 review: - strengths: [] - weaknesses: [] + strengths: + - Correct and complete Campbell Diagram with all key components (mode curves, EO + lines, critical speed markers) + - Clean, deterministic data generation with realistic rotordynamics context and + proper mode shape naming + - Good use of zorder for visual layering (modes on top of EO lines, markers on top + of everything) + - Smart intersection-finding algorithm using sign changes with linear interpolation + - Proper explicit font sizing throughout, following matplotlib library rules exactly + weaknesses: + - Legend is too large (9 entries) and placed in upper-left where it covers mode + curves; consider placing it outside the plot or using a more compact layout + - No visual storytelling — critical speed intersections are marked but there is + no emphasis on which are most significant (e.g., operating range shading, annotation + of the most dangerous crossings) + - Mode curves have very subtle speed-dependent variation (slopes 0.0003-0.0008), + making the diagram look like mostly horizontal lines; more pronounced gyroscopic + effects would make it more visually interesting + - EO line labels (1x, 2x, 3x) could be positioned more carefully to avoid visual + clutter near the plot edge + image_description: 'The plot displays a Campbell Diagram with 5 natural frequency + mode curves plotted against Rotational Speed (RPM) on the x-axis and Frequency + (Hz) on the y-axis. The mode curves are: 1st Bending (blue, ~18 Hz, rising slightly), + 2nd Bending (orange, ~45 Hz, falling slightly), 1st Torsional (green, ~62 Hz, + nearly flat), Axial (purple, ~78 Hz, falling slightly), and 3rd Bending (brown, + ~95 Hz, rising slightly). Three gray dashed engine order lines (1x, 2x, 3x) radiate + from the origin with increasing slopes. Red circular markers with white edges + highlight critical speed intersections where engine order lines cross mode curves. + The title reads "campbell-basic · matplotlib · pyplots.ai". Top and right spines + are removed, with a subtle y-axis grid. A 9-item legend sits in the upper-left + corner covering some of the data area.' + criteria_checklist: + visual_quality: + score: 25 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title 24pt, labels 20pt, ticks 16pt, + legend 14pt' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text elements + - id: VQ-03 + name: Element Visibility + score: 5 + max: 6 + passed: true + comment: Lines at linewidth=3 and markers at s=250 are good; EO lines could + be slightly more distinct + - id: VQ-04 + name: Color Accessibility + score: 4 + max: 4 + passed: true + comment: Blue/orange/green/purple/brown palette is colorblind-safe; gray dashed + EO lines provide distinction + - id: VQ-05 + name: Layout & Canvas + score: 0 + max: 4 + passed: false + comment: Legend with 9 entries is oversized and covers the upper-left data + area, partially obscuring mode curves + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: Rotational Speed (RPM) and Frequency (Hz) with proper units + design_excellence: + score: 10 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 4 + max: 8 + passed: false + comment: Custom color palette, gray dashed EO lines, red markers with white + edges — well-configured default but not publication-grade + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: true + comment: Top/right spines removed, subtle y-grid (alpha=0.2), tight_layout; + good refinement + - id: DE-03 + name: Data Storytelling + score: 2 + max: 6 + passed: false + comment: Data displayed without emphasis on which intersections are most critical; + no operating range shading or viewer guidance + spec_compliance: + score: 14 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct Campbell Diagram with all required components + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: true + comment: All major features present; legend does not clearly separate modes + from EO lines visually + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=RPM, Y=Hz correctly mapped + - id: SC-04 + name: Title & Legend + score: 3 + max: 3 + passed: true + comment: Title format correct, legend labels match data + data_quality: + score: 14 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 5 + max: 6 + passed: true + comment: 5 modes with varying speed-dependent behavior, 3 engine orders, multiple + intersections; gyroscopic effects subtle + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Realistic rotordynamics scenario with proper mode shape names and + frequency ranges + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: 0-6000 RPM, 18-95 Hz range appropriate for rotating machinery + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: 'Linear flow: imports, data, plot, save' + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: Fully deterministic data from formulas + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: Only matplotlib.pyplot and numpy + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Clean intersection-finding logic, appropriate complexity + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot.png, no deprecated functions + library_mastery: + score: 6 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: true + comment: Good Axes API usage, proper zorder layering for visual depth + - id: LM-02 + name: Distinctive Features + score: 2 + max: 5 + passed: false + comment: Zorder layering somewhat distinctive, but overall generic line/scatter + plotting + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - annotations + patterns: + - data-generation + - iteration-over-groups + - explicit-figure + dataprep: + - interpolation + styling: + - edge-highlighting + - grid-styling From c9891d8642b67c548c55e224cda3bbe9ecc53595 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:15:40 +0000 Subject: [PATCH 4/9] fix(matplotlib): address review feedback for campbell-basic Attempt 1/3 - fixes based on AI review --- .../implementations/matplotlib.py | 212 ++++++++++++++---- 1 file changed, 165 insertions(+), 47 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index 5831da5e80..add81c61a4 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 Quality: 79/100 | Created: 2026-02-15 @@ -6,18 +6,21 @@ import matplotlib.pyplot as plt import numpy as np +from matplotlib.lines import Line2D +from matplotlib.patches import Patch # Data -speed_rpm = np.linspace(0, 6000, 80) +speed_rpm = np.linspace(0, 6000, 200) speed_hz = speed_rpm / 60 -# Natural frequency modes (Hz) - slight variation with speed due to gyroscopic effects -mode_1_bending = 18 + 0.0008 * speed_rpm -mode_2_bending = 45 - 0.0005 * speed_rpm -mode_1_torsional = 62 + 0.0003 * speed_rpm -mode_axial = 78 - 0.0006 * speed_rpm -mode_3_bending = 95 + 0.0004 * speed_rpm +# Natural frequency modes (Hz) - pronounced gyroscopic effects +# Forward whirl modes increase with speed, backward whirl modes decrease +mode_1_bending = 18 + 0.004 * speed_rpm - 1.5e-7 * speed_rpm**2 +mode_2_bending = 48 - 0.003 * speed_rpm + 2.0e-7 * speed_rpm**2 +mode_1_torsional = 62 + 0.0004 * speed_rpm +mode_axial = 74 - 0.005 * speed_rpm + 4.0e-7 * speed_rpm**2 +mode_3_bending = 88 + 0.005 * speed_rpm - 3.5e-7 * speed_rpm**2 modes = [mode_1_bending, mode_2_bending, mode_1_torsional, mode_axial, mode_3_bending] mode_labels = ["1st Bending", "2nd Bending", "1st Torsional", "Axial", "3rd Bending"] @@ -27,10 +30,11 @@ engine_orders = [1, 2, 3] eo_freq = {eo: eo * speed_hz for eo in engine_orders} -# Find critical speed intersections (where EO lines cross mode curves) +# Find critical speed intersections critical_speeds = [] critical_freqs = [] -for mode in modes: +critical_labels = [] +for mode, mlabel in zip(modes, mode_labels, strict=True): for eo in engine_orders: eo_freqs = eo * speed_hz diff = mode - eo_freqs @@ -42,69 +46,183 @@ if 100 < rpm_interp < 5900: critical_speeds.append(rpm_interp) critical_freqs.append(freq_interp) + critical_labels.append(f"{mlabel}\n{eo}x @ {int(rpm_interp)} RPM") + +# Define operating range for storytelling +op_min, op_max = 2500, 4500 # Plot fig, ax = plt.subplots(figsize=(16, 9)) y_max = 110 +# Operating range shading +ax.axvspan(op_min, op_max, alpha=0.06, color="#306998", zorder=0) +ax.axvline(op_min, color="#306998", linewidth=1, linestyle=":", alpha=0.4, zorder=1) +ax.axvline(op_max, color="#306998", linewidth=1, linestyle=":", alpha=0.4, zorder=1) +ax.text( + (op_min + op_max) / 2, + 3, + "Operating Range", + fontsize=13, + color="#306998", + ha="center", + va="bottom", + fontstyle="italic", + alpha=0.6, +) + +# Mode curves with varying linewidth for visual hierarchy for mode, label, color in zip(modes, mode_labels, mode_colors, strict=True): - ax.plot(speed_rpm, mode, linewidth=3, color=color, label=label, zorder=3) + ax.plot(speed_rpm, mode, linewidth=2.8, color=color, label=label, zorder=3, solid_capstyle="round") +# Engine order lines for eo in engine_orders: eo_line = eo_freq[eo] visible = eo_line <= y_max ax.plot( - speed_rpm[visible], - eo_line[visible], - linewidth=2, - color="#888888", - linestyle="--", - alpha=0.7, - label=f"{eo}x EO", - zorder=2, + speed_rpm[visible], eo_line[visible], linewidth=1.8, color="#AAAAAA", linestyle=(0, (8, 4)), alpha=0.6, zorder=2 ) -ax.scatter( - critical_speeds, - critical_freqs, - s=250, - color="#D62728", - edgecolors="white", - linewidth=1.5, - zorder=5, - label="Critical Speeds", -) - -# Engine order line labels inside the plot area +# Engine order labels placed along the lines using ax.annotate with rotation for eo in engine_orders: eo_line = eo * speed_hz - mask = eo_line <= y_max * 0.95 - if np.any(mask): - last_idx = np.where(mask)[0][-1] - label_rpm = speed_rpm[last_idx] - label_freq = eo_line[last_idx] - ax.text( - label_rpm, - label_freq + 2, - f"{eo}x", - fontsize=15, - color="#666666", - va="bottom", + # Place label at ~30% along the visible line for clean positioning + target_freq = y_max * 0.30 + target_rpm = target_freq * 60 / eo + if target_rpm < 5800: + angle_rad = np.arctan2( + (eo * (speed_rpm[1] - speed_rpm[0]) / 60) * (9 / y_max), (speed_rpm[1] - speed_rpm[0]) * (16 / 6000) + ) + angle_deg = np.degrees(angle_rad) + ax.annotate( + f"{eo}×", + xy=(target_rpm, target_freq), + fontsize=14, + color="#777777", + fontweight="bold", ha="center", + va="bottom", + rotation=angle_deg, + rotation_mode="anchor", + zorder=4, + bbox={"boxstyle": "round,pad=0.15", "facecolor": "white", "edgecolor": "none", "alpha": 0.8}, + ) + +# Critical speed markers - differentiate those in operating range +in_op = [(op_min <= s <= op_max) for s in critical_speeds] +out_op = [not x for x in in_op] + +cs_arr = np.array(critical_speeds) +cf_arr = np.array(critical_freqs) +in_op_arr = np.array(in_op) +out_op_arr = np.array(out_op) + +# Markers outside operating range (subtle) +if np.any(out_op_arr): + ax.scatter( + cs_arr[out_op_arr], + cf_arr[out_op_arr], + s=200, + color="#D62728", + edgecolors="white", + linewidth=1.5, + zorder=5, + alpha=0.5, + ) + +# Markers inside operating range (emphasized - the dangerous ones) +if np.any(in_op_arr): + ax.scatter( + cs_arr[in_op_arr], + cf_arr[in_op_arr], + s=350, + color="#D62728", + edgecolors="white", + linewidth=2, + zorder=6, + marker="D", + ) + # Annotate the most critical intersections (inside operating range) + cl_arr = np.array(critical_labels) + op_speeds = cs_arr[in_op_arr] + op_freqs = cf_arr[in_op_arr] + op_labels = cl_arr[in_op_arr] + # Sort by frequency for alternating offset directions + sort_idx = np.argsort(op_freqs) + for i, si in enumerate(sort_idx): + short_label = op_labels[si].split("\n")[0] + # Alternate annotation direction to avoid overlaps + if op_freqs[si] > y_max * 0.7: + dx, dy = 20, -22 # point down for high-frequency intersections + elif i % 2 == 0: + dx, dy = 22, 16 + else: + dx, dy = -22, -18 + ax.annotate( + short_label, + xy=(op_speeds[si], op_freqs[si]), + xytext=(dx, dy), + textcoords="offset points", + fontsize=11, + color="#B71C1C", fontweight="bold", + arrowprops={"arrowstyle": "-", "color": "#B71C1C", "lw": 0.8}, + zorder=7, + bbox={ + "boxstyle": "round,pad=0.2", + "facecolor": "white", + "edgecolor": "#B71C1C", + "alpha": 0.85, + "linewidth": 0.6, + }, ) # Style ax.set_xlabel("Rotational Speed (RPM)", fontsize=20) ax.set_ylabel("Frequency (Hz)", fontsize=20) -ax.set_title("campbell-basic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium") +ax.set_title("campbell-basic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium", pad=16) ax.tick_params(axis="both", labelsize=16) ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) +ax.spines["left"].set_linewidth(0.6) +ax.spines["bottom"].set_linewidth(0.6) +ax.spines["left"].set_color("#555555") +ax.spines["bottom"].set_color("#555555") ax.set_xlim(0, 6000) ax.set_ylim(0, y_max) -ax.legend(fontsize=14, loc="upper left", framealpha=0.9, edgecolor="none") -ax.yaxis.grid(True, alpha=0.2, linewidth=0.8) +ax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color="#CCCCCC") +ax.xaxis.grid(True, alpha=0.08, linewidth=0.4, color="#CCCCCC") + +# Compact legend outside the plot area using matplotlib's bbox_to_anchor +legend_handles = ax.get_legend_handles_labels() +mode_handles = legend_handles[0][:5] +mode_names = legend_handles[1][:5] + +# Add custom entries for EO lines and critical speeds +eo_handle = Line2D([0], [0], color="#AAAAAA", linewidth=1.8, linestyle=(0, (8, 4)), alpha=0.6) +crit_outside = Line2D( + [0], [0], marker="o", color="none", markerfacecolor="#D62728", markeredgecolor="white", markersize=10, alpha=0.5 +) +crit_inside = Line2D( + [0], [0], marker="D", color="none", markerfacecolor="#D62728", markeredgecolor="white", markersize=10 +) +op_handle = Patch(facecolor="#306998", alpha=0.12, edgecolor="none") + +all_handles = mode_handles + [eo_handle, crit_outside, crit_inside, op_handle] +all_labels = mode_names + ["Engine Order (1×–3×)", "Critical Speed", "Critical (in op. range)", "Operating Range"] + +ax.legend( + all_handles, + all_labels, + fontsize=13, + loc="upper left", + bbox_to_anchor=(1.01, 1), + framealpha=0.95, + edgecolor="#DDDDDD", + borderpad=0.8, + labelspacing=0.7, + handlelength=1.8, +) -plt.tight_layout() +fig.subplots_adjust(right=0.78) plt.savefig("plot.png", dpi=300, bbox_inches="tight") From f5726faad90cd9b6a6c308b8dab7fd3cc3915d59 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:19:49 +0000 Subject: [PATCH 5/9] chore(matplotlib): update quality score 87 and review feedback for campbell-basic --- .../implementations/matplotlib.py | 4 +- plots/campbell-basic/metadata/matplotlib.yaml | 175 +++++++++--------- 2 files changed, 92 insertions(+), 87 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index add81c61a4..cbc91d388f 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 -Quality: 79/100 | Created: 2026-02-15 +Quality: 87/100 | Created: 2026-02-15 """ import matplotlib.pyplot as plt diff --git a/plots/campbell-basic/metadata/matplotlib.yaml b/plots/campbell-basic/metadata/matplotlib.yaml index 960065e550..62aa5003f2 100644 --- a/plots/campbell-basic/metadata/matplotlib.yaml +++ b/plots/campbell-basic/metadata/matplotlib.yaml @@ -1,7 +1,7 @@ library: matplotlib specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:11:37Z' +updated: '2026-02-15T21:19:49Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026882 issue: 4241 @@ -10,42 +10,48 @@ library_version: 3.10.8 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot_thumb.png preview_html: null -quality_score: 79 +quality_score: 87 review: strengths: - - Correct and complete Campbell Diagram with all key components (mode curves, EO - lines, critical speed markers) - - Clean, deterministic data generation with realistic rotordynamics context and - proper mode shape naming - - Good use of zorder for visual layering (modes on top of EO lines, markers on top - of everything) - - Smart intersection-finding algorithm using sign changes with linear interpolation - - Proper explicit font sizing throughout, following matplotlib library rules exactly + - Excellent data storytelling through operating range shading and differentiated + critical speed markers (faded circles vs emphasized diamonds) + - Comprehensive, well-organized legend that clearly distinguishes all element types + (modes, EO lines, critical speeds, operating range) + - Engine order labels positioned along their lines with correct rotation angles, + using white background boxes for readability + - Realistic rotordynamic data with proper mode naming and physically plausible frequency + behavior (gyroscopic effects) + - Clean visual hierarchy with subtle gridlines, refined spines, and intentional + color choices weaknesses: - - Legend is too large (9 entries) and placed in upper-left where it covers mode - curves; consider placing it outside the plot or using a more compact layout - - No visual storytelling — critical speed intersections are marked but there is - no emphasis on which are most significant (e.g., operating range shading, annotation - of the most dangerous crossings) - - Mode curves have very subtle speed-dependent variation (slopes 0.0003-0.0008), - making the diagram look like mostly horizontal lines; more pronounced gyroscopic - effects would make it more visually interesting - - EO line labels (1x, 2x, 3x) could be positioned more carefully to avoid visual - clutter near the plot edge - image_description: 'The plot displays a Campbell Diagram with 5 natural frequency - mode curves plotted against Rotational Speed (RPM) on the x-axis and Frequency - (Hz) on the y-axis. The mode curves are: 1st Bending (blue, ~18 Hz, rising slightly), - 2nd Bending (orange, ~45 Hz, falling slightly), 1st Torsional (green, ~62 Hz, - nearly flat), Axial (purple, ~78 Hz, falling slightly), and 3rd Bending (brown, - ~95 Hz, rising slightly). Three gray dashed engine order lines (1x, 2x, 3x) radiate - from the origin with increasing slopes. Red circular markers with white edges - highlight critical speed intersections where engine order lines cross mode curves. - The title reads "campbell-basic · matplotlib · pyplots.ai". Top and right spines - are removed, with a subtle y-axis grid. A 9-item legend sits in the upper-left - corner covering some of the data area.' + - The 1st Torsional and Axial critical speed annotations in the operating range + are very close together (~60-63 Hz), risking visual overlap — consider increasing + vertical offset separation between them + - 'Layout balance could be improved: the external legend takes ~22% of canvas width, + compressing the plot area — consider placing the legend below the plot or making + it more compact' + - The green (1st Torsional) and orange (2nd Bending) colors could be better distinguished + for deuteranopia accessibility — consider using a more blue-shifted alternative + for one of them + image_description: 'The plot displays a Campbell Diagram with rotational speed (0-6000 + RPM) on the x-axis and frequency (0-110 Hz) on the y-axis. Five natural frequency + mode curves are plotted: 1st Bending (blue, rising), 2nd Bending (orange, declining), + 1st Torsional (green, nearly flat at ~62 Hz), Axial (purple, declining), and 3rd + Bending (brown/dark orange, rising then flattening). Three dashed gray engine + order lines (1x, 2x, 3x) radiate diagonally from the origin with rotated labels. + Critical speed intersections outside the operating range are shown as faded pink + circles, while those inside the operating range (2500-4500 RPM, shaded light blue + with dotted vertical boundaries) are emphasized with larger red diamond markers. + Annotations in dark red bordered boxes label the critical modes in the operating + range: 3rd Bending, 2nd Bending, 1st Torsional, and Axial. A comprehensive legend + is positioned outside the plot to the right, listing all mode curves, engine order + lines, critical speed markers (inside and outside), and the operating range. The + title reads "campbell-basic · matplotlib · pyplots.ai." The overall layout is + clean with subtle differentiated gridlines (y-axis more visible than x-axis) and + removed top/right spines with thinned left/bottom spines.' criteria_checklist: visual_quality: - score: 25 + score: 26 max: 30 items: - id: VQ-01 @@ -53,66 +59,64 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, labels 20pt, ticks 16pt, - legend 14pt' + comment: 'All font sizes explicitly set: title 24, labels 20, ticks 16, legend + 13, annotations 11-14' - id: VQ-02 name: No Overlap - score: 6 + score: 5 max: 6 passed: true - comment: No overlapping text elements + comment: 1st Torsional and Axial annotations very close together near 60-63 + Hz, near-overlap - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Lines at linewidth=3 and markers at s=250 are good; EO lines could - be slightly more distinct + comment: Lines 2.8 lw, markers differentiated (200 outside, 350 inside), good + hierarchy - id: VQ-04 name: Color Accessibility - score: 4 + score: 3 max: 4 passed: true - comment: Blue/orange/green/purple/brown palette is colorblind-safe; gray dashed - EO lines provide distinction + comment: Generally good, but green and orange may challenge deuteranopia - id: VQ-05 - name: Layout & Canvas - score: 0 + name: Layout Balance + score: 2 max: 4 - passed: false - comment: Legend with 9 entries is oversized and covers the upper-left data - area, partially obscuring mode curves + passed: true + comment: External legend takes ~22% of canvas width, plot area ~55% of canvas - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Rotational Speed (RPM) and Frequency (Hz) with proper units + comment: Rotational Speed (RPM) and Frequency (Hz) with units design_excellence: - score: 10 + score: 16 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 4 + score: 6 max: 8 - passed: false - comment: Custom color palette, gray dashed EO lines, red markers with white - edges — well-configured default but not publication-grade + passed: true + comment: Custom palette, visual hierarchy with differentiated markers, operating + range shading, clean spines - id: DE-02 name: Visual Refinement - score: 4 + score: 5 max: 6 passed: true - comment: Top/right spines removed, subtle y-grid (alpha=0.2), tight_layout; - good refinement + comment: Refined spines, subtle differentiated grid, minor annotation crowding - id: DE-03 name: Data Storytelling - score: 2 + score: 5 max: 6 - passed: false - comment: Data displayed without emphasis on which intersections are most critical; - no operating range shading or viewer guidance + passed: true + comment: Operating range shading draws eye to danger zone, diamond vs circle + markers show risk differentiation spec_compliance: score: 14 max: 15 @@ -122,26 +126,26 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell Diagram with all required components + comment: Correct Campbell Diagram with frequency vs rotational speed - id: SC-02 name: Required Features score: 3 max: 4 passed: true - comment: All major features present; legend does not clearly separate modes - from EO lines visually + comment: All major features present; optional individual critical speed zone + shading not implemented - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=RPM, Y=Hz correctly mapped + comment: X=RPM, Y=Hz correctly mapped, EO slopes = order/60 - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct, legend labels match data + comment: Title format correct, legend comprehensive with all categories data_quality: score: 14 max: 15 @@ -151,23 +155,22 @@ review: score: 5 max: 6 passed: true - comment: 5 modes with varying speed-dependent behavior, 3 engine orders, multiple - intersections; gyroscopic effects subtle + comment: Forward whirl, backward whirl, flat torsional, and mixed modes shown; + some modes close in frequency - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Realistic rotordynamics scenario with proper mode shape names and - frequency ranges + comment: Authentic rotordynamic scenario with proper mode nomenclature - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: 0-6000 RPM, 18-95 Hz range appropriate for rotating machinery + comment: Frequencies 18-100 Hz, operating range 2500-4500 RPM, all realistic code_quality: - score: 10 + score: 9 max: 10 items: - id: CQ-01 @@ -175,33 +178,33 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports, data, plot, save' + comment: Clean imports-data-plot-save flow, no functions or classes - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic data from formulas + comment: All data deterministic via mathematical formulas - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: Only matplotlib.pyplot and numpy + comment: 'All imports used: pyplot, numpy, Line2D, Patch' - id: CQ-04 name: Code Elegance - score: 2 + score: 1 max: 2 passed: true - comment: Clean intersection-finding logic, appropriate complexity + comment: Well-structured but verbose annotation logic with alternating directions - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png, no deprecated functions + comment: Saves as plot.png, dpi=300, bbox_inches=tight library_mastery: - score: 6 + score: 8 max: 10 items: - id: LM-01 @@ -209,25 +212,27 @@ review: score: 4 max: 5 passed: true - comment: Good Axes API usage, proper zorder layering for visual depth + comment: 'Good idiomatic matplotlib: axes methods, custom legend with Line2D/Patch, + annotate with arrowprops' - id: LM-02 name: Distinctive Features - score: 2 + score: 4 max: 5 - passed: false - comment: Zorder layering somewhat distinctive, but overall generic line/scatter - plotting + passed: true + comment: Uses Line2D/Patch legend handles, annotate with rotation_mode, axvspan, + fine spine control verdict: REJECTED impl_tags: dependencies: [] techniques: - annotations + - custom-legend patterns: - data-generation - iteration-over-groups - - explicit-figure dataprep: - interpolation styling: - - edge-highlighting + - alpha-blending - grid-styling + - edge-highlighting From 18c0d03f543f9579b3f3aa3c64dddea6e20d4681 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:23:48 +0000 Subject: [PATCH 6/9] fix(matplotlib): address review feedback for campbell-basic Attempt 2/3 - fixes based on AI review --- .../implementations/matplotlib.py | 134 +++++++----------- 1 file changed, 48 insertions(+), 86 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index cbc91d388f..1713f95585 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 Quality: 87/100 | Created: 2026-02-15 @@ -15,16 +15,15 @@ speed_hz = speed_rpm / 60 # Natural frequency modes (Hz) - pronounced gyroscopic effects -# Forward whirl modes increase with speed, backward whirl modes decrease mode_1_bending = 18 + 0.004 * speed_rpm - 1.5e-7 * speed_rpm**2 mode_2_bending = 48 - 0.003 * speed_rpm + 2.0e-7 * speed_rpm**2 -mode_1_torsional = 62 + 0.0004 * speed_rpm -mode_axial = 74 - 0.005 * speed_rpm + 4.0e-7 * speed_rpm**2 -mode_3_bending = 88 + 0.005 * speed_rpm - 3.5e-7 * speed_rpm**2 +mode_1_torsional = 58 + 0.0004 * speed_rpm +mode_axial = 78 - 0.005 * speed_rpm + 4.0e-7 * speed_rpm**2 +mode_3_bending = 92 + 0.005 * speed_rpm - 3.5e-7 * speed_rpm**2 modes = [mode_1_bending, mode_2_bending, mode_1_torsional, mode_axial, mode_3_bending] mode_labels = ["1st Bending", "2nd Bending", "1st Torsional", "Axial", "3rd Bending"] -mode_colors = ["#306998", "#E8833A", "#4DAF4A", "#984EA3", "#A65628"] +mode_colors = ["#306998", "#E8833A", "#2B9EB3", "#984EA3", "#A65628"] # Engine order lines engine_orders = [1, 2, 3] @@ -33,7 +32,7 @@ # Find critical speed intersections critical_speeds = [] critical_freqs = [] -critical_labels = [] +critical_mode_labels = [] for mode, mlabel in zip(modes, mode_labels, strict=True): for eo in engine_orders: eo_freqs = eo * speed_hz @@ -46,14 +45,14 @@ if 100 < rpm_interp < 5900: critical_speeds.append(rpm_interp) critical_freqs.append(freq_interp) - critical_labels.append(f"{mlabel}\n{eo}x @ {int(rpm_interp)} RPM") + critical_mode_labels.append(mlabel) -# Define operating range for storytelling +# Define operating range op_min, op_max = 2500, 4500 # Plot fig, ax = plt.subplots(figsize=(16, 9)) -y_max = 110 +y_max = 120 # Operating range shading ax.axvspan(op_min, op_max, alpha=0.06, color="#306998", zorder=0) @@ -71,29 +70,22 @@ alpha=0.6, ) -# Mode curves with varying linewidth for visual hierarchy +# Mode curves for mode, label, color in zip(modes, mode_labels, mode_colors, strict=True): ax.plot(speed_rpm, mode, linewidth=2.8, color=color, label=label, zorder=3, solid_capstyle="round") -# Engine order lines +# Engine order lines with rotated labels for eo in engine_orders: eo_line = eo_freq[eo] visible = eo_line <= y_max ax.plot( speed_rpm[visible], eo_line[visible], linewidth=1.8, color="#AAAAAA", linestyle=(0, (8, 4)), alpha=0.6, zorder=2 ) - -# Engine order labels placed along the lines using ax.annotate with rotation -for eo in engine_orders: - eo_line = eo * speed_hz - # Place label at ~30% along the visible line for clean positioning - target_freq = y_max * 0.30 + target_freq = y_max * 0.28 target_rpm = target_freq * 60 / eo if target_rpm < 5800: - angle_rad = np.arctan2( - (eo * (speed_rpm[1] - speed_rpm[0]) / 60) * (9 / y_max), (speed_rpm[1] - speed_rpm[0]) * (16 / 6000) - ) - angle_deg = np.degrees(angle_rad) + slope_display = (eo / 60) * (9 / y_max) / (16 / 6000) + angle_deg = np.degrees(np.arctan(slope_display)) ax.annotate( f"{eo}×", xy=(target_rpm, target_freq), @@ -108,58 +100,33 @@ bbox={"boxstyle": "round,pad=0.15", "facecolor": "white", "edgecolor": "none", "alpha": 0.8}, ) -# Critical speed markers - differentiate those in operating range -in_op = [(op_min <= s <= op_max) for s in critical_speeds] -out_op = [not x for x in in_op] - +# Critical speed markers cs_arr = np.array(critical_speeds) cf_arr = np.array(critical_freqs) -in_op_arr = np.array(in_op) -out_op_arr = np.array(out_op) +in_op = (cs_arr >= op_min) & (cs_arr <= op_max) -# Markers outside operating range (subtle) -if np.any(out_op_arr): +if np.any(~in_op): ax.scatter( - cs_arr[out_op_arr], - cf_arr[out_op_arr], - s=200, - color="#D62728", - edgecolors="white", - linewidth=1.5, - zorder=5, - alpha=0.5, + cs_arr[~in_op], cf_arr[~in_op], s=200, color="#D62728", edgecolors="white", linewidth=1.5, zorder=5, alpha=0.5 ) -# Markers inside operating range (emphasized - the dangerous ones) -if np.any(in_op_arr): +if np.any(in_op): ax.scatter( - cs_arr[in_op_arr], - cf_arr[in_op_arr], - s=350, - color="#D62728", - edgecolors="white", - linewidth=2, - zorder=6, - marker="D", + cs_arr[in_op], cf_arr[in_op], s=350, color="#D62728", edgecolors="white", linewidth=2, zorder=6, marker="D" ) - # Annotate the most critical intersections (inside operating range) - cl_arr = np.array(critical_labels) - op_speeds = cs_arr[in_op_arr] - op_freqs = cf_arr[in_op_arr] - op_labels = cl_arr[in_op_arr] - # Sort by frequency for alternating offset directions + # Annotate critical intersections inside operating range with spread offsets + op_speeds = cs_arr[in_op] + op_freqs = cf_arr[in_op] + op_mlabels = np.array(critical_mode_labels)[in_op] sort_idx = np.argsort(op_freqs) - for i, si in enumerate(sort_idx): - short_label = op_labels[si].split("\n")[0] - # Alternate annotation direction to avoid overlaps - if op_freqs[si] > y_max * 0.7: - dx, dy = 20, -22 # point down for high-frequency intersections - elif i % 2 == 0: - dx, dy = 22, 16 - else: - dx, dy = -22, -18 + n_labels = len(sort_idx) + for rank, si in enumerate(sort_idx): + # Spread annotations vertically: alternate left/right, increasing vertical offset + sign = 1 if rank % 2 == 0 else -1 + dx = sign * 25 + dy = -20 + rank * (40 / max(n_labels - 1, 1)) ax.annotate( - short_label, + op_mlabels[si], xy=(op_speeds[si], op_freqs[si]), xytext=(dx, dy), textcoords="offset points", @@ -182,23 +149,17 @@ ax.set_ylabel("Frequency (Hz)", fontsize=20) ax.set_title("campbell-basic · matplotlib · pyplots.ai", fontsize=24, fontweight="medium", pad=16) ax.tick_params(axis="both", labelsize=16) -ax.spines["top"].set_visible(False) -ax.spines["right"].set_visible(False) -ax.spines["left"].set_linewidth(0.6) -ax.spines["bottom"].set_linewidth(0.6) -ax.spines["left"].set_color("#555555") -ax.spines["bottom"].set_color("#555555") +for spine in ("top", "right"): + ax.spines[spine].set_visible(False) +for spine in ("left", "bottom"): + ax.spines[spine].set_linewidth(0.6) + ax.spines[spine].set_color("#555555") ax.set_xlim(0, 6000) ax.set_ylim(0, y_max) ax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color="#CCCCCC") ax.xaxis.grid(True, alpha=0.08, linewidth=0.4, color="#CCCCCC") -# Compact legend outside the plot area using matplotlib's bbox_to_anchor -legend_handles = ax.get_legend_handles_labels() -mode_handles = legend_handles[0][:5] -mode_names = legend_handles[1][:5] - -# Add custom entries for EO lines and critical speeds +# Inset legend for better layout balance eo_handle = Line2D([0], [0], color="#AAAAAA", linewidth=1.8, linestyle=(0, (8, 4)), alpha=0.6) crit_outside = Line2D( [0], [0], marker="o", color="none", markerfacecolor="#D62728", markeredgecolor="white", markersize=10, alpha=0.5 @@ -208,21 +169,22 @@ ) op_handle = Patch(facecolor="#306998", alpha=0.12, edgecolor="none") -all_handles = mode_handles + [eo_handle, crit_outside, crit_inside, op_handle] -all_labels = mode_names + ["Engine Order (1×–3×)", "Critical Speed", "Critical (in op. range)", "Operating Range"] +handles = ax.get_legend_handles_labels()[0][:5] +labels = ax.get_legend_handles_labels()[1][:5] +handles += [eo_handle, crit_outside, crit_inside, op_handle] +labels += ["Engine Order (1×–3×)", "Critical Speed", "Critical (in op. range)", "Operating Range"] ax.legend( - all_handles, - all_labels, - fontsize=13, + handles, + labels, + fontsize=12, loc="upper left", - bbox_to_anchor=(1.01, 1), framealpha=0.95, edgecolor="#DDDDDD", - borderpad=0.8, - labelspacing=0.7, - handlelength=1.8, + borderpad=0.6, + labelspacing=0.5, + handlelength=1.6, ) -fig.subplots_adjust(right=0.78) +plt.tight_layout() plt.savefig("plot.png", dpi=300, bbox_inches="tight") From 8c7dbeff14ea8b002cc3cf53c8ef34aee8dbd7c3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:27:46 +0000 Subject: [PATCH 7/9] chore(matplotlib): update quality score 88 and review feedback for campbell-basic --- .../implementations/matplotlib.py | 4 +- plots/campbell-basic/metadata/matplotlib.yaml | 161 +++++++++--------- 2 files changed, 84 insertions(+), 81 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index 1713f95585..a740c795f1 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 -Quality: 87/100 | Created: 2026-02-15 +Quality: 88/100 | Created: 2026-02-15 """ import matplotlib.pyplot as plt diff --git a/plots/campbell-basic/metadata/matplotlib.yaml b/plots/campbell-basic/metadata/matplotlib.yaml index 62aa5003f2..f5659d6817 100644 --- a/plots/campbell-basic/metadata/matplotlib.yaml +++ b/plots/campbell-basic/metadata/matplotlib.yaml @@ -1,7 +1,7 @@ library: matplotlib specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:19:49Z' +updated: '2026-02-15T21:27:45Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026882 issue: 4241 @@ -10,48 +10,42 @@ library_version: 3.10.8 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot_thumb.png preview_html: null -quality_score: 87 +quality_score: 88 review: strengths: - - Excellent data storytelling through operating range shading and differentiated - critical speed markers (faded circles vs emphasized diamonds) - - Comprehensive, well-organized legend that clearly distinguishes all element types - (modes, EO lines, critical speeds, operating range) - - Engine order labels positioned along their lines with correct rotation angles, - using white background boxes for readability - - Realistic rotordynamic data with proper mode naming and physically plausible frequency - behavior (gyroscopic effects) - - Clean visual hierarchy with subtle gridlines, refined spines, and intentional - color choices + - 'Excellent spec compliance: all required features implemented including optional + operating range shading' + - Strong data storytelling through visual differentiation of critical speeds inside + vs. outside operating range (diamond vs. circle markers, opacity contrast, labeled + annotations) + - Realistic rotordynamics data with physically plausible mode behaviors and frequencies + - Clean visual design with removed spines, subtle grid, and custom color palette + - Well-constructed custom legend that clearly explains all 9 element types weaknesses: - - The 1st Torsional and Axial critical speed annotations in the operating range - are very close together (~60-63 Hz), risking visual overlap — consider increasing - vertical offset separation between them - - 'Layout balance could be improved: the external legend takes ~22% of canvas width, - compressing the plot area — consider placing the legend below the plot or making - it more compact' - - The green (1st Torsional) and orange (2nd Bending) colors could be better distinguished - for deuteranopia accessibility — consider using a more blue-shifted alternative - for one of them - image_description: 'The plot displays a Campbell Diagram with rotational speed (0-6000 - RPM) on the x-axis and frequency (0-110 Hz) on the y-axis. Five natural frequency - mode curves are plotted: 1st Bending (blue, rising), 2nd Bending (orange, declining), - 1st Torsional (green, nearly flat at ~62 Hz), Axial (purple, declining), and 3rd - Bending (brown/dark orange, rising then flattening). Three dashed gray engine - order lines (1x, 2x, 3x) radiate diagonally from the origin with rotated labels. - Critical speed intersections outside the operating range are shown as faded pink - circles, while those inside the operating range (2500-4500 RPM, shaded light blue - with dotted vertical boundaries) are emphasized with larger red diamond markers. - Annotations in dark red bordered boxes label the critical modes in the operating - range: 3rd Bending, 2nd Bending, 1st Torsional, and Axial. A comprehensive legend - is positioned outside the plot to the right, listing all mode curves, engine order - lines, critical speed markers (inside and outside), and the operating range. The - title reads "campbell-basic · matplotlib · pyplots.ai." The overall layout is - clean with subtle differentiated gridlines (y-axis more visible than x-axis) and - removed top/right spines with thinned left/bottom spines.' + - Legend is large (9 items) and positioned in the upper-left corner where it covers + some data area; could use a more compact layout or external positioning + - Annotation labels for critical speeds inside the operating range are close together, + particularly 1st Torsional and Axial around 58-65 Hz at 3500-4000 RPM + - Code complexity could be slightly reduced in the intersection-finding and annotation-spreading + sections + image_description: 'The plot displays a Campbell Diagram with 5 natural frequency + mode curves plotted against rotational speed (0-6000 RPM) on the x-axis and frequency + (0-120 Hz) on the y-axis. The mode curves are: 1st Bending (dark blue, rising + gently then leveling), 2nd Bending (orange, declining slightly), 1st Torsional + (cyan, nearly flat around 58 Hz), Axial (purple, declining), and 3rd Bending (brown, + rising then flattening). Three gray dashed engine order lines (1x, 2x, 3x) radiate + from the origin with rotated labels. Critical speed intersections outside the + operating range are shown as semi-transparent red circles (about 10 markers), + while intersections inside the operating range (2500-4500 RPM, shaded light blue + with dotted vertical boundaries) are displayed as large red diamond markers with + labeled annotations connected by thin lines (2nd Bending, 3rd Bending, 1st Torsional, + Axial). An italic "Operating Range" label sits near the bottom of the shaded region. + A comprehensive legend in the upper left lists all 9 element types. Top and right + spines are removed, subtle grid lines are present, and the title reads "campbell-basic + · matplotlib · pyplots.ai".' criteria_checklist: visual_quality: - score: 26 + score: 27 max: 30 items: - id: VQ-01 @@ -59,34 +53,34 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24, labels 20, ticks 16, legend - 13, annotations 11-14' + comment: 'All font sizes explicitly set: title 24pt, axis labels 20pt, ticks + 16pt, legend 12pt, annotations 11-14pt' - id: VQ-02 name: No Overlap score: 5 max: 6 passed: true - comment: 1st Torsional and Axial annotations very close together near 60-63 - Hz, near-overlap + comment: Minor proximity between 1st Torsional and Axial annotations in operating + range; not severely overlapping but close - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Lines 2.8 lw, markers differentiated (200 outside, 350 inside), good - hierarchy + comment: Line widths 2.8 for modes, markers s=200/350 for critical speeds, + well-adapted to data density - id: VQ-04 name: Color Accessibility - score: 3 + score: 4 max: 4 passed: true - comment: Generally good, but green and orange may challenge deuteranopia + comment: Blue, orange, cyan, purple, brown palette; no red-green as sole distinguisher - id: VQ-05 name: Layout Balance score: 2 max: 4 - passed: true - comment: External legend takes ~22% of canvas width, plot area ~55% of canvas + passed: false + comment: Large 9-item legend in upper left covers data area; some lost space - id: VQ-06 name: Axis Labels & Title score: 2 @@ -94,7 +88,7 @@ review: passed: true comment: Rotational Speed (RPM) and Frequency (Hz) with units design_excellence: - score: 16 + score: 15 max: 20 items: - id: DE-01 @@ -102,23 +96,25 @@ review: score: 6 max: 8 passed: true - comment: Custom palette, visual hierarchy with differentiated markers, operating - range shading, clean spines + comment: Custom palette, intentional visual hierarchy with operating range + shading and differentiated marker styles; clearly above defaults - id: DE-02 name: Visual Refinement score: 5 max: 6 passed: true - comment: Refined spines, subtle differentiated grid, minor annotation crowding + comment: Top/right spines removed, subtle grid with differentiated x/y alpha, + spine colors customized, generous whitespace - id: DE-03 name: Data Storytelling - score: 5 + score: 4 max: 6 passed: true - comment: Operating range shading draws eye to danger zone, diamond vs circle - markers show risk differentiation + comment: 'Good visual hierarchy: operating range highlighted, critical speeds + inside emphasized with large diamonds + annotations vs. small faded circles + outside' spec_compliance: - score: 14 + score: 15 max: 15 items: - id: SC-01 @@ -126,26 +122,28 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell Diagram with frequency vs rotational speed + comment: Correct Campbell Diagram with natural frequency curves overlaid on + engine order excitation lines - id: SC-02 name: Required Features - score: 3 + score: 4 max: 4 passed: true - comment: All major features present; optional individual critical speed zone - shading not implemented + comment: 'All spec features present: 5 modes, 3 engine orders, critical speed + markers, mode labels, EO labels, clean legend, operating range shading' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=RPM, Y=Hz correctly mapped, EO slopes = order/60 + comment: X=RPM, Y=Hz; engine order slopes correctly computed as order/60 - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct, legend comprehensive with all categories + comment: Title format correct; legend labels accurately describe all element + types data_quality: score: 14 max: 15 @@ -155,20 +153,22 @@ review: score: 5 max: 6 passed: true - comment: Forward whirl, backward whirl, flat torsional, and mixed modes shown; - some modes close in frequency + comment: 5 modes with varied behaviors (rising, falling, flat) demonstrating + gyroscopic effects; multiple EO crossings at different frequencies - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Authentic rotordynamic scenario with proper mode nomenclature + comment: Authentic rotordynamics scenario with plausible mode names, frequency + ranges, and operating RPM band - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Frequencies 18-100 Hz, operating range 2500-4500 RPM, all realistic + comment: 'Realistic values: 18-92 Hz base frequencies, 0-6000 RPM range, 2500-4500 + RPM operating range' code_quality: score: 9 max: 10 @@ -178,50 +178,52 @@ review: score: 3 max: 3 passed: true - comment: Clean imports-data-plot-save flow, no functions or classes + comment: Linear imports-data-plot-save structure, no functions/classes - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: All data deterministic via mathematical formulas + comment: Fully deterministic data (mathematical functions, no randomness) - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: 'All imports used: pyplot, numpy, Line2D, Patch' + comment: 'All imports used: plt, np, Line2D for legend handles, Patch for + operating range handle' - id: CQ-04 name: Code Elegance score: 1 max: 2 passed: true - comment: Well-structured but verbose annotation logic with alternating directions + comment: Clean overall but intersection-finding and annotation spread logic + add moderate complexity - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png, dpi=300, bbox_inches=tight - library_mastery: + comment: Saves as plot.png with dpi=300 + library_features: score: 8 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 passed: true - comment: 'Good idiomatic matplotlib: axes methods, custom legend with Line2D/Patch, - annotate with arrowprops' + comment: Proper Axes-level API throughout, annotate with arrowprops/textcoords, + axvspan/axvline, scatter with distinct markers - id: LM-02 name: Distinctive Features - score: 4 + score: 3 max: 5 passed: true - comment: Uses Line2D/Patch legend handles, annotate with rotation_mode, axvspan, - fine spine control - verdict: REJECTED + comment: Custom legend via proxy artists (Line2D, Patch), annotate with rotation_mode + anchor, axvspan shading + verdict: APPROVED impl_tags: dependencies: [] techniques: @@ -230,6 +232,7 @@ impl_tags: patterns: - data-generation - iteration-over-groups + - explicit-figure dataprep: - interpolation styling: From ec079288a01678d7d8ec423bb75fd4561e9cedb2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:32:12 +0000 Subject: [PATCH 8/9] fix(matplotlib): address review feedback for campbell-basic Attempt 3/3 - fixes based on AI review --- .../implementations/matplotlib.py | 132 ++++++++++-------- 1 file changed, 76 insertions(+), 56 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index a740c795f1..2bc88d1281 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 Quality: 88/100 | Created: 2026-02-15 @@ -8,13 +8,14 @@ import numpy as np from matplotlib.lines import Line2D from matplotlib.patches import Patch +from matplotlib.ticker import FuncFormatter # Data speed_rpm = np.linspace(0, 6000, 200) speed_hz = speed_rpm / 60 -# Natural frequency modes (Hz) - pronounced gyroscopic effects +# Natural frequency modes (Hz) - realistic gyroscopic effects mode_1_bending = 18 + 0.004 * speed_rpm - 1.5e-7 * speed_rpm**2 mode_2_bending = 48 - 0.003 * speed_rpm + 2.0e-7 * speed_rpm**2 mode_1_torsional = 58 + 0.0004 * speed_rpm @@ -25,39 +26,32 @@ mode_labels = ["1st Bending", "2nd Bending", "1st Torsional", "Axial", "3rd Bending"] mode_colors = ["#306998", "#E8833A", "#2B9EB3", "#984EA3", "#A65628"] -# Engine order lines engine_orders = [1, 2, 3] eo_freq = {eo: eo * speed_hz for eo in engine_orders} -# Find critical speed intersections -critical_speeds = [] -critical_freqs = [] -critical_mode_labels = [] +# Find critical speed intersections via sign changes +op_min, op_max = 2500, 4500 +critical_speeds, critical_freqs, critical_mlabels = [], [], [] for mode, mlabel in zip(modes, mode_labels, strict=True): for eo in engine_orders: - eo_freqs = eo * speed_hz - diff = mode - eo_freqs - sign_changes = np.where(np.diff(np.sign(diff)))[0] - for idx in sign_changes: - frac = abs(diff[idx]) / (abs(diff[idx]) + abs(diff[idx + 1])) - rpm_interp = speed_rpm[idx] + frac * (speed_rpm[idx + 1] - speed_rpm[idx]) - freq_interp = mode[idx] + frac * (mode[idx + 1] - mode[idx]) - if 100 < rpm_interp < 5900: - critical_speeds.append(rpm_interp) - critical_freqs.append(freq_interp) - critical_mode_labels.append(mlabel) - -# Define operating range -op_min, op_max = 2500, 4500 + diff = mode - eo * speed_hz + for idx in np.where(np.diff(np.sign(diff)))[0]: + t = abs(diff[idx]) / (abs(diff[idx]) + abs(diff[idx + 1])) + rpm = speed_rpm[idx] + t * (speed_rpm[idx + 1] - speed_rpm[idx]) + freq = mode[idx] + t * (mode[idx + 1] - mode[idx]) + if 100 < rpm < 5900: + critical_speeds.append(rpm) + critical_freqs.append(freq) + critical_mlabels.append(mlabel) # Plot fig, ax = plt.subplots(figsize=(16, 9)) y_max = 120 # Operating range shading -ax.axvspan(op_min, op_max, alpha=0.06, color="#306998", zorder=0) -ax.axvline(op_min, color="#306998", linewidth=1, linestyle=":", alpha=0.4, zorder=1) -ax.axvline(op_max, color="#306998", linewidth=1, linestyle=":", alpha=0.4, zorder=1) +ax.axvspan(op_min, op_max, alpha=0.07, color="#306998", zorder=0) +ax.axvline(op_min, color="#306998", linewidth=1.2, linestyle=":", alpha=0.5, zorder=1) +ax.axvline(op_max, color="#306998", linewidth=1.2, linestyle=":", alpha=0.5, zorder=1) ax.text( (op_min + op_max) / 2, 3, @@ -71,8 +65,29 @@ ) # Mode curves -for mode, label, color in zip(modes, mode_labels, mode_colors, strict=True): - ax.plot(speed_rpm, mode, linewidth=2.8, color=color, label=label, zorder=3, solid_capstyle="round") +for mode, _label, color in zip(modes, mode_labels, mode_colors, strict=True): + ax.plot(speed_rpm, mode, linewidth=2.8, color=color, zorder=3, solid_capstyle="round") + +# End-of-line labels with vertical de-collision +end_vals = [(mode[-1], label, color) for mode, label, color in zip(modes, mode_labels, mode_colors, strict=True)] +end_vals.sort(key=lambda x: x[0]) +min_gap = 4.5 # minimum Hz gap between adjacent labels +positions = [v[0] for v in end_vals] +for i in range(1, len(positions)): + if positions[i] - positions[i - 1] < min_gap: + positions[i] = positions[i - 1] + min_gap +for y_pos, (_, label, color) in zip(positions, end_vals, strict=True): + ax.annotate( + label, + xy=(speed_rpm[-1], y_pos), + xytext=(8, 0), + textcoords="offset points", + fontsize=10, + color=color, + fontweight="bold", + va="center", + zorder=4, + ) # Engine order lines with rotated labels for eo in engine_orders: @@ -101,46 +116,43 @@ ) # Critical speed markers -cs_arr = np.array(critical_speeds) -cf_arr = np.array(critical_freqs) +cs_arr, cf_arr = np.array(critical_speeds), np.array(critical_freqs) in_op = (cs_arr >= op_min) & (cs_arr <= op_max) if np.any(~in_op): ax.scatter( - cs_arr[~in_op], cf_arr[~in_op], s=200, color="#D62728", edgecolors="white", linewidth=1.5, zorder=5, alpha=0.5 + cs_arr[~in_op], cf_arr[~in_op], s=200, color="#D62728", edgecolors="white", linewidth=1.5, zorder=5, alpha=0.45 ) if np.any(in_op): ax.scatter( cs_arr[in_op], cf_arr[in_op], s=350, color="#D62728", edgecolors="white", linewidth=2, zorder=6, marker="D" ) - # Annotate critical intersections inside operating range with spread offsets - op_speeds = cs_arr[in_op] - op_freqs = cf_arr[in_op] - op_mlabels = np.array(critical_mode_labels)[in_op] - sort_idx = np.argsort(op_freqs) - n_labels = len(sort_idx) - for rank, si in enumerate(sort_idx): - # Spread annotations vertically: alternate left/right, increasing vertical offset + # Annotate critical intersections inside operating range with well-separated offsets + op_s, op_f, op_m = cs_arr[in_op], cf_arr[in_op], np.array(critical_mlabels)[in_op] + order = np.argsort(op_f) + n = len(order) + for rank, si in enumerate(order): + # Alternate left/right with increasing vertical spread to avoid overlap sign = 1 if rank % 2 == 0 else -1 - dx = sign * 25 - dy = -20 + rank * (40 / max(n_labels - 1, 1)) + dx = sign * 35 + dy = -30 + rank * (60 / max(n - 1, 1)) ax.annotate( - op_mlabels[si], - xy=(op_speeds[si], op_freqs[si]), + op_m[si], + xy=(op_s[si], op_f[si]), xytext=(dx, dy), textcoords="offset points", fontsize=11, color="#B71C1C", fontweight="bold", - arrowprops={"arrowstyle": "-", "color": "#B71C1C", "lw": 0.8}, + arrowprops={"arrowstyle": "-|>", "color": "#B71C1C", "lw": 1.0, "shrinkB": 4}, zorder=7, bbox={ - "boxstyle": "round,pad=0.2", - "facecolor": "white", + "boxstyle": "round,pad=0.25", + "facecolor": "#FFF3F3", "edgecolor": "#B71C1C", - "alpha": 0.85, - "linewidth": 0.6, + "alpha": 0.9, + "linewidth": 0.7, }, ) @@ -159,7 +171,10 @@ ax.yaxis.grid(True, alpha=0.15, linewidth=0.6, color="#CCCCCC") ax.xaxis.grid(True, alpha=0.08, linewidth=0.4, color="#CCCCCC") -# Inset legend for better layout balance +# Format x-axis with thousand separator for readability +ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x:,.0f}")) + +# Compact two-column legend positioned in upper-left to avoid covering data eo_handle = Line2D([0], [0], color="#AAAAAA", linewidth=1.8, linestyle=(0, (8, 4)), alpha=0.6) crit_outside = Line2D( [0], [0], marker="o", color="none", markerfacecolor="#D62728", markeredgecolor="white", markersize=10, alpha=0.5 @@ -169,21 +184,26 @@ ) op_handle = Patch(facecolor="#306998", alpha=0.12, edgecolor="none") -handles = ax.get_legend_handles_labels()[0][:5] -labels = ax.get_legend_handles_labels()[1][:5] -handles += [eo_handle, crit_outside, crit_inside, op_handle] -labels += ["Engine Order (1×–3×)", "Critical Speed", "Critical (in op. range)", "Operating Range"] +handles = [Line2D([0], [0], color=c, linewidth=2.8) for c in mode_colors] + [ + eo_handle, + crit_outside, + crit_inside, + op_handle, +] +labels = mode_labels + ["Engine Order (1×–3×)", "Critical Speed", "Critical (op. range)", "Operating Range"] ax.legend( handles, labels, - fontsize=12, + fontsize=11, loc="upper left", - framealpha=0.95, + ncol=2, + framealpha=0.92, edgecolor="#DDDDDD", - borderpad=0.6, - labelspacing=0.5, - handlelength=1.6, + borderpad=0.5, + labelspacing=0.4, + handlelength=1.4, + columnspacing=1.0, ) plt.tight_layout() From d7e34be9a66baaf66eab507e4146da858ff81f9f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:36:08 +0000 Subject: [PATCH 9/9] chore(matplotlib): update quality score 90 and review feedback for campbell-basic --- .../implementations/matplotlib.py | 4 +- plots/campbell-basic/metadata/matplotlib.yaml | 156 +++++++++--------- 2 files changed, 78 insertions(+), 82 deletions(-) diff --git a/plots/campbell-basic/implementations/matplotlib.py b/plots/campbell-basic/implementations/matplotlib.py index 2bc88d1281..4e6846cc7e 100644 --- a/plots/campbell-basic/implementations/matplotlib.py +++ b/plots/campbell-basic/implementations/matplotlib.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: matplotlib 3.10.8 | Python 3.14.3 -Quality: 88/100 | Created: 2026-02-15 +Quality: 90/100 | Created: 2026-02-15 """ import matplotlib.pyplot as plt diff --git a/plots/campbell-basic/metadata/matplotlib.yaml b/plots/campbell-basic/metadata/matplotlib.yaml index f5659d6817..0d4c7296ee 100644 --- a/plots/campbell-basic/metadata/matplotlib.yaml +++ b/plots/campbell-basic/metadata/matplotlib.yaml @@ -1,7 +1,7 @@ library: matplotlib specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:27:45Z' +updated: '2026-02-15T21:36:08Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026882 issue: 4241 @@ -10,39 +10,40 @@ library_version: 3.10.8 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/matplotlib/plot_thumb.png preview_html: null -quality_score: 88 +quality_score: 90 review: strengths: - - 'Excellent spec compliance: all required features implemented including optional - operating range shading' - - Strong data storytelling through visual differentiation of critical speeds inside - vs. outside operating range (diamond vs. circle markers, opacity contrast, labeled - annotations) - - Realistic rotordynamics data with physically plausible mode behaviors and frequencies - - Clean visual design with removed spines, subtle grid, and custom color palette - - Well-constructed custom legend that clearly explains all 9 element types + - Excellent differentiation between critical speeds inside vs outside operating + range using marker shape, size, opacity, and annotation styling — creates strong + visual hierarchy + - Sophisticated end-of-line label de-collision algorithm prevents overlap at the + right edge + - Rotated engine order labels with white background boxes are elegant and readable + - Operating range shading with dotted boundaries is tasteful and informative + - Linear interpolation for finding exact intersection points is mathematically sound + - Comprehensive two-column legend covers all visual elements without cluttering weaknesses: - - Legend is large (9 items) and positioned in the upper-left corner where it covers - some data area; could use a more compact layout or external positioning - - Annotation labels for critical speeds inside the operating range are close together, - particularly 1st Torsional and Axial around 58-65 Hz at 3500-4000 RPM - - Code complexity could be slightly reduced in the intersection-finding and annotation-spreading - sections - image_description: 'The plot displays a Campbell Diagram with 5 natural frequency - mode curves plotted against rotational speed (0-6000 RPM) on the x-axis and frequency - (0-120 Hz) on the y-axis. The mode curves are: 1st Bending (dark blue, rising - gently then leveling), 2nd Bending (orange, declining slightly), 1st Torsional - (cyan, nearly flat around 58 Hz), Axial (purple, declining), and 3rd Bending (brown, - rising then flattening). Three gray dashed engine order lines (1x, 2x, 3x) radiate - from the origin with rotated labels. Critical speed intersections outside the - operating range are shown as semi-transparent red circles (about 10 markers), - while intersections inside the operating range (2500-4500 RPM, shaded light blue - with dotted vertical boundaries) are displayed as large red diamond markers with - labeled annotations connected by thin lines (2nd Bending, 3rd Bending, 1st Torsional, - Axial). An italic "Operating Range" label sits near the bottom of the shaded region. - A comprehensive legend in the upper left lists all 9 element types. Top and right - spines are removed, subtle grid lines are present, and the title reads "campbell-basic - · matplotlib · pyplots.ai".' + - Right-edge mode labels for Axial and 1st Torsional are vertically close despite + de-collision logic — the minimum gap could be slightly larger + - Legend font size (11pt) is smaller than the recommended 16pt in library rules, + though appropriate given 9 legend entries + - Imports from three separate matplotlib submodules (lines, patches, ticker) adds + minor verbosity + image_description: The plot displays a Campbell Diagram with 5 natural frequency + mode curves (1st Bending in dark blue, 2nd Bending in orange, 1st Torsional in + cyan, Axial in purple, 3rd Bending in brown) plotted against rotational speed + from 0 to 6,000 RPM on the x-axis and frequency from 0 to 120 Hz on the y-axis. + Three dashed gray engine order lines (1x, 2x, 3x) extend diagonally from the origin + with rotated labels on white background boxes. A light blue shaded band marks + the operating range (2,500-4,500 RPM) with dotted vertical boundary lines and + an italic "Operating Range" label at the bottom. Critical speed intersections + outside the operating range appear as semi-transparent red circles, while those + inside appear as larger red diamond markers with annotated mode labels in red-bordered + boxes connected by arrow lines. End-of-line mode labels in bold matching colors + appear at the right edge with vertical de-collision. The title reads "campbell-basic + · matplotlib · pyplots.ai" in medium weight. A two-column legend in the upper-left + distinguishes all element types. Top and right spines are removed, subtle grids + are applied, and the x-axis uses thousand separators. criteria_checklist: visual_quality: score: 27 @@ -53,34 +54,35 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis labels 20pt, ticks - 16pt, legend 12pt, annotations 11-14pt' + comment: 'All font sizes explicitly set: title 24pt, labels 20pt, ticks 16pt, + legend 11pt, annotations 10-14pt' - id: VQ-02 name: No Overlap score: 5 max: 6 passed: true - comment: Minor proximity between 1st Torsional and Axial annotations in operating - range; not severely overlapping but close + comment: De-collision logic for end-of-line labels works well; Axial and 1st + Torsional right-edge labels are close but readable - id: VQ-03 name: Element Visibility score: 6 max: 6 passed: true - comment: Line widths 2.8 for modes, markers s=200/350 for critical speeds, - well-adapted to data density + comment: Line widths of 2.8 well-suited; critical markers appropriately sized + (s=200/350) - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: Blue, orange, cyan, purple, brown palette; no red-green as sole distinguisher + comment: Blue, orange, cyan, purple, brown palette is colorblind-distinguishable - id: VQ-05 - name: Layout Balance - score: 2 + name: Layout & Canvas + score: 3 max: 4 - passed: false - comment: Large 9-item legend in upper left covers data area; some lost space + passed: true + comment: Good canvas utilization; right-edge labels extend slightly beyond + plot area - id: VQ-06 name: Axis Labels & Title score: 2 @@ -88,31 +90,30 @@ review: passed: true comment: Rotational Speed (RPM) and Frequency (Hz) with units design_excellence: - score: 15 + score: 16 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 7 max: 8 passed: true - comment: Custom palette, intentional visual hierarchy with operating range - shading and differentiated marker styles; clearly above defaults + comment: 'Near publication-ready: custom palette, rotated EO labels with white + boxes, differentiated critical speed markers' - id: DE-02 name: Visual Refinement score: 5 max: 6 passed: true - comment: Top/right spines removed, subtle grid with differentiated x/y alpha, - spine colors customized, generous whitespace + comment: Subtle grids, spines removed/thinned, generous whitespace, elegant + operating range shading - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: 'Good visual hierarchy: operating range highlighted, critical speeds - inside emphasized with large diamonds + annotations vs. small faded circles - outside' + comment: 'Clear visual hierarchy: diamond markers + annotations emphasize + critical speeds in operating range vs translucent circles outside' spec_compliance: score: 15 max: 15 @@ -122,28 +123,26 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell Diagram with natural frequency curves overlaid on - engine order excitation lines + comment: Correct Campbell Diagram with frequency vs RPM overlay - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All spec features present: 5 modes, 3 engine orders, critical speed - markers, mode labels, EO labels, clean legend, operating range shading' + comment: 'All spec features: 5 modes, 3 engine orders, critical speed markers, + labels, legend, operating range' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=RPM, Y=Hz; engine order slopes correctly computed as order/60 + comment: X=RPM, Y=Hz correctly assigned - id: SC-04 name: Title & Legend score: 3 max: 3 passed: true - comment: Title format correct; legend labels accurately describe all element - types + comment: Title format correct; comprehensive legend with all element types data_quality: score: 14 max: 15 @@ -153,22 +152,21 @@ review: score: 5 max: 6 passed: true - comment: 5 modes with varied behaviors (rising, falling, flat) demonstrating - gyroscopic effects; multiple EO crossings at different frequencies + comment: Shows modes with varied gyroscopic behavior; both in-range and out-of-range + critical speeds visible - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Authentic rotordynamics scenario with plausible mode names, frequency - ranges, and operating RPM band + comment: Authentic rotordynamics scenario with standard engineering mode names - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: 'Realistic values: 18-92 Hz base frequencies, 0-6000 RPM range, 2500-4500 - RPM operating range' + comment: Baseline frequencies 18-92 Hz and 0-6000 RPM are realistic for rotating + machinery code_quality: score: 9 max: 10 @@ -178,35 +176,34 @@ review: score: 3 max: 3 passed: true - comment: Linear imports-data-plot-save structure, no functions/classes + comment: 'Flat script: imports, data, plot, save' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: Fully deterministic data (mathematical functions, no randomness) + comment: Deterministic data from formulas, no randomness - id: CQ-03 name: Clean Imports - score: 2 + score: 1 max: 2 passed: true - comment: 'All imports used: plt, np, Line2D for legend handles, Patch for - operating range handle' + comment: All imports used; minor verbosity importing from separate matplotlib + submodules - id: CQ-04 name: Code Elegance - score: 1 + score: 2 max: 2 passed: true - comment: Clean overall but intersection-finding and annotation spread logic - add moderate complexity + comment: Clean intersection-finding with linear interpolation; Pythonic loops - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png with dpi=300 - library_features: - score: 8 + comment: Saves as plot.png, dpi=300, bbox_inches=tight + library_mastery: + score: 9 max: 10 items: - id: LM-01 @@ -214,15 +211,14 @@ review: score: 5 max: 5 passed: true - comment: Proper Axes-level API throughout, annotate with arrowprops/textcoords, - axvspan/axvline, scatter with distinct markers + comment: Axes methods throughout, FuncFormatter, axvspan, proper tight_layout - id: LM-02 name: Distinctive Features - score: 3 + score: 4 max: 5 passed: true - comment: Custom legend via proxy artists (Line2D, Patch), annotate with rotation_mode - anchor, axvspan shading + comment: Custom legend with Line2D/Patch handles, FuncFormatter, rotation_mode + anchor, axvspan verdict: APPROVED impl_tags: dependencies: []