{"spec_id":"slope-basic","library":"seaborn","language":"python","code":"\"\"\" anyplot.ai\nslope-basic: Basic Slope Chart (Slopegraph)\nLibrary: seaborn 0.13.2 | Python 3.13.14\nQuality: 92/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\n\nimport matplotlib.pyplot as plt\nimport pandas as pd\nimport seaborn as sns\nfrom matplotlib.lines import Line2D\n\n\n# Theme tokens (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\n\nCOLOR_INCREASE = \"#009E73\"  # Imprint palette position 1 — brand green, also reads as \"gain\"\nCOLOR_DECREASE = \"#AE3030\"  # Imprint palette position 5 — semantic anchor for loss/decrease\n\nsns.set_theme(\n    style=\"white\",\n    rc={\"figure.facecolor\": PAGE_BG, \"axes.facecolor\": PAGE_BG, \"axes.labelcolor\": INK, \"text.color\": INK},\n)\n\n# Data — tech company revenue comparison Q1 vs Q4 (four rank crossings)\ndata = {\n    \"entity\": [\"StreamPeak\", \"DataCore\", \"CloudSync\", \"NetPulse\", \"CodeBase\", \"ByteFlow\", \"LogicGrid\", \"TechVault\"],\n    \"Q1 ($M)\": [50, 110, 165, 220, 275, 325, 378, 430],\n    \"Q4 ($M)\": [95, 60, 230, 178, 335, 268, 415, 368],\n}\n\ndf = pd.DataFrame(data)\ndf[\"change\"] = df[\"Q4 ($M)\"] - df[\"Q1 ($M)\"]\ndf[\"direction\"] = df[\"change\"].apply(lambda x: \"Increase\" if x > 0 else \"Decrease\")\ndf = df.sort_values(\"Q1 ($M)\").reset_index(drop=True)\n\ndf_melted = df.melt(\n    id_vars=[\"entity\", \"direction\"], value_vars=[\"Q1 ($M)\", \"Q4 ($M)\"], var_name=\"Period\", value_name=\"Revenue ($M)\"\n)\ndf_melted[\"period_num\"] = df_melted[\"Period\"].map({\"Q1 ($M)\": 0, \"Q4 ($M)\": 1})\n\n# Plot — landscape 3200×1800\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\npalette = {\"Increase\": COLOR_INCREASE, \"Decrease\": COLOR_DECREASE}\n\nsns.lineplot(\n    data=df_melted,\n    x=\"period_num\",\n    y=\"Revenue ($M)\",\n    hue=\"direction\",\n    units=\"entity\",\n    estimator=None,\n    palette=palette,\n    linewidth=2.2,\n    marker=\"o\",\n    markersize=7,\n    alpha=0.9,\n    legend=False,\n    ax=ax,\n)\n\n# Faint column guides — the two vertical \"axes\" the spec calls for, one per time point\nax.axvline(x=0, color=INK_SOFT, alpha=0.3, linewidth=1, zorder=1)\nax.axvline(x=1, color=INK_SOFT, alpha=0.3, linewidth=1, zorder=1)\n\n# Endpoint labels double as the value axis (no separate y-axis needed). annotation_clip=False\n# is required here: the default clip-to-axes-bbox behavior is what truncated \"CodeBase\"/\"NetPulse\"\n# into \"cdeBase\"/\"etPulse\" in the previous render.\nfor _, row in df.iterrows():\n    color = palette[row[\"direction\"]]\n    q1_val = int(row[\"Q1 ($M)\"])\n    q4_val = int(row[\"Q4 ($M)\"])\n\n    ax.annotate(\n        f\"{row['entity']} ({q1_val})\",\n        xy=(0, q1_val),\n        xytext=(-10, 0),\n        textcoords=\"offset points\",\n        fontsize=9.5,\n        color=color,\n        ha=\"right\",\n        va=\"center\",\n        fontweight=\"medium\",\n        annotation_clip=False,\n    )\n    ax.annotate(\n        f\"({q4_val}) {row['entity']}\",\n        xy=(1, q4_val),\n        xytext=(10, 0),\n        textcoords=\"offset points\",\n        fontsize=9.5,\n        color=color,\n        ha=\"left\",\n        va=\"center\",\n        fontweight=\"medium\",\n        annotation_clip=False,\n    )\n\n# Style — no shared y-axis: each column is its own vertical scale, per the spec's \"vertical\n# axes labeled with time point names\" note, so a combined Revenue axis would be redundant.\nax.set_xticks([0, 1])\nax.set_xticklabels([\"Q1 Revenue ($M)\", \"Q4 Revenue ($M)\"], fontsize=10, color=INK, fontweight=\"medium\")\nax.xaxis.set_ticks_position(\"top\")\nax.xaxis.set_label_position(\"top\")\nax.tick_params(axis=\"x\", top=True, bottom=False, labeltop=True, labelbottom=False, length=0, pad=10)\nax.set_xlabel(\"\")\nax.set_ylabel(\"\")\nax.set_yticks([])\nfor spine in ax.spines.values():\n    spine.set_visible(False)\nax.set_xlim(-0.15, 1.15)\n\ny_min = min(df[\"Q1 ($M)\"].min(), df[\"Q4 ($M)\"].min())\ny_max = max(df[\"Q1 ($M)\"].max(), df[\"Q4 ($M)\"].max())\ny_padding = (y_max - y_min) * 0.10\nax.set_ylim(y_min - y_padding, y_max + y_padding)\n\nfig.suptitle(\"slope-basic · python · seaborn · anyplot.ai\", fontsize=12, fontweight=\"medium\", color=INK, y=0.97)\n\nlegend_elements = [\n    Line2D([0], [0], color=COLOR_INCREASE, linewidth=2.2, marker=\"o\", markersize=6, label=\"Increase\"),\n    Line2D([0], [0], color=COLOR_DECREASE, linewidth=2.2, marker=\"o\", markersize=6, label=\"Decrease\"),\n]\nfig.legend(\n    handles=legend_elements,\n    loc=\"upper center\",\n    bbox_to_anchor=(0.5, 0.90),\n    ncol=2,\n    frameon=False,\n    fontsize=9,\n    labelcolor=INK,\n)\n\nfig.subplots_adjust(left=0.26, right=0.74, top=0.78, bottom=0.06)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}