{"spec_id":"campbell-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\ncampbell-basic: Campbell Diagram\nLibrary: seaborn 0.13.2 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-28\n\"\"\"\n\nimport os\n\nimport matplotlib.patches as mpatches\nimport matplotlib.pyplot as plt\nimport numpy as np\nimport pandas as pd\nimport seaborn as sns\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nANYPLOT_AMBER = \"#DDCC77\"\n\n# Data\nnp.random.seed(42)\nrpm = np.linspace(0, 6000, 100)\n\nmode_1_bending = 15 + 0.0018 * rpm + 1.2 * np.sin(rpm / 1500)\nmode_2_bending = 44 - 0.0025 * rpm + 0.8 * np.cos(rpm / 2000)\nmode_1_torsional = 52 + 0.0035 * rpm\nmode_axial = 68 - 0.0008 * rpm + 1.0 * np.sin(rpm / 1200)\nmode_3_bending = 82 + 0.0022 * rpm + 0.6 * np.sin(rpm / 1800)\n\nmodes = {\n    \"1st Bending\": mode_1_bending,\n    \"2nd Bending\": mode_2_bending,\n    \"1st Torsional\": mode_1_torsional,\n    \"Axial\": mode_axial,\n    \"3rd Bending\": mode_3_bending,\n}\n\nrecords = []\nfor mode_name, freq in modes.items():\n    for r, f in zip(rpm, freq, strict=False):\n        records.append({\"RPM\": r, \"Frequency (Hz)\": f, \"Mode\": mode_name})\ndf = pd.DataFrame(records)\n\nengine_orders = [1, 2, 3]\n\ncritical_speeds, critical_freqs = [], []\nfor mode_freq in modes.values():\n    for order in engine_orders:\n        diff = mode_freq - order * rpm / 60\n        for idx in np.where(np.diff(np.sign(diff)))[0]:\n            t = abs(diff[idx]) / (abs(diff[idx]) + abs(diff[idx + 1]))\n            cs_rpm = rpm[idx] + t * (rpm[idx + 1] - rpm[idx])\n            cs_freq = order * cs_rpm / 60\n            if 100 < cs_rpm < 5900:\n                critical_speeds.append(cs_rpm)\n                critical_freqs.append(cs_freq)\n\nop_low, op_high = 2800, 4200\nin_operating = [op_low <= s <= op_high for s in critical_speeds]\n\ncs_df = pd.DataFrame(\n    {\n        \"RPM\": critical_speeds,\n        \"Frequency (Hz)\": critical_freqs,\n        \"Status\": [\"In Range\" if inside else \"Outside\" for inside in in_operating],\n    }\n)\n\n# Plot setup\nsns.set_theme(\n    style=\"ticks\",\n    rc={\n        \"figure.facecolor\": PAGE_BG,\n        \"axes.facecolor\": PAGE_BG,\n        \"axes.edgecolor\": INK_SOFT,\n        \"axes.labelcolor\": INK,\n        \"text.color\": INK,\n        \"xtick.color\": INK_SOFT,\n        \"ytick.color\": INK_SOFT,\n        \"legend.facecolor\": ELEVATED_BG,\n        \"legend.edgecolor\": INK_SOFT,\n    },\n)\n\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400)\nfig.set_facecolor(PAGE_BG)\n\n# Operating range highlight (amber = caution)\nax.axvspan(op_low, op_high, color=ANYPLOT_AMBER, alpha=0.10, zorder=0)\nax.axvline(op_low, color=ANYPLOT_AMBER, linewidth=0.8, linestyle=\"--\", alpha=0.6, zorder=1)\nax.axvline(op_high, color=ANYPLOT_AMBER, linewidth=0.8, linestyle=\"--\", alpha=0.6, zorder=1)\nax.text(\n    (op_low + op_high) / 2,\n    3,\n    \"Operating\\nRange\",\n    fontsize=7,\n    color=ANYPLOT_AMBER,\n    ha=\"center\",\n    va=\"bottom\",\n    fontweight=\"bold\",\n    linespacing=1.3,\n)\n\n# Natural frequency curves via seaborn lineplot with hue\nmode_colors = IMPRINT_PALETTE[:5]\nsns.lineplot(\n    data=df,\n    x=\"RPM\",\n    y=\"Frequency (Hz)\",\n    hue=\"Mode\",\n    palette=dict(zip(modes.keys(), mode_colors, strict=False)),\n    linewidth=2.0,\n    ax=ax,\n    legend=False,\n    hue_order=list(modes.keys()),\n)\n\n# Engine order lines (structural reference)\neo_label_x = {1: 4600, 2: 2200, 3: 1400}\nfor order in engine_orders:\n    eo_freq = order * rpm / 60\n    ax.plot(rpm, eo_freq, color=INK_SOFT, linewidth=1.2, linestyle=\"--\", alpha=0.65, zorder=2)\n    lx = eo_label_x[order]\n    ax.text(\n        lx,\n        order * lx / 60 + 1.5,\n        f\"{order}x\",\n        fontsize=8,\n        color=INK_SOFT,\n        fontweight=\"bold\",\n        va=\"bottom\",\n        ha=\"center\",\n        bbox={\"boxstyle\": \"round,pad=0.12\", \"fc\": ELEVATED_BG, \"ec\": INK_SOFT, \"alpha\": 0.85, \"linewidth\": 0.5},\n    )\n\n# Critical speed markers via seaborn scatterplot (split by status for independent alpha)\ncs_in_df = cs_df[cs_df[\"Status\"] == \"In Range\"]\ncs_out_df = cs_df[cs_df[\"Status\"] == \"Outside\"]\nsns.scatterplot(\n    data=cs_in_df,\n    x=\"RPM\",\n    y=\"Frequency (Hz)\",\n    color=IMPRINT_PALETTE[4],\n    s=120,\n    ax=ax,\n    zorder=5,\n    edgecolor=PAGE_BG,\n    linewidth=0.8,\n    legend=False,\n)\nsns.scatterplot(\n    data=cs_out_df,\n    x=\"RPM\",\n    y=\"Frequency (Hz)\",\n    color=INK_MUTED,\n    s=30,\n    alpha=0.7,\n    ax=ax,\n    zorder=5,\n    edgecolor=PAGE_BG,\n    linewidth=0.8,\n    legend=False,\n)\n\n# Direct mode labels (right edge, increased vertical separation)\ny_offsets = {\"1st Bending\": -2, \"2nd Bending\": 3, \"1st Torsional\": 3, \"Axial\": -3, \"3rd Bending\": 0}\nfor i, (name, freq) in enumerate(modes.items()):\n    ax.text(\n        6080,\n        freq[-1] + y_offsets[name],\n        name,\n        fontsize=8,\n        color=mode_colors[i],\n        fontweight=\"bold\",\n        va=\"center\",\n        ha=\"left\",\n        clip_on=False,\n    )\n\n# Axes limits and labels\ny_max = max(m.max() for m in modes.values())\nax.set_xlim(0, 6000)\nax.set_ylim(0, y_max + 5)\n\ntitle = \"campbell-basic · python · seaborn · anyplot.ai\"\ntitle_fontsize = max(8, round(12 * (67 / len(title) if len(title) > 67 else 1.0)))\n\nax.set_xlabel(\"Rotational Speed (RPM)\", fontsize=10, color=INK)\nax.set_ylabel(\"Frequency (Hz)\", fontsize=10, color=INK)\nax.set_title(title, fontsize=title_fontsize, fontweight=\"medium\", color=INK)\nax.tick_params(axis=\"both\", labelsize=8)\nax.grid(True, axis=\"both\", alpha=0.12, linewidth=0.5, color=INK)\nax.set_axisbelow(True)\nsns.despine(ax=ax)\n\n# Compact legend: critical speed status + operating range only\ncs_in = mpatches.Patch(facecolor=IMPRINT_PALETTE[4], label=\"Critical (in range)\")\ncs_out = mpatches.Patch(facecolor=INK_MUTED, label=\"Critical (outside)\")\nop_leg = mpatches.Patch(facecolor=ANYPLOT_AMBER, alpha=0.45, label=f\"Operating ({op_low}–{op_high} RPM)\")\nax.legend(handles=[cs_in, cs_out, op_leg], fontsize=8, loc=\"lower right\", frameon=True, fancybox=False, framealpha=0.9)\n\nfig.subplots_adjust(left=0.08, right=0.80, top=0.92, bottom=0.12)\n\n# Save\nplt.savefig(f\"plot-{THEME}.png\", dpi=400)\n"}