From b4310425d1c5452f00bd3e6119707e7573ed046c 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(plotnine): implement campbell-basic --- .../implementations/plotnine.py | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 plots/campbell-basic/implementations/plotnine.py diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py new file mode 100644 index 0000000000..8055068b0e --- /dev/null +++ b/plots/campbell-basic/implementations/plotnine.py @@ -0,0 +1,156 @@ +"""pyplots.ai +campbell-basic: Campbell Diagram +Library: plotnine | Python 3.13 +Quality: pending | Created: 2026-02-15 +""" + +import numpy as np +import pandas as pd +from plotnine import ( + aes, + element_blank, + element_line, + element_text, + geom_line, + geom_point, + geom_text, + ggplot, + labs, + scale_color_identity, + scale_linetype_identity, + scale_x_continuous, + scale_y_continuous, + theme, + theme_minimal, +) + + +# Data - Natural frequencies vs rotational speed for a rotating machine +np.random.seed(42) +speed = np.linspace(0, 6000, 80) + +# Natural frequency modes (Hz) - slight variation with speed due to gyroscopic effects +mode_1_bending = 18 + speed * 0.0008 + np.random.normal(0, 0.15, len(speed)) +mode_2_bending = 42 - speed * 0.0005 + np.random.normal(0, 0.15, len(speed)) +mode_1_torsional = 65 + speed * 0.0012 + np.random.normal(0, 0.15, len(speed)) +mode_axial = 88 + speed * 0.0003 + np.random.normal(0, 0.15, len(speed)) + +modes = { + "1st Bending": mode_1_bending, + "2nd Bending": mode_2_bending, + "1st Torsional": mode_1_torsional, + "Axial": mode_axial, +} + +# Build long-format DataFrame for natural frequency curves +records = [] +for mode_name, freq_values in modes.items(): + for s, f in zip(speed, freq_values, strict=True): + records.append({"Speed (RPM)": s, "Frequency (Hz)": f, "label": mode_name}) + +df_modes = pd.DataFrame(records) + +# Engine order lines: frequency = order * speed / 60 +engine_orders = [1, 2, 3] +eo_max_speed = 6000 +eo_records = [] +for order in engine_orders: + for s in [0, eo_max_speed]: + f = order * s / 60 + eo_records.append({"Speed (RPM)": s, "Frequency (Hz)": f, "label": f"{order}x"}) + +df_eo = pd.DataFrame(eo_records) + +# Calculate critical speed intersections (engine order line meets natural freq curve) +critical_points = [] +for order in engine_orders: + eo_freq = order * speed / 60 + for _mode_name, freq_values in modes.items(): + diff = eo_freq - freq_values + sign_changes = np.where(np.diff(np.sign(diff)))[0] + for idx in sign_changes: + s0, s1 = speed[idx], speed[idx + 1] + f0_eo, f1_eo = eo_freq[idx], eo_freq[idx + 1] + f0_mode, f1_mode = freq_values[idx], freq_values[idx + 1] + t = (f0_mode - f0_eo) / ((f1_eo - f0_eo) - (f1_mode - f0_mode)) + crit_speed = s0 + t * (s1 - s0) + crit_freq = f0_eo + t * (f1_eo - f0_eo) + if 0 < crit_speed < 6000 and 0 < crit_freq < 110: + critical_points.append({"Speed (RPM)": crit_speed, "Frequency (Hz)": crit_freq}) + +df_critical = pd.DataFrame(critical_points) + +# Colors for mode curves +mode_colors = {"1st Bending": "#306998", "2nd Bending": "#E57373", "1st Torsional": "#81C784", "Axial": "#FFB74D"} +df_modes["color"] = df_modes["label"].map(mode_colors) + +# Engine order line color +eo_color = "#9E9E9E" +df_eo["color"] = eo_color + +# Mode label positions (at right edge of each curve) +mode_label_data = [] +for mode_name, freq_values in modes.items(): + mode_label_data.append( + { + "Speed (RPM)": speed[-1] + 80, + "Frequency (Hz)": freq_values[-1], + "label": mode_name, + "color": mode_colors[mode_name], + } + ) +df_mode_labels = pd.DataFrame(mode_label_data) + +# Engine order label positions (near top of each line within plot area) +eo_label_data = [] +for order in engine_orders: + label_speed = min(100 / (order / 60) * 0.92, 5600) + label_freq = order * label_speed / 60 + eo_label_data.append( + {"Speed (RPM)": label_speed, "Frequency (Hz)": label_freq + 2.5, "label": f"{order}x", "color": eo_color} + ) +df_eo_labels = pd.DataFrame(eo_label_data) + +# Plot +plot = ( + ggplot() + # Natural frequency curves + + geom_line(df_modes, aes(x="Speed (RPM)", y="Frequency (Hz)", group="label", color="color"), size=2.2) + # Engine order lines + + geom_line( + df_eo, aes(x="Speed (RPM)", y="Frequency (Hz)", group="label", color="color"), size=1.2, linetype="dashed" + ) + # Critical speed markers + + geom_point( + df_critical, aes(x="Speed (RPM)", y="Frequency (Hz)"), color="#D32F2F", fill="#D32F2F", size=5, shape="D" + ) + # Mode labels at right edge + + geom_text( + df_mode_labels, aes(x="Speed (RPM)", y="Frequency (Hz)", label="label", color="color"), size=11, ha="left" + ) + # Engine order labels + + geom_text( + df_eo_labels, + aes(x="Speed (RPM)", y="Frequency (Hz)", label="label", color="color"), + size=11, + fontstyle="italic", + ) + + scale_color_identity() + + scale_linetype_identity() + + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 6800)) + + scale_y_continuous(breaks=range(0, 121, 20), limits=(0, 115)) + + labs(x="Rotational Speed (RPM)", y="Frequency (Hz)", title="campbell-basic · plotnine · pyplots.ai") + + theme_minimal() + + theme( + figure_size=(16, 9), + text=element_text(size=14), + axis_title=element_text(size=20), + axis_text=element_text(size=16), + plot_title=element_text(size=24, ha="center"), + panel_grid_major=element_line(color="#E0E0E0", size=0.5, alpha=0.25), + panel_grid_minor=element_blank(), + ) +) + +# Save +plot.save("plot.png", dpi=300, verbose=False) From 4df5a3ae9495e5a75b2b862f961d5a3216e27447 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(plotnine): add metadata for campbell-basic --- plots/campbell-basic/metadata/plotnine.yaml | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 plots/campbell-basic/metadata/plotnine.yaml diff --git a/plots/campbell-basic/metadata/plotnine.yaml b/plots/campbell-basic/metadata/plotnine.yaml new file mode 100644 index 0000000000..25d70ebc1a --- /dev/null +++ b/plots/campbell-basic/metadata/plotnine.yaml @@ -0,0 +1,19 @@ +# Per-library metadata for plotnine implementation of campbell-basic +# Auto-generated by impl-generate.yml + +library: plotnine +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: 22043026955 +issue: 4241 +python_version: 3.14.3 +library_version: 0.15.3 +preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot.png +preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot_thumb.png +preview_html: null +quality_score: null +review: + strengths: [] + weaknesses: [] From a1a1b0ed0247e6d9a7039318411087f7f8aa9c21 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:11:41 +0000 Subject: [PATCH 3/9] chore(plotnine): update quality score 86 and review feedback for campbell-basic --- .../implementations/plotnine.py | 6 +- plots/campbell-basic/metadata/plotnine.yaml | 236 +++++++++++++++++- 2 files changed, 232 insertions(+), 10 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 8055068b0e..74a99ea51f 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram -Library: plotnine | Python 3.13 -Quality: pending | Created: 2026-02-15 +Library: plotnine 0.15.3 | Python 3.14.3 +Quality: 86/100 | Created: 2026-02-15 """ import numpy as np diff --git a/plots/campbell-basic/metadata/plotnine.yaml b/plots/campbell-basic/metadata/plotnine.yaml index 25d70ebc1a..da2a40d9f0 100644 --- a/plots/campbell-basic/metadata/plotnine.yaml +++ b/plots/campbell-basic/metadata/plotnine.yaml @@ -1,10 +1,7 @@ -# Per-library metadata for plotnine implementation of campbell-basic -# Auto-generated by impl-generate.yml - library: plotnine specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:08:00Z' +updated: '2026-02-15T21:11:40Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026955 issue: 4241 @@ -13,7 +10,232 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot_thumb.png preview_html: null -quality_score: null +quality_score: 86 review: - strengths: [] - weaknesses: [] + strengths: + - Excellent data quality with realistic rotordynamic context and appropriate scale + values + - Clean critical speed intersection calculation using linear interpolation with + sign-change detection + - 'Good visual hierarchy: solid colored mode curves vs dashed gray EO lines vs red + diamond markers' + - Direct labeling of mode curves at right edge is effective and avoids legend clutter + - All font sizes explicitly set following library guidelines for 4800x2700 output + weaknesses: + - Missing a formal legend as requested by the spec — while direct labeling works + well, a small legend distinguishing Natural Frequency Modes from Engine Order + Lines would satisfy the spec requirement + - Color palette includes coral-red and green which may be problematic for colorblind + viewers — consider using a more colorblind-safe palette (e.g., blue, orange, purple, + brown) + - The y-axis range extends to 120 Hz when data maxes around 90 Hz, leaving unused + space at the top of the plot + - Engine order label placement could be improved — the 3x label sits very close + to the top edge, and labels could be rotated to follow the line angle for better + association + image_description: 'The plot shows a Campbell Diagram (interference diagram) for + rotating machinery analysis. Four natural frequency curves are displayed as solid + colored lines: 1st Bending (dark blue, ~18-23 Hz, slightly increasing), 2nd Bending + (coral/salmon, ~43-39 Hz, slightly decreasing), 1st Torsional (green, ~65-73 Hz, + increasing), and Axial (orange/yellow, ~88-90 Hz, nearly flat). Three engine order + excitation lines (1x, 2x, 3x) are shown as gray dashed diagonal lines originating + from the origin. Red diamond markers indicate critical speed intersections where + engine order lines cross natural frequency curves. Mode labels are positioned + at the right edge of each curve, and engine order labels (1x, 2x, 3x) appear in + italic gray text near the top of each line. The title "campbell-basic · plotnine + · pyplots.ai" is centered at the top. X-axis shows "Rotational Speed (RPM)" from + 0-6000, Y-axis shows "Frequency (Hz)" from 0-120. The background uses a minimal + theme with subtle light gray grid lines.' + criteria_checklist: + visual_quality: + score: 28 + max: 30 + items: + - id: VQ-01 + name: Text Legibility + score: 8 + max: 8 + passed: true + comment: 'All font sizes explicitly set: title 24pt, axis titles 20pt, axis + text 16pt, base 14pt. All text clearly readable.' + - id: VQ-02 + name: No Overlap + score: 6 + max: 6 + passed: true + comment: No overlapping text elements. Mode labels well-positioned at right + edge, EO labels well-spaced. + - id: VQ-03 + name: Element Visibility + score: 6 + max: 6 + passed: true + comment: Mode lines at size 2.2, EO lines at 1.2, critical markers at size + 5 — all clearly visible. + - id: VQ-04 + name: Color Accessibility + score: 3 + max: 4 + passed: false + comment: Coral and green modes could be confused by deuteranopic viewers. + Not purely red-green dependent due to lightness differences. + - id: VQ-05 + name: Layout & Canvas + score: 3 + max: 4 + passed: false + comment: Good 16:9 layout. Slight wasted space at top (120 Hz limit when data + maxes at ~90 Hz). + - id: VQ-06 + name: Axis Labels & Title + score: 2 + max: 2 + passed: true + comment: 'Descriptive labels with units: Rotational Speed (RPM) and Frequency + (Hz).' + design_excellence: + score: 13 + max: 20 + items: + - id: DE-01 + name: Aesthetic Sophistication + score: 5 + max: 8 + passed: false + comment: Thoughtful custom palette, gray EO lines, red diamond markers, italic + EO labels. Above defaults but not publication-ready. + - id: DE-02 + name: Visual Refinement + score: 4 + max: 6 + passed: false + comment: Minimal theme with subtle grid (alpha=0.25), minor grid removed. + Could benefit from further tick/spine refinement. + - id: DE-03 + name: Data Storytelling + score: 4 + max: 6 + passed: false + comment: Red diamonds draw attention to critical speeds. Direct labeling and + line style separation create good visual hierarchy. + spec_compliance: + score: 13 + max: 15 + items: + - id: SC-01 + name: Plot Type + score: 5 + max: 5 + passed: true + comment: Correct Campbell diagram with natural frequency curves, engine order + lines, and critical speed markers. + - id: SC-02 + name: Required Features + score: 3 + max: 4 + passed: false + comment: All major features present. Missing formal legend distinguishing + mode curves from engine order lines as requested by spec. + - id: SC-03 + name: Data Mapping + score: 3 + max: 3 + passed: true + comment: X=RPM, Y=Hz correctly assigned. All data visible in range. + - id: SC-04 + name: Title & Legend + score: 2 + max: 3 + passed: false + comment: Title format correct. No formal legend present — direct labels used + instead. + data_quality: + score: 15 + max: 15 + items: + - id: DQ-01 + name: Feature Coverage + score: 6 + max: 6 + passed: true + comment: Shows 4 modes with realistic gyroscopic effects, 3 EO lines, multiple + critical speed intersections. + - id: DQ-02 + name: Realistic Context + score: 5 + max: 5 + passed: true + comment: Rotating machinery context with realistic mode names and behaviors. + - id: DQ-03 + name: Appropriate Scale + score: 4 + max: 4 + passed: true + comment: Speed range 0-6000 RPM and frequencies 18-90 Hz realistic for industrial + rotating equipment. + code_quality: + score: 10 + max: 10 + items: + - id: CQ-01 + name: KISS Structure + score: 3 + max: 3 + passed: true + comment: Clean Imports → Data → Plot → Save structure. No functions or classes. + - id: CQ-02 + name: Reproducibility + score: 2 + max: 2 + passed: true + comment: np.random.seed(42) set. + - id: CQ-03 + name: Clean Imports + score: 2 + max: 2 + passed: true + comment: All imports used, no unused imports. + - id: CQ-04 + name: Code Elegance + score: 2 + max: 2 + passed: true + comment: Appropriate complexity. Linear interpolation for critical speed calculation + is well-implemented. + - id: CQ-05 + name: Output & API + score: 1 + max: 1 + passed: true + comment: Saves as plot.png, no deprecated functions. + library_features: + score: 7 + max: 10 + items: + - id: LM-01 + name: Idiomatic Usage + score: 4 + max: 5 + passed: false + comment: Good grammar-of-graphics layer composition with scale_color_identity + and separate DataFrames per geom. + - id: LM-02 + name: Distinctive Features + score: 3 + max: 5 + passed: false + comment: 'Uses ggplot-specific patterns: layer composition, scale_color_identity, + fontstyle in geom_text. Somewhat generic ggplot usage.' + verdict: REJECTED +impl_tags: + dependencies: [] + techniques: + - layer-composition + - annotations + patterns: + - data-generation + - iteration-over-groups + dataprep: + - interpolation + styling: + - grid-styling From 3fe33708d761810c11e29dd5b65b39af16e87be2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:19:31 +0000 Subject: [PATCH 4/9] fix(plotnine): address review feedback for campbell-basic Attempt 1/3 - fixes based on AI review --- .../implementations/plotnine.py | 187 ++++++++++++------ 1 file changed, 124 insertions(+), 63 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 74a99ea51f..817fec1df2 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 Quality: 86/100 | Created: 2026-02-15 @@ -8,16 +8,20 @@ import pandas as pd from plotnine import ( aes, + annotate, element_blank, element_line, + element_rect, element_text, geom_line, geom_point, geom_text, ggplot, + guide_legend, + guides, labs, - scale_color_identity, - scale_linetype_identity, + scale_color_manual, + scale_linetype_manual, scale_x_continuous, scale_y_continuous, theme, @@ -42,22 +46,24 @@ "Axial": mode_axial, } +# Colorblind-safe palette (blue, amber, purple, teal) - all distinguishable +mode_colors = {"1st Bending": "#306998", "2nd Bending": "#E69F00", "1st Torsional": "#882D9E", "Axial": "#009E73"} +eo_color = "#757575" + # Build long-format DataFrame for natural frequency curves records = [] for mode_name, freq_values in modes.items(): for s, f in zip(speed, freq_values, strict=True): - records.append({"Speed (RPM)": s, "Frequency (Hz)": f, "label": mode_name}) + records.append({"Speed": s, "Frequency": f, "Mode": mode_name}) df_modes = pd.DataFrame(records) # Engine order lines: frequency = order * speed / 60 engine_orders = [1, 2, 3] -eo_max_speed = 6000 eo_records = [] for order in engine_orders: - for s in [0, eo_max_speed]: - f = order * s / 60 - eo_records.append({"Speed (RPM)": s, "Frequency (Hz)": f, "label": f"{order}x"}) + for s in np.linspace(0, 6000, 80): + eo_records.append({"Speed": s, "Frequency": order * s / 60, "Mode": f"{order}x Engine Order"}) df_eo = pd.DataFrame(eo_records) @@ -65,7 +71,7 @@ critical_points = [] for order in engine_orders: eo_freq = order * speed / 60 - for _mode_name, freq_values in modes.items(): + for mode_name, freq_values in modes.items(): diff = eo_freq - freq_values sign_changes = np.where(np.diff(np.sign(diff)))[0] for idx in sign_changes: @@ -75,82 +81,137 @@ t = (f0_mode - f0_eo) / ((f1_eo - f0_eo) - (f1_mode - f0_mode)) crit_speed = s0 + t * (s1 - s0) crit_freq = f0_eo + t * (f1_eo - f0_eo) - if 0 < crit_speed < 6000 and 0 < crit_freq < 110: - critical_points.append({"Speed (RPM)": crit_speed, "Frequency (Hz)": crit_freq}) + if 0 < crit_speed < 6000 and 0 < crit_freq < 100: + critical_points.append( + {"Speed": crit_speed, "Frequency": crit_freq, "order": f"{order}x", "mode_name": mode_name} + ) df_critical = pd.DataFrame(critical_points) -# Colors for mode curves -mode_colors = {"1st Bending": "#306998", "2nd Bending": "#E57373", "1st Torsional": "#81C784", "Axial": "#FFB74D"} -df_modes["color"] = df_modes["label"].map(mode_colors) - -# Engine order line color -eo_color = "#9E9E9E" -df_eo["color"] = eo_color - -# Mode label positions (at right edge of each curve) -mode_label_data = [] -for mode_name, freq_values in modes.items(): - mode_label_data.append( - { - "Speed (RPM)": speed[-1] + 80, - "Frequency (Hz)": freq_values[-1], - "label": mode_name, - "color": mode_colors[mode_name], - } - ) -df_mode_labels = pd.DataFrame(mode_label_data) - -# Engine order label positions (near top of each line within plot area) +# Engine order labels positioned along the lines eo_label_data = [] for order in engine_orders: - label_speed = min(100 / (order / 60) * 0.92, 5600) - label_freq = order * label_speed / 60 - eo_label_data.append( - {"Speed (RPM)": label_speed, "Frequency (Hz)": label_freq + 2.5, "label": f"{order}x", "color": eo_color} - ) + label_speed = min(92 / (order / 60) * 0.85, 5200) + eo_label_data.append({"Speed": label_speed, "Frequency": order * label_speed / 60 + 2.5, "label": f"{order}x"}) df_eo_labels = pd.DataFrame(eo_label_data) +# Storytelling: find the 1x/1st Bending critical speed (most operationally significant) +annot_speed = annot_freq = None +if len(df_critical) > 0: + annot_row = df_critical[(df_critical["order"] == "1x") & (df_critical["mode_name"] == "1st Bending")] + if len(annot_row) > 0: + annot_speed = annot_row.iloc[0]["Speed"] + annot_freq = annot_row.iloc[0]["Frequency"] + +# Build legend mappings (modes + one representative EO entry) +all_mode_names = list(mode_colors.keys()) +eo_names = [f"{o}x Engine Order" for o in engine_orders] + +color_values = {**mode_colors} +for name in eo_names: + color_values[name] = eo_color + +linetype_values = dict.fromkeys(all_mode_names, "solid") +for name in eo_names: + linetype_values[name] = "dashed" + +legend_breaks = all_mode_names + eo_names[:1] +legend_labels = all_mode_names + ["Engine Order (1x, 2x, 3x)"] + +# Combine mode and EO data for unified legend via scale_color_manual +df_all_lines = pd.concat([df_modes, df_eo], ignore_index=True) + # Plot plot = ( - ggplot() - # Natural frequency curves - + geom_line(df_modes, aes(x="Speed (RPM)", y="Frequency (Hz)", group="label", color="color"), size=2.2) - # Engine order lines - + geom_line( - df_eo, aes(x="Speed (RPM)", y="Frequency (Hz)", group="label", color="color"), size=1.2, linetype="dashed" - ) + ggplot(df_all_lines, aes(x="Speed", y="Frequency", color="Mode", linetype="Mode", group="Mode")) + + geom_line(size=1.8) # Critical speed markers + geom_point( - df_critical, aes(x="Speed (RPM)", y="Frequency (Hz)"), color="#D32F2F", fill="#D32F2F", size=5, shape="D" - ) - # Mode labels at right edge - + geom_text( - df_mode_labels, aes(x="Speed (RPM)", y="Frequency (Hz)", label="label", color="color"), size=11, ha="left" + df_critical, + aes(x="Speed", y="Frequency"), + color="#C62828", + fill="#EF5350", + size=5, + shape="D", + stroke=0.8, + inherit_aes=False, ) # Engine order labels + geom_text( df_eo_labels, - aes(x="Speed (RPM)", y="Frequency (Hz)", label="label", color="color"), - size=11, + aes(x="Speed", y="Frequency", label="label"), + color=eo_color, + size=10, fontstyle="italic", + inherit_aes=False, ) - + scale_color_identity() - + scale_linetype_identity() - + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 6800)) - + scale_y_continuous(breaks=range(0, 121, 20), limits=(0, 115)) - + labs(x="Rotational Speed (RPM)", y="Frequency (Hz)", title="campbell-basic · plotnine · pyplots.ai") - + theme_minimal() + # Scales for legend + + scale_color_manual(values=color_values, breaks=legend_breaks, labels=legend_labels, name=" ") + + scale_linetype_manual(values=linetype_values, breaks=legend_breaks, labels=legend_labels, name=" ") + + guides(color=guide_legend(override_aes={"size": 1.5}), linetype=guide_legend()) + + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 7200)) + + scale_y_continuous(breaks=range(0, 101, 10), limits=(-5, 102)) + + labs(x="Rotational Speed (RPM)", y="Frequency (Hz)", title="campbell-basic \u00b7 plotnine \u00b7 pyplots.ai") + + theme_minimal(base_size=14) + theme( figure_size=(16, 9), - text=element_text(size=14), - axis_title=element_text(size=20), - axis_text=element_text(size=16), - plot_title=element_text(size=24, ha="center"), - panel_grid_major=element_line(color="#E0E0E0", size=0.5, alpha=0.25), + text=element_text(size=14, color="#333333"), + axis_title=element_text(size=20, face="bold", color="#222222"), + axis_text=element_text(size=16, color="#555555"), + plot_title=element_text(size=24, ha="center", face="bold", color="#1a1a1a"), + legend_text=element_text(size=14), + legend_title=element_text(size=1, color="white"), + legend_position="bottom", + legend_direction="horizontal", + legend_background=element_rect(fill="white", alpha=0.85, color="#DDDDDD", size=0.3), + legend_key_width=40, + panel_grid_major=element_line(color="#E0E0E0", size=0.3), panel_grid_minor=element_blank(), + plot_background=element_rect(fill="white", color="white"), + panel_background=element_rect(fill="#FAFAFA", color="#EEEEEE", size=0.3), + axis_line=element_line(color="#BBBBBB", size=0.5), ) ) +# Add mode labels at right edge with matching colors +for mode_name, freq_values in modes.items(): + df_label = pd.DataFrame([{"Speed": speed[-1] + 100, "Frequency": freq_values[-1], "label": mode_name}]) + plot = plot + geom_text( + df_label, + aes(x="Speed", y="Frequency", label="label"), + color=mode_colors[mode_name], + size=10, + ha="left", + fontweight="bold", + inherit_aes=False, + ) + +# Add critical speed annotation for storytelling +if annot_speed is not None: + plot = ( + plot + + annotate( + "segment", + x=annot_speed, + xend=annot_speed, + y=0, + yend=annot_freq, + color="#C62828", + linetype="dotted", + size=0.6, + alpha=0.5, + ) + + annotate( + "text", + x=annot_speed, + y=-3, + label=f"{int(round(annot_speed))} RPM", + color="#C62828", + size=8, + ha="center", + fontstyle="italic", + ) + ) + # Save plot.save("plot.png", dpi=300, verbose=False) From 28d2282fc309808cb3eee023aa65e21fd8a66c0d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:23:35 +0000 Subject: [PATCH 5/9] chore(plotnine): update quality score 85 and review feedback for campbell-basic --- .../implementations/plotnine.py | 4 +- plots/campbell-basic/metadata/plotnine.yaml | 204 +++++++++--------- 2 files changed, 103 insertions(+), 105 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 817fec1df2..6af25fbac2 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 86/100 | Created: 2026-02-15 +Quality: 85/100 | Created: 2026-02-15 """ import numpy as np diff --git a/plots/campbell-basic/metadata/plotnine.yaml b/plots/campbell-basic/metadata/plotnine.yaml index da2a40d9f0..fcb040e980 100644 --- a/plots/campbell-basic/metadata/plotnine.yaml +++ b/plots/campbell-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:11:40Z' +updated: '2026-02-15T21:23:35Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026955 issue: 4241 @@ -10,45 +10,43 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 86 +quality_score: 85 review: strengths: - - Excellent data quality with realistic rotordynamic context and appropriate scale - values - - Clean critical speed intersection calculation using linear interpolation with - sign-change detection - - 'Good visual hierarchy: solid colored mode curves vs dashed gray EO lines vs red - diamond markers' - - Direct labeling of mode curves at right edge is effective and avoids legend clutter - - All font sizes explicitly set following library guidelines for 4800x2700 output + - Colorblind-safe custom palette with strong color differentiation across all four + modes + - Critical speed intersection calculation using sign-change interpolation is mathematically + sound + - 1127 RPM annotation creates effective data storytelling with a clear focal point + - Clean legend design consolidating 3 engine order lines into one entry + - Idiomatic plotnine grammar-of-graphics composition with proper aes mapping and + scale layering weaknesses: - - Missing a formal legend as requested by the spec — while direct labeling works - well, a small legend distinguishing Natural Frequency Modes from Engine Order - Lines would satisfy the spec requirement - - Color palette includes coral-red and green which may be problematic for colorblind - viewers — consider using a more colorblind-safe palette (e.g., blue, orange, purple, - brown) - - The y-axis range extends to 120 Hz when data maxes around 90 Hz, leaving unused - space at the top of the plot - - Engine order label placement could be improved — the 3x label sits very close - to the top edge, and labels could be rotated to follow the line angle for better - association - image_description: 'The plot shows a Campbell Diagram (interference diagram) for - rotating machinery analysis. Four natural frequency curves are displayed as solid - colored lines: 1st Bending (dark blue, ~18-23 Hz, slightly increasing), 2nd Bending - (coral/salmon, ~43-39 Hz, slightly decreasing), 1st Torsional (green, ~65-73 Hz, - increasing), and Axial (orange/yellow, ~88-90 Hz, nearly flat). Three engine order - excitation lines (1x, 2x, 3x) are shown as gray dashed diagonal lines originating - from the origin. Red diamond markers indicate critical speed intersections where - engine order lines cross natural frequency curves. Mode labels are positioned - at the right edge of each curve, and engine order labels (1x, 2x, 3x) appear in - italic gray text near the top of each line. The title "campbell-basic · plotnine - · pyplots.ai" is centered at the top. X-axis shows "Rotational Speed (RPM)" from - 0-6000, Y-axis shows "Frequency (Hz)" from 0-120. The background uses a minimal - theme with subtle light gray grid lines.' + - Right-edge mode labels push x-axis to 7200 creating asymmetric layout; consider + positioning labels within the plot area or using a secondary legend + - Engine order labels (3x, 2x) partially overlap with mode lines making them hard + to read + - Legend title suppression uses a hack (size=1, white color) rather than a clean + approach + - Only 4 modes when spec suggests 4-5; adding a 5th mode would improve feature coverage + image_description: 'The plot displays a Campbell Diagram for rotating machinery + analysis. Four natural frequency mode curves are drawn as thick solid lines: 1st + Bending (dark blue, ~18-23 Hz), 2nd Bending (amber/gold, ~42-39 Hz declining), + 1st Torsional (purple, ~65-72 Hz rising), and Axial (teal/green, ~88-90 Hz nearly + flat). Three gray dashed engine order lines (1x, 2x, 3x) radiate from the origin + with increasing slopes. Red diamond markers with pink fill highlight critical + speed intersections where engine order lines cross natural frequency curves (~10 + intersection points visible). Mode names are labeled in bold matching colors at + the right edge of the plot. Engine order labels (1x, 2x, 3x) appear in gray italic + along each diagonal line. A dotted red vertical line drops from the 1x/1st Bending + intersection down to the x-axis with an annotation reading "1127 RPM" in red italic, + highlighting the most operationally significant critical speed. The background + is light gray (#FAFAFA) with subtle grid lines. A horizontal legend at the bottom + lists all four modes with colored solid line swatches plus one dashed gray "Engine + Order (1x, 2x, 3x)" entry.' criteria_checklist: visual_quality: - score: 28 + score: 26 max: 30 items: - id: VQ-01 @@ -56,70 +54,69 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis titles 20pt, axis - text 16pt, base 14pt. All text clearly readable.' + comment: 'All font sizes explicitly set: title 24pt, axis titles 20pt bold, + tick labels 16pt, legend 14pt' - id: VQ-02 name: No Overlap - score: 6 + score: 5 max: 6 - passed: true - comment: No overlapping text elements. Mode labels well-positioned at right - edge, EO labels well-spaced. + passed: false + comment: 3x engine order label partially overlaps with Axial mode line near + 1300 RPM - id: VQ-03 name: Element Visibility - score: 6 + score: 5 max: 6 - passed: true - comment: Mode lines at size 2.2, EO lines at 1.2, critical markers at size - 5 — all clearly visible. + passed: false + comment: Lines thick at 1.8, diamond markers visible. Engine order labels + slightly faint against gray dashed lines - id: VQ-04 name: Color Accessibility - score: 3 + score: 4 max: 4 - passed: false - comment: Coral and green modes could be confused by deuteranopic viewers. - Not purely red-green dependent due to lightness differences. + passed: true + comment: 'Colorblind-safe palette: blue, amber, purple, teal. No red-green + distinction issues' - id: VQ-05 - name: Layout & Canvas - score: 3 + name: Layout Balance + score: 2 max: 4 passed: false - comment: Good 16:9 layout. Slight wasted space at top (120 Hz limit when data - maxes at ~90 Hz). + comment: x-axis extended to 7200 for right-edge labels creates asymmetric + whitespace - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: 'Descriptive labels with units: Rotational Speed (RPM) and Frequency - (Hz).' + comment: Rotational Speed (RPM) and Frequency (Hz) - descriptive with units design_excellence: - score: 13 + score: 14 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 5 + score: 6 max: 8 - passed: false - comment: Thoughtful custom palette, gray EO lines, red diamond markers, italic - EO labels. Above defaults but not publication-ready. + passed: true + comment: Custom palette, styled panel, intentional typography hierarchy, solid/dashed + distinction - id: DE-02 name: Visual Refinement score: 4 max: 6 - passed: false - comment: Minimal theme with subtle grid (alpha=0.25), minor grid removed. - Could benefit from further tick/spine refinement. + passed: true + comment: Minor grid disabled, subtle major grid, panel background customized. + Legend title hack inelegant - id: DE-03 name: Data Storytelling score: 4 max: 6 - passed: false - comment: Red diamonds draw attention to critical speeds. Direct labeling and - line style separation create good visual hierarchy. + passed: true + comment: 1127 RPM annotation creates focal point. Diamond markers and right-edge + labels build visual hierarchy spec_compliance: - score: 13 + score: 14 max: 15 items: - id: SC-01 @@ -127,54 +124,54 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell diagram with natural frequency curves, engine order - lines, and critical speed markers. + comment: Correct Campbell diagram with frequency curves, engine order lines, + and critical speed markers - id: SC-02 name: Required Features score: 3 max: 4 passed: false - comment: All major features present. Missing formal legend distinguishing - mode curves from engine order lines as requested by spec. + comment: All major features present. Only 4 modes vs suggested 4-5. Optional + zone shading not implemented - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=RPM, Y=Hz correctly assigned. All data visible in range. + comment: X=Speed (RPM), Y=Frequency (Hz) correctly assigned - id: SC-04 - name: Title & Legend - score: 2 + name: Title Format + score: 3 max: 3 - passed: false - comment: Title format correct. No formal legend present — direct labels used - instead. + passed: true + comment: campbell-basic · plotnine · pyplots.ai correct. Legend consolidates + EO lines cleanly data_quality: - score: 15 + score: 14 max: 15 items: - id: DQ-01 name: Feature Coverage - score: 6 + score: 5 max: 6 - passed: true - comment: Shows 4 modes with realistic gyroscopic effects, 3 EO lines, multiple - critical speed intersections. + passed: false + comment: Shows modes with different speed behaviors. Could show a 5th mode + for fuller coverage - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Rotating machinery context with realistic mode names and behaviors. + comment: Real rotating machinery scenario with proper mode names - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Speed range 0-6000 RPM and frequencies 18-90 Hz realistic for industrial - rotating equipment. + comment: Frequencies 18-90 Hz, speed 0-6000 RPM - realistic turbomachinery + values code_quality: - score: 10 + score: 9 max: 10 items: - id: CQ-01 @@ -182,56 +179,57 @@ review: score: 3 max: 3 passed: true - comment: Clean Imports → Data → Plot → Save structure. No functions or classes. + comment: 'Linear flow: imports, data, plot, save. No functions or classes' - id: CQ-02 name: Reproducibility score: 2 max: 2 passed: true - comment: np.random.seed(42) set. + comment: np.random.seed(42) set - id: CQ-03 name: Clean Imports score: 2 max: 2 passed: true - comment: All imports used, no unused imports. + comment: All imports used - id: CQ-04 name: Code Elegance - score: 2 + score: 1 max: 2 - passed: true - comment: Appropriate complexity. Linear interpolation for critical speed calculation - is well-implemented. + passed: false + comment: Loop-based record construction verbose. Legend title hidden via size=1/white + hack - 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 library_features: - score: 7 + score: 8 max: 10 items: - id: LM-01 name: Idiomatic Usage - score: 4 + score: 5 max: 5 - passed: false - comment: Good grammar-of-graphics layer composition with scale_color_identity - and separate DataFrames per geom. + passed: true + comment: 'Excellent ggplot grammar: aes mapping, scale_manual, guide_legend, + annotate layers, theme composition' - id: LM-02 name: Distinctive Features score: 3 max: 5 - passed: false - comment: 'Uses ggplot-specific patterns: layer composition, scale_color_identity, - fontstyle in geom_text. Somewhat generic ggplot usage.' + passed: true + comment: Uses guide_legend override_aes, scale_linetype_manual, annotate segment, + element_blank/rect layering verdict: REJECTED impl_tags: dependencies: [] techniques: - - layer-composition - annotations + - layer-composition + - custom-legend patterns: - data-generation - iteration-over-groups From 2d513997e99b5a5ac66e3530591d18b25072599c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:27:01 +0000 Subject: [PATCH 6/9] fix(plotnine): address review feedback for campbell-basic Attempt 2/3 - fixes based on AI review --- .../implementations/plotnine.py | 115 ++++++++---------- 1 file changed, 53 insertions(+), 62 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 6af25fbac2..b4dfc953b8 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,4 +1,4 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 Quality: 85/100 | Created: 2026-02-15 @@ -34,38 +34,43 @@ speed = np.linspace(0, 6000, 80) # Natural frequency modes (Hz) - slight variation with speed due to gyroscopic effects -mode_1_bending = 18 + speed * 0.0008 + np.random.normal(0, 0.15, len(speed)) -mode_2_bending = 42 - speed * 0.0005 + np.random.normal(0, 0.15, len(speed)) -mode_1_torsional = 65 + speed * 0.0012 + np.random.normal(0, 0.15, len(speed)) -mode_axial = 88 + speed * 0.0003 + np.random.normal(0, 0.15, len(speed)) - modes = { - "1st Bending": mode_1_bending, - "2nd Bending": mode_2_bending, - "1st Torsional": mode_1_torsional, - "Axial": mode_axial, + "1st Bending": 18 + speed * 0.0008 + np.random.normal(0, 0.15, len(speed)), + "2nd Bending": 42 - speed * 0.0005 + np.random.normal(0, 0.15, len(speed)), + "1st Torsional": 55 + speed * 0.0012 + np.random.normal(0, 0.15, len(speed)), + "2nd Torsional": 72 + speed * 0.0006 + np.random.normal(0, 0.15, len(speed)), + "Axial": 88 + speed * 0.0003 + np.random.normal(0, 0.15, len(speed)), } -# Colorblind-safe palette (blue, amber, purple, teal) - all distinguishable -mode_colors = {"1st Bending": "#306998", "2nd Bending": "#E69F00", "1st Torsional": "#882D9E", "Axial": "#009E73"} -eo_color = "#757575" +# Colorblind-safe palette (blue, amber, purple, teal, brown) - all distinguishable +mode_colors = { + "1st Bending": "#306998", + "2nd Bending": "#E69F00", + "1st Torsional": "#882D9E", + "2nd Torsional": "#D55E00", + "Axial": "#009E73", +} +eo_color = "#555555" # Build long-format DataFrame for natural frequency curves -records = [] -for mode_name, freq_values in modes.items(): - for s, f in zip(speed, freq_values, strict=True): - records.append({"Speed": s, "Frequency": f, "Mode": mode_name}) - -df_modes = pd.DataFrame(records) +df_modes = pd.DataFrame( + [ + {"Speed": s, "Frequency": f, "Mode": name} + for name, freqs in modes.items() + for s, f in zip(speed, freqs, strict=True) + ] +) # Engine order lines: frequency = order * speed / 60 engine_orders = [1, 2, 3] -eo_records = [] -for order in engine_orders: - for s in np.linspace(0, 6000, 80): - eo_records.append({"Speed": s, "Frequency": order * s / 60, "Mode": f"{order}x Engine Order"}) - -df_eo = pd.DataFrame(eo_records) +eo_speed = np.linspace(0, 6000, 80) +df_eo = pd.DataFrame( + [ + {"Speed": s, "Frequency": order * s / 60, "Mode": f"{order}x Engine Order"} + for order in engine_orders + for s in eo_speed + ] +) # Calculate critical speed intersections (engine order line meets natural freq curve) critical_points = [] @@ -81,19 +86,21 @@ t = (f0_mode - f0_eo) / ((f1_eo - f0_eo) - (f1_mode - f0_mode)) crit_speed = s0 + t * (s1 - s0) crit_freq = f0_eo + t * (f1_eo - f0_eo) - if 0 < crit_speed < 6000 and 0 < crit_freq < 100: + if 0 < crit_speed < 6000 and 0 < crit_freq < 105: critical_points.append( {"Speed": crit_speed, "Frequency": crit_freq, "order": f"{order}x", "mode_name": mode_name} ) df_critical = pd.DataFrame(critical_points) -# Engine order labels positioned along the lines -eo_label_data = [] -for order in engine_orders: - label_speed = min(92 / (order / 60) * 0.85, 5200) - eo_label_data.append({"Speed": label_speed, "Frequency": order * label_speed / 60 + 2.5, "label": f"{order}x"}) -df_eo_labels = pd.DataFrame(eo_label_data) +# Engine order labels positioned along the lines - place in clear areas +eo_label_data = pd.DataFrame( + [ + {"Speed": 4800, "Frequency": 1 * 4800 / 60 + 3, "label": "1x"}, + {"Speed": 2400, "Frequency": 2 * 2400 / 60 + 3, "label": "2x"}, + {"Speed": 1600, "Frequency": 3 * 1600 / 60 + 3, "label": "3x"}, + ] +) # Storytelling: find the 1x/1st Bending critical speed (most operationally significant) annot_speed = annot_freq = None @@ -107,13 +114,8 @@ all_mode_names = list(mode_colors.keys()) eo_names = [f"{o}x Engine Order" for o in engine_orders] -color_values = {**mode_colors} -for name in eo_names: - color_values[name] = eo_color - -linetype_values = dict.fromkeys(all_mode_names, "solid") -for name in eo_names: - linetype_values[name] = "dashed" +color_values = {**mode_colors, **dict.fromkeys(eo_names, eo_color)} +linetype_values = {**dict.fromkeys(all_mode_names, "solid"), **dict.fromkeys(eo_names, "dashed")} legend_breaks = all_mode_names + eo_names[:1] legend_labels = all_mode_names + ["Engine Order (1x, 2x, 3x)"] @@ -136,21 +138,22 @@ stroke=0.8, inherit_aes=False, ) - # Engine order labels + # Engine order labels - larger and darker for better visibility + geom_text( - df_eo_labels, + eo_label_data, aes(x="Speed", y="Frequency", label="label"), - color=eo_color, - size=10, + color="#333333", + size=12, fontstyle="italic", + fontweight="bold", inherit_aes=False, ) # Scales for legend - + scale_color_manual(values=color_values, breaks=legend_breaks, labels=legend_labels, name=" ") - + scale_linetype_manual(values=linetype_values, breaks=legend_breaks, labels=legend_labels, name=" ") + + scale_color_manual(values=color_values, breaks=legend_breaks, labels=legend_labels) + + scale_linetype_manual(values=linetype_values, breaks=legend_breaks, labels=legend_labels) + guides(color=guide_legend(override_aes={"size": 1.5}), linetype=guide_legend()) - + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 7200)) - + scale_y_continuous(breaks=range(0, 101, 10), limits=(-5, 102)) + + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 6200)) + + scale_y_continuous(breaks=range(0, 111, 10), limits=(-5, 108)) + labs(x="Rotational Speed (RPM)", y="Frequency (Hz)", title="campbell-basic \u00b7 plotnine \u00b7 pyplots.ai") + theme_minimal(base_size=14) + theme( @@ -160,7 +163,7 @@ axis_text=element_text(size=16, color="#555555"), plot_title=element_text(size=24, ha="center", face="bold", color="#1a1a1a"), legend_text=element_text(size=14), - legend_title=element_text(size=1, color="white"), + legend_title=element_blank(), legend_position="bottom", legend_direction="horizontal", legend_background=element_rect(fill="white", alpha=0.85, color="#DDDDDD", size=0.3), @@ -173,19 +176,6 @@ ) ) -# Add mode labels at right edge with matching colors -for mode_name, freq_values in modes.items(): - df_label = pd.DataFrame([{"Speed": speed[-1] + 100, "Frequency": freq_values[-1], "label": mode_name}]) - plot = plot + geom_text( - df_label, - aes(x="Speed", y="Frequency", label="label"), - color=mode_colors[mode_name], - size=10, - ha="left", - fontweight="bold", - inherit_aes=False, - ) - # Add critical speed annotation for storytelling if annot_speed is not None: plot = ( @@ -207,9 +197,10 @@ y=-3, label=f"{int(round(annot_speed))} RPM", color="#C62828", - size=8, + size=9, ha="center", fontstyle="italic", + fontweight="bold", ) ) From 031eb06f37c89d1effdbef41c194fdd46c8b6529 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:30:56 +0000 Subject: [PATCH 7/9] chore(plotnine): update quality score 86 and review feedback for campbell-basic --- .../implementations/plotnine.py | 4 +- plots/campbell-basic/metadata/plotnine.yaml | 157 +++++++++--------- 2 files changed, 82 insertions(+), 79 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index b4dfc953b8..098e23536a 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,7 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 85/100 | Created: 2026-02-15 +Quality: 86/100 | Created: 2026-02-15 """ import numpy as np diff --git a/plots/campbell-basic/metadata/plotnine.yaml b/plots/campbell-basic/metadata/plotnine.yaml index fcb040e980..ba16e3611e 100644 --- a/plots/campbell-basic/metadata/plotnine.yaml +++ b/plots/campbell-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:23:35Z' +updated: '2026-02-15T21:30:55Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026955 issue: 4241 @@ -10,40 +10,42 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 85 +quality_score: 86 review: strengths: - - Colorblind-safe custom palette with strong color differentiation across all four - modes - - Critical speed intersection calculation using sign-change interpolation is mathematically - sound - - 1127 RPM annotation creates effective data storytelling with a clear focal point - - Clean legend design consolidating 3 engine order lines into one entry - - Idiomatic plotnine grammar-of-graphics composition with proper aes mapping and - scale layering + - 'Excellent spec compliance: all required Campbell Diagram features are present + (5 modes, 3 EO lines, critical speed markers, labels, legend)' + - Strong colorblind-safe palette with good color choices that are easily distinguishable + - Effective data storytelling with the 1127 RPM critical speed annotation highlighting + the most significant resonance + - Realistic rotordynamic data with proper mode names and physically plausible frequency + behavior + - Expert use of plotnine grammar of graphics with proper layering, scale mappings, + and theme customization weaknesses: - - Right-edge mode labels push x-axis to 7200 creating asymmetric layout; consider - positioning labels within the plot area or using a secondary legend - - Engine order labels (3x, 2x) partially overlap with mode lines making them hard - to read - - Legend title suppression uses a hack (size=1, white color) rather than a clean - approach - - Only 4 modes when spec suggests 4-5; adding a 5th mode would improve feature coverage - image_description: 'The plot displays a Campbell Diagram for rotating machinery - analysis. Four natural frequency mode curves are drawn as thick solid lines: 1st - Bending (dark blue, ~18-23 Hz), 2nd Bending (amber/gold, ~42-39 Hz declining), - 1st Torsional (purple, ~65-72 Hz rising), and Axial (teal/green, ~88-90 Hz nearly - flat). Three gray dashed engine order lines (1x, 2x, 3x) radiate from the origin - with increasing slopes. Red diamond markers with pink fill highlight critical - speed intersections where engine order lines cross natural frequency curves (~10 - intersection points visible). Mode names are labeled in bold matching colors at - the right edge of the plot. Engine order labels (1x, 2x, 3x) appear in gray italic - along each diagonal line. A dotted red vertical line drops from the 1x/1st Bending - intersection down to the x-axis with an annotation reading "1127 RPM" in red italic, - highlighting the most operationally significant critical speed. The background - is light gray (#FAFAFA) with subtle grid lines. A horizontal legend at the bottom - lists all four modes with colored solid line swatches plus one dashed gray "Engine - Order (1x, 2x, 3x)" entry.' + - 'Layout has some wasted vertical space: Y-axis range extends to -5 Hz mainly for + one annotation, leaving dead space below 0' + - Legend at bottom is functional but could be more compact or integrated (e.g., + direct labels on curves) + - Critical speed markers cluster in the lower-left region where multiple EO lines + converge with 1st Bending mode + - Code complexity around legend construction (breaks/labels mapping) is somewhat + verbose for a KISS script + image_description: 'The plot displays a Campbell Diagram plotting natural frequency + (Hz) on the Y-axis against rotational speed (RPM) on the X-axis, ranging from + 0-6000 RPM and 0-110 Hz. Five solid-colored natural frequency mode curves are + shown: 1st Bending (blue, ~18-23 Hz, slightly increasing), 2nd Bending (amber/yellow, + ~42-39 Hz, slightly decreasing), 1st Torsional (purple, ~55-62 Hz, increasing), + 2nd Torsional (orange, ~72-76 Hz, slightly increasing), and Axial (teal/green, + ~88-90 Hz, nearly flat). Three dashed gray engine order lines (1x, 2x, 3x) radiate + from the origin with increasing slopes, labeled in bold italic text along the + lines. Red diamond markers highlight approximately 10+ critical speed intersections + where engine order lines cross natural frequency curves. A dotted vertical red + line at approximately 1127 RPM with a red italic label annotates the most significant + critical speed (1x crossing 1st Bending). The title "campbell-basic · plotnine + · pyplots.ai" is centered and bold at the top. A horizontal legend at the bottom + lists all five modes and a consolidated "Engine Order (1x, 2x, 3x)" entry in a + white-bordered box. The background is light gray (#FAFAFA) with subtle major gridlines.' criteria_checklist: visual_quality: score: 26 @@ -54,42 +56,42 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis titles 20pt bold, + comment: 'All font sizes explicitly set: title 24pt, axis labels 20pt bold, tick labels 16pt, legend 14pt' - id: VQ-02 name: No Overlap score: 5 max: 6 - passed: false - comment: 3x engine order label partially overlaps with Axial mode line near - 1300 RPM + passed: true + comment: 'Very minor near-overlap: 3x label approaches upper boundary; some + critical speed marker clustering in lower-left' - id: VQ-03 name: Element Visibility score: 5 max: 6 - passed: false - comment: Lines thick at 1.8, diamond markers visible. Engine order labels - slightly faint against gray dashed lines + passed: true + comment: Lines thick at 1.8, diamond markers visible at size 5. Some marker + clustering near origin - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: 'Colorblind-safe palette: blue, amber, purple, teal. No red-green - distinction issues' + comment: 'Excellent colorblind-safe palette close to Wong palette: blue, amber, + purple, vermilion, green' - id: VQ-05 name: Layout Balance score: 2 max: 4 passed: false - comment: x-axis extended to 7200 for right-edge labels creates asymmetric - whitespace + comment: Negative Y-axis range (-5 to 0) mostly dead space; bottom legend + takes vertical space - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Rotational Speed (RPM) and Frequency (Hz) - descriptive with units + comment: Rotational Speed (RPM) and Frequency (Hz) with units design_excellence: score: 14 max: 20 @@ -99,24 +101,24 @@ review: score: 6 max: 8 passed: true - comment: Custom palette, styled panel, intentional typography hierarchy, solid/dashed - distinction + comment: Strong design with custom colorblind-safe palette, intentional typography + hierarchy, styled panel background - id: DE-02 name: Visual Refinement score: 4 max: 6 passed: true - comment: Minor grid disabled, subtle major grid, panel background customized. - Legend title hack inelegant + comment: 'Minor grid removed, panel background #FAFAFA, legend styled with + border/alpha, axis lines added' - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: 1127 RPM annotation creates focal point. Diamond markers and right-edge - labels build visual hierarchy + comment: 1127 RPM annotation highlights most significant critical speed; red + diamonds create visual hierarchy spec_compliance: - score: 14 + score: 15 max: 15 items: - id: SC-01 @@ -124,28 +126,28 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell diagram with frequency curves, engine order lines, - and critical speed markers + comment: Correct Campbell Diagram with natural frequency curves and engine + order excitation lines - id: SC-02 name: Required Features - score: 3 + score: 4 max: 4 - passed: false - comment: All major features present. Only 4 modes vs suggested 4-5. Optional - zone shading not implemented + passed: true + comment: 'All spec features: 5 modes, 3 EO lines, critical speed markers, + mode labels, EO labels, clean legend' - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=Speed (RPM), Y=Frequency (Hz) correctly assigned + comment: X=RPM, Y=Hz correctly assigned, all data visible - id: SC-04 - name: Title Format + name: Title & Legend score: 3 max: 3 passed: true - comment: campbell-basic · plotnine · pyplots.ai correct. Legend consolidates - EO lines cleanly + comment: Title format correct, legend clear with consolidated Engine Order + entry data_quality: score: 14 max: 15 @@ -154,22 +156,23 @@ review: name: Feature Coverage score: 5 max: 6 - passed: false - comment: Shows modes with different speed behaviors. Could show a 5th mode - for fuller coverage + passed: true + comment: Shows 5 modes with varying speed dependence, 3 engine orders, many + critical speed intersections - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real rotating machinery scenario with proper mode names + comment: Real rotordynamic scenario with proper mode names and realistic frequency + ranges - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: Frequencies 18-90 Hz, speed 0-6000 RPM - realistic turbomachinery - values + comment: 'Realistic: 0-6000 RPM, 18-90 Hz frequencies, appropriate gyroscopic + effects' code_quality: score: 9 max: 10 @@ -179,7 +182,7 @@ review: score: 3 max: 3 passed: true - comment: 'Linear flow: imports, data, plot, save. No functions or classes' + comment: Linear imports-data-plot-save structure, no functions/classes - id: CQ-02 name: Reproducibility score: 2 @@ -196,15 +199,15 @@ review: name: Code Elegance score: 1 max: 2 - passed: false - comment: Loop-based record construction verbose. Legend title hidden via size=1/white - hack + passed: true + comment: Clear but somewhat verbose legend construction with breaks/labels + mapping - id: CQ-05 name: Output & API score: 1 max: 1 passed: true - comment: Saves as plot.png, dpi=300 + comment: Saves as plot.png, no deprecated functions library_features: score: 8 max: 10 @@ -214,16 +217,16 @@ review: score: 5 max: 5 passed: true - comment: 'Excellent ggplot grammar: aes mapping, scale_manual, guide_legend, - annotate layers, theme composition' + comment: 'Expert grammar of graphics: aes mapping, layer composition, scale_color/linetype_manual, + guide_legend, annotate' - id: LM-02 name: Distinctive Features score: 3 max: 5 passed: true - comment: Uses guide_legend override_aes, scale_linetype_manual, annotate segment, - element_blank/rect layering - verdict: REJECTED + comment: Uses ggplot-style layer composition, guide_legend(override_aes), + element theming system + verdict: APPROVED impl_tags: dependencies: [] techniques: From cbb33590f9451c3a5585d7b783a003f70000e939 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:36:50 +0000 Subject: [PATCH 8/9] fix(plotnine): address review feedback for campbell-basic Attempt 3/3 - fixes based on AI review --- .../implementations/plotnine.py | 210 ++++++++++-------- 1 file changed, 114 insertions(+), 96 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 098e23536a..388383b0f0 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,7 +1,6 @@ -""" pyplots.ai +"""pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 -Quality: 86/100 | Created: 2026-02-15 """ import numpy as np @@ -9,12 +8,14 @@ from plotnine import ( aes, annotate, + coord_cartesian, element_blank, element_line, element_rect, element_text, geom_line, geom_point, + geom_rect, geom_text, ggplot, guide_legend, @@ -22,6 +23,7 @@ labs, scale_color_manual, scale_linetype_manual, + scale_size_identity, scale_x_continuous, scale_y_continuous, theme, @@ -29,30 +31,26 @@ ) -# Data - Natural frequencies vs rotational speed for a rotating machine +# Data — Natural frequencies vs rotational speed for rotating machinery np.random.seed(42) speed = np.linspace(0, 6000, 80) -# Natural frequency modes (Hz) - slight variation with speed due to gyroscopic effects +# Natural frequency modes with pronounced gyroscopic speed dependence modes = { - "1st Bending": 18 + speed * 0.0008 + np.random.normal(0, 0.15, len(speed)), - "2nd Bending": 42 - speed * 0.0005 + np.random.normal(0, 0.15, len(speed)), - "1st Torsional": 55 + speed * 0.0012 + np.random.normal(0, 0.15, len(speed)), - "2nd Torsional": 72 + speed * 0.0006 + np.random.normal(0, 0.15, len(speed)), - "Axial": 88 + speed * 0.0003 + np.random.normal(0, 0.15, len(speed)), + "1st Bending": 18 + speed * 0.0015 + np.random.normal(0, 0.12, len(speed)), + "2nd Bending": 45 - speed * 0.002 + np.random.normal(0, 0.12, len(speed)), + "1st Torsional": 52 + speed * 0.0025 + np.random.normal(0, 0.12, len(speed)), + "2nd Torsional": 75 + speed * 0.001 + np.random.normal(0, 0.12, len(speed)), + "Axial": 90 - speed * 0.0004 + np.random.normal(0, 0.12, len(speed)), } -# Colorblind-safe palette (blue, amber, purple, teal, brown) - all distinguishable -mode_colors = { - "1st Bending": "#306998", - "2nd Bending": "#E69F00", - "1st Torsional": "#882D9E", - "2nd Torsional": "#D55E00", - "Axial": "#009E73", -} -eo_color = "#555555" +# Colorblind-safe palette starting with Python Blue +palette = ["#306998", "#E69F00", "#882D9E", "#D55E00", "#009E73"] +mode_names = list(modes.keys()) +mode_colors = dict(zip(mode_names, palette, strict=True)) +eo_color = "#888888" -# Build long-format DataFrame for natural frequency curves +# Long-format DataFrame for natural frequency curves df_modes = pd.DataFrame( [ {"Speed": s, "Frequency": f, "Mode": name} @@ -61,122 +59,138 @@ ] ) -# Engine order lines: frequency = order * speed / 60 +# Engine order lines: frequency = order × speed / 60 engine_orders = [1, 2, 3] -eo_speed = np.linspace(0, 6000, 80) +eo_names = [f"{o}x EO" for o in engine_orders] df_eo = pd.DataFrame( - [ - {"Speed": s, "Frequency": order * s / 60, "Mode": f"{order}x Engine Order"} - for order in engine_orders - for s in eo_speed - ] + [{"Speed": s, "Frequency": order * s / 60, "Mode": f"{order}x EO"} for order in engine_orders for s in speed] ) -# Calculate critical speed intersections (engine order line meets natural freq curve) +# Critical speed intersections (EO line crosses natural frequency curve) critical_points = [] for order in engine_orders: eo_freq = order * speed / 60 - for mode_name, freq_values in modes.items(): + for _mode_name, freq_values in modes.items(): diff = eo_freq - freq_values sign_changes = np.where(np.diff(np.sign(diff)))[0] for idx in sign_changes: s0, s1 = speed[idx], speed[idx + 1] f0_eo, f1_eo = eo_freq[idx], eo_freq[idx + 1] - f0_mode, f1_mode = freq_values[idx], freq_values[idx + 1] - t = (f0_mode - f0_eo) / ((f1_eo - f0_eo) - (f1_mode - f0_mode)) - crit_speed = s0 + t * (s1 - s0) - crit_freq = f0_eo + t * (f1_eo - f0_eo) - if 0 < crit_speed < 6000 and 0 < crit_freq < 105: - critical_points.append( - {"Speed": crit_speed, "Frequency": crit_freq, "order": f"{order}x", "mode_name": mode_name} - ) - + f0_m, f1_m = freq_values[idx], freq_values[idx + 1] + t = (f0_m - f0_eo) / ((f1_eo - f0_eo) - (f1_m - f0_m)) + cs, cf = s0 + t * (s1 - s0), f0_eo + t * (f1_eo - f0_eo) + if 0 < cs < 6000 and 0 < cf < 110: + critical_points.append({"Speed": cs, "Frequency": cf}) df_critical = pd.DataFrame(critical_points) -# Engine order labels positioned along the lines - place in clear areas -eo_label_data = pd.DataFrame( - [ - {"Speed": 4800, "Frequency": 1 * 4800 / 60 + 3, "label": "1x"}, - {"Speed": 2400, "Frequency": 2 * 2400 / 60 + 3, "label": "2x"}, - {"Speed": 1600, "Frequency": 3 * 1600 / 60 + 3, "label": "3x"}, - ] -) - -# Storytelling: find the 1x/1st Bending critical speed (most operationally significant) +# Storytelling: 1x / 1st Bending critical speed (most operationally significant) +eo1_freq = speed / 60 +diff_1b = eo1_freq - modes["1st Bending"] +sc_idx = np.where(np.diff(np.sign(diff_1b)))[0] annot_speed = annot_freq = None -if len(df_critical) > 0: - annot_row = df_critical[(df_critical["order"] == "1x") & (df_critical["mode_name"] == "1st Bending")] - if len(annot_row) > 0: - annot_speed = annot_row.iloc[0]["Speed"] - annot_freq = annot_row.iloc[0]["Frequency"] +if len(sc_idx) > 0: + idx = sc_idx[0] + t = (modes["1st Bending"][idx] - eo1_freq[idx]) / ( + (eo1_freq[idx + 1] - eo1_freq[idx]) - (modes["1st Bending"][idx + 1] - modes["1st Bending"][idx]) + ) + annot_speed = speed[idx] + t * (speed[idx + 1] - speed[idx]) + annot_freq = eo1_freq[idx] + t * (eo1_freq[idx + 1] - eo1_freq[idx]) -# Build legend mappings (modes + one representative EO entry) -all_mode_names = list(mode_colors.keys()) -eo_names = [f"{o}x Engine Order" for o in engine_orders] +# Combine all line data and add line weight column for size differentiation +df_lines = pd.concat([df_modes, df_eo], ignore_index=True) +df_lines["_lw"] = df_lines["Mode"].apply(lambda m: 2.0 if "EO" not in m else 1.0) -color_values = {**mode_colors, **dict.fromkeys(eo_names, eo_color)} -linetype_values = {**dict.fromkeys(all_mode_names, "solid"), **dict.fromkeys(eo_names, "dashed")} +# Legend mappings — consolidated EO into one entry +color_map = {**mode_colors, **dict.fromkeys(eo_names, eo_color)} +ltype_map = {**dict.fromkeys(mode_names, "solid"), **dict.fromkeys(eo_names, "dashed")} +breaks = mode_names + eo_names[:1] +labels = mode_names + ["Engine Order (1×, 2×, 3×)"] -legend_breaks = all_mode_names + eo_names[:1] -legend_labels = all_mode_names + ["Engine Order (1x, 2x, 3x)"] +# Operating range band (nominal: 2000–4500 RPM) +df_band = pd.DataFrame([{"xmin": 2000, "xmax": 4500, "ymin": 0, "ymax": 110}]) -# Combine mode and EO data for unified legend via scale_color_manual -df_all_lines = pd.concat([df_modes, df_eo], ignore_index=True) +# EO labels positioned along lines +eo_labels = pd.DataFrame( + [ + {"Speed": 4500, "Frequency": 4500 / 60 + 3, "label": "1×"}, + {"Speed": 2200, "Frequency": 2 * 2200 / 60 + 3, "label": "2×"}, + {"Speed": 1500, "Frequency": 3 * 1500 / 60 + 3, "label": "3×"}, + ] +) -# Plot +# Plot — grammar of graphics layer composition plot = ( - ggplot(df_all_lines, aes(x="Speed", y="Frequency", color="Mode", linetype="Mode", group="Mode")) - + geom_line(size=1.8) + ggplot(df_lines, aes("Speed", "Frequency", color="Mode", linetype="Mode", group="Mode")) + # Operating range shading + + geom_rect( + df_band, + aes(xmin="xmin", xmax="xmax", ymin="ymin", ymax="ymax"), + fill="#306998", + alpha=0.04, + color="none", + inherit_aes=False, + ) + # Natural frequency + EO lines with size-identity for weight differentiation + + geom_line(aes(size="_lw")) + + scale_size_identity() # Critical speed markers + geom_point( df_critical, - aes(x="Speed", y="Frequency"), + aes("Speed", "Frequency"), color="#C62828", fill="#EF5350", - size=5, + size=4.5, shape="D", - stroke=0.8, + stroke=0.7, inherit_aes=False, + show_legend=False, ) - # Engine order labels - larger and darker for better visibility + # EO line labels + geom_text( - eo_label_data, - aes(x="Speed", y="Frequency", label="label"), - color="#333333", - size=12, + eo_labels, + aes("Speed", "Frequency", label="label"), + color="#555555", + size=11, fontstyle="italic", fontweight="bold", inherit_aes=False, + show_legend=False, ) - # Scales for legend - + scale_color_manual(values=color_values, breaks=legend_breaks, labels=legend_labels) - + scale_linetype_manual(values=linetype_values, breaks=legend_breaks, labels=legend_labels) - + guides(color=guide_legend(override_aes={"size": 1.5}), linetype=guide_legend()) - + scale_x_continuous(breaks=range(0, 7000, 1000), limits=(0, 6200)) - + scale_y_continuous(breaks=range(0, 111, 10), limits=(-5, 108)) - + labs(x="Rotational Speed (RPM)", y="Frequency (Hz)", title="campbell-basic \u00b7 plotnine \u00b7 pyplots.ai") + # Unified legend via scale_manual with custom breaks/labels + + scale_color_manual(values=color_map, breaks=breaks, labels=labels) + + scale_linetype_manual(values=ltype_map, breaks=breaks, labels=labels) + + guides(color=guide_legend(override_aes={"size": [1.8] * 5 + [1.0]}), linetype=guide_legend()) + # coord_cartesian for zoom without data removal + + scale_x_continuous(breaks=range(0, 7000, 1000)) + + scale_y_continuous(breaks=range(0, 111, 10)) + + coord_cartesian(xlim=(0, 6200), ylim=(0, 108)) + + labs(x="Rotational Speed (RPM)", y="Natural Frequency (Hz)", title="campbell-basic · plotnine · pyplots.ai") + # Publication-quality theme + theme_minimal(base_size=14) + theme( figure_size=(16, 9), - text=element_text(size=14, color="#333333"), - axis_title=element_text(size=20, face="bold", color="#222222"), - axis_text=element_text(size=16, color="#555555"), + text=element_text(family="sans-serif", color="#333333"), plot_title=element_text(size=24, ha="center", face="bold", color="#1a1a1a"), - legend_text=element_text(size=14), + axis_title_x=element_text(size=20, face="bold", color="#222222"), + axis_title_y=element_text(size=20, face="bold", color="#222222"), + axis_text=element_text(size=16, color="#555555"), + legend_text=element_text(size=13), legend_title=element_blank(), legend_position="bottom", legend_direction="horizontal", - legend_background=element_rect(fill="white", alpha=0.85, color="#DDDDDD", size=0.3), - legend_key_width=40, - panel_grid_major=element_line(color="#E0E0E0", size=0.3), + legend_background=element_rect(fill="white", alpha=0.9, color="#CCCCCC", size=0.4), + legend_key_width=35, + legend_key_height=18, + panel_grid_major=element_line(color="#E5E5E5", size=0.25), panel_grid_minor=element_blank(), plot_background=element_rect(fill="white", color="white"), - panel_background=element_rect(fill="#FAFAFA", color="#EEEEEE", size=0.3), - axis_line=element_line(color="#BBBBBB", size=0.5), + panel_background=element_rect(fill="#FAFAFA", color="#E0E0E0", size=0.3), + axis_line=element_line(color="#CCCCCC", size=0.4), + plot_margin=0.02, ) ) -# Add critical speed annotation for storytelling +# Storytelling: annotate the most significant critical speed if annot_speed is not None: plot = ( plot @@ -188,21 +202,25 @@ yend=annot_freq, color="#C62828", linetype="dotted", - size=0.6, - alpha=0.5, + size=0.7, + alpha=0.6, ) + annotate( "text", - x=annot_speed, - y=-3, - label=f"{int(round(annot_speed))} RPM", + x=annot_speed + 180, + y=annot_freq + 5, + label=f"Critical: {int(round(annot_speed))} RPM", color="#C62828", size=9, - ha="center", + ha="left", fontstyle="italic", fontweight="bold", ) ) -# Save +# Operating range label +plot = plot + annotate( + "text", x=3250, y=104, label="Operating Range", color="#306998", size=8, alpha=0.5, fontweight="bold" +) + plot.save("plot.png", dpi=300, verbose=False) From 329fe20e41e6275cfe40bf475a7ca8a15af1b663 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sun, 15 Feb 2026 21:40:57 +0000 Subject: [PATCH 9/9] chore(plotnine): update quality score 90 and review feedback for campbell-basic --- .../implementations/plotnine.py | 3 +- plots/campbell-basic/metadata/plotnine.yaml | 158 +++++++++--------- 2 files changed, 80 insertions(+), 81 deletions(-) diff --git a/plots/campbell-basic/implementations/plotnine.py b/plots/campbell-basic/implementations/plotnine.py index 388383b0f0..73d4e8fa2e 100644 --- a/plots/campbell-basic/implementations/plotnine.py +++ b/plots/campbell-basic/implementations/plotnine.py @@ -1,6 +1,7 @@ -"""pyplots.ai +""" pyplots.ai campbell-basic: Campbell Diagram Library: plotnine 0.15.3 | Python 3.14.3 +Quality: 90/100 | Created: 2026-02-15 """ import numpy as np diff --git a/plots/campbell-basic/metadata/plotnine.yaml b/plots/campbell-basic/metadata/plotnine.yaml index ba16e3611e..9fe5ff78dd 100644 --- a/plots/campbell-basic/metadata/plotnine.yaml +++ b/plots/campbell-basic/metadata/plotnine.yaml @@ -1,7 +1,7 @@ library: plotnine specification_id: campbell-basic created: '2026-02-15T21:08:00Z' -updated: '2026-02-15T21:30:55Z' +updated: '2026-02-15T21:40:57Z' generated_by: claude-opus-4-5-20251101 workflow_run: 22043026955 issue: 4241 @@ -10,45 +10,45 @@ library_version: 0.15.3 preview_url: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot.png preview_thumb: https://storage.googleapis.com/pyplots-images/plots/campbell-basic/plotnine/plot_thumb.png preview_html: null -quality_score: 86 +quality_score: 90 review: strengths: - - 'Excellent spec compliance: all required Campbell Diagram features are present - (5 modes, 3 EO lines, critical speed markers, labels, legend)' - - Strong colorblind-safe palette with good color choices that are easily distinguishable - - Effective data storytelling with the 1127 RPM critical speed annotation highlighting - the most significant resonance - - Realistic rotordynamic data with proper mode names and physically plausible frequency - behavior - - Expert use of plotnine grammar of graphics with proper layering, scale mappings, - and theme customization + - Excellent use of plotnine grammar of graphics with layered composition (operating + range band, lines, markers, annotations) + - 'Strong data storytelling with Critical: 1180 RPM annotation and vertical dotted + line creating immediate visual focus' + - Colorblind-safe palette with both color AND linetype differentiation between modes + and EO lines + - Consolidated legend entry for all three EO lines reduces clutter while maintaining + clarity + - Publication-quality typography with explicitly set font sizes at all levels + - Realistic turbomachinery data with appropriate mode names, frequency ranges, and + gyroscopic speed dependence weaknesses: - - 'Layout has some wasted vertical space: Y-axis range extends to -5 Hz mainly for - one annotation, leaving dead space below 0' - - Legend at bottom is functional but could be more compact or integrated (e.g., - direct labels on curves) - - Critical speed markers cluster in the lower-left region where multiple EO lines - converge with 1st Bending mode - - Code complexity around legend construction (breaks/labels mapping) is somewhat - verbose for a KISS script - image_description: 'The plot displays a Campbell Diagram plotting natural frequency - (Hz) on the Y-axis against rotational speed (RPM) on the X-axis, ranging from - 0-6000 RPM and 0-110 Hz. Five solid-colored natural frequency mode curves are - shown: 1st Bending (blue, ~18-23 Hz, slightly increasing), 2nd Bending (amber/yellow, - ~42-39 Hz, slightly decreasing), 1st Torsional (purple, ~55-62 Hz, increasing), - 2nd Torsional (orange, ~72-76 Hz, slightly increasing), and Axial (teal/green, - ~88-90 Hz, nearly flat). Three dashed gray engine order lines (1x, 2x, 3x) radiate - from the origin with increasing slopes, labeled in bold italic text along the - lines. Red diamond markers highlight approximately 10+ critical speed intersections - where engine order lines cross natural frequency curves. A dotted vertical red - line at approximately 1127 RPM with a red italic label annotates the most significant - critical speed (1x crossing 1st Bending). The title "campbell-basic · plotnine - · pyplots.ai" is centered and bold at the top. A horizontal legend at the bottom - lists all five modes and a consolidated "Engine Order (1x, 2x, 3x)" entry in a - white-bordered box. The background is light gray (#FAFAFA) with subtle major gridlines.' + - Operating range band is nearly invisible at alpha=0.04 — could benefit from slightly + higher opacity + - Duplicate interpolation code for 1x/1st Bending critical speed annotation repeats + the general intersection logic + - 3x EO line extends well beyond the plot useful frequency range creating visual + noise in the upper portion + image_description: 'The plot displays a Campbell diagram for rotating machinery + analysis. Five natural frequency curves are plotted against rotational speed (0–6000 + RPM): 1st Bending (dark blue, ~18 Hz baseline, slightly increasing), 2nd Bending + (amber/yellow, ~45 Hz baseline, decreasing), 1st Torsional (purple, ~52 Hz baseline, + increasing), 2nd Torsional (red-orange, ~75 Hz baseline, roughly flat), and Axial + (teal/green, ~90 Hz baseline, nearly flat). Three dashed gray engine order lines + (1×, 2×, 3×) radiate diagonally from the origin. Red diamond markers are placed + at each intersection of an EO line with a natural frequency curve (approximately + 10 critical speed points). A dotted red vertical line at ~1180 RPM with an italic + annotation "Critical: 1180 RPM" highlights the 1×/1st Bending intersection as + the most operationally significant. A very subtle blue-tinted operating range + band spans 2000–4500 RPM with a faint "Operating Range" label. The bottom legend + is horizontal with a white background and gray border, consolidating all three + EO lines into a single "Engine Order (1×, 2×, 3×)" entry. Title reads "campbell-basic + · plotnine · pyplots.ai" in bold centered text.' criteria_checklist: visual_quality: - score: 26 + score: 27 max: 30 items: - id: VQ-01 @@ -56,67 +56,67 @@ review: score: 8 max: 8 passed: true - comment: 'All font sizes explicitly set: title 24pt, axis labels 20pt bold, - tick labels 16pt, legend 14pt' + comment: 'All font sizes explicitly set: title 24pt, axis titles 20pt, ticks + 16pt, legend 13pt' - id: VQ-02 name: No Overlap score: 5 max: 6 passed: true - comment: 'Very minor near-overlap: 3x label approaches upper boundary; some - critical speed marker clustering in lower-left' + comment: 'Minor: 3x label slightly crowds 2nd Torsional line area' - id: VQ-03 name: Element Visibility - score: 5 + score: 6 max: 6 passed: true - comment: Lines thick at 1.8, diamond markers visible at size 5. Some marker - clustering near origin + comment: Mode lines weight 2.0, EO lines 1.0, diamond markers size 4.5 — excellent + differentiation - id: VQ-04 name: Color Accessibility score: 4 max: 4 passed: true - comment: 'Excellent colorblind-safe palette close to Wong palette: blue, amber, - purple, vermilion, green' + comment: Colorblind-safe palette with linetype differentiation - id: VQ-05 name: Layout Balance score: 2 max: 4 passed: false - comment: Negative Y-axis range (-5 to 0) mostly dead space; bottom legend - takes vertical space + comment: Operating range band extremely subtle (alpha=0.04); 3x EO line extends + beyond useful range; bottom legend compresses plot - id: VQ-06 name: Axis Labels & Title score: 2 max: 2 passed: true - comment: Rotational Speed (RPM) and Frequency (Hz) with units + comment: 'Descriptive with units: Rotational Speed (RPM) and Natural Frequency + (Hz)' design_excellence: - score: 14 + score: 16 max: 20 items: - id: DE-01 name: Aesthetic Sophistication - score: 6 + score: 7 max: 8 passed: true - comment: Strong design with custom colorblind-safe palette, intentional typography - hierarchy, styled panel background + comment: Custom colorblind-safe palette, intentional typography hierarchy, + refined panel background, publication-quality polish - id: DE-02 name: Visual Refinement - score: 4 + score: 5 max: 6 passed: true - comment: 'Minor grid removed, panel background #FAFAFA, legend styled with - border/alpha, axis lines added' + comment: Subtle grid, minor grid removed, panel background with border, good + whitespace; legend border slightly heavy - id: DE-03 name: Data Storytelling score: 4 max: 6 passed: true - comment: 1127 RPM annotation highlights most significant critical speed; red - diamonds create visual hierarchy + comment: 'Critical: 1180 RPM annotation with dotted vertical line creates + focal point; operating range band adds context; visual hierarchy via line + weights' spec_compliance: score: 15 max: 15 @@ -126,27 +126,26 @@ review: score: 5 max: 5 passed: true - comment: Correct Campbell Diagram with natural frequency curves and engine - order excitation lines + comment: Correct Campbell diagram with frequency curves and EO lines - id: SC-02 name: Required Features score: 4 max: 4 passed: true - comment: 'All spec features: 5 modes, 3 EO lines, critical speed markers, - mode labels, EO labels, clean legend' + comment: 5 modes, 3 EO lines, critical speed markers, mode labels, EO labels, + operating range shading - id: SC-03 name: Data Mapping score: 3 max: 3 passed: true - comment: X=RPM, Y=Hz correctly assigned, all data visible + comment: X=Speed (RPM), Y=Frequency (Hz) correctly mapped - id: SC-04 - name: Title & Legend + name: Title Format score: 3 max: 3 passed: true - comment: Title format correct, legend clear with consolidated Engine Order + comment: campbell-basic · plotnine · pyplots.ai with consolidated EO legend entry data_quality: score: 14 @@ -157,22 +156,20 @@ review: score: 5 max: 6 passed: true - comment: Shows 5 modes with varying speed dependence, 3 engine orders, many - critical speed intersections + comment: 5 modes with varying speed dependence, multiple intersections; could + show more dramatic gyroscopic variation - id: DQ-02 name: Realistic Context score: 5 max: 5 passed: true - comment: Real rotordynamic scenario with proper mode names and realistic frequency - ranges + comment: Real turbomachinery terminology and plausible frequency ranges - id: DQ-03 name: Appropriate Scale score: 4 max: 4 passed: true - comment: 'Realistic: 0-6000 RPM, 18-90 Hz frequencies, appropriate gyroscopic - effects' + comment: Realistic RPM and Hz values for rotating machinery code_quality: score: 9 max: 10 @@ -182,7 +179,7 @@ review: score: 3 max: 3 passed: true - comment: Linear imports-data-plot-save structure, no functions/classes + comment: Imports, data, plot, save — no functions or classes - id: CQ-02 name: Reproducibility score: 2 @@ -199,17 +196,17 @@ review: name: Code Elegance score: 1 max: 2 - passed: true - comment: Clear but somewhat verbose legend construction with breaks/labels - mapping + passed: false + comment: Duplicate interpolation logic for 1x/1st Bending annotation repeats + general intersection pattern - 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 with dpi=300 library_features: - score: 8 + score: 9 max: 10 items: - id: LM-01 @@ -217,15 +214,15 @@ review: score: 5 max: 5 passed: true - comment: 'Expert grammar of graphics: aes mapping, layer composition, scale_color/linetype_manual, - guide_legend, annotate' + comment: Expert grammar-of-graphics composition with layer stacking, scale_manual, + guide_legend, coord_cartesian, annotate - id: LM-02 name: Distinctive Features - score: 3 + score: 4 max: 5 passed: true - comment: Uses ggplot-style layer composition, guide_legend(override_aes), - element theming system + comment: scale_size_identity for line weight, guide_legend(override_aes), + coord_cartesian zoom, geom_rect band verdict: APPROVED impl_tags: dependencies: [] @@ -240,3 +237,4 @@ impl_tags: - interpolation styling: - grid-styling + - alpha-blending