{"spec_id":"bump-basic","library":"matplotlib","language":"python","code":"\"\"\" anyplot.ai\nbump-basic: Basic Bump Chart\nLibrary: matplotlib 3.10.9 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport matplotlib.patheffects as pe\nimport matplotlib.pyplot as plt\nimport numpy as np\nfrom scipy.interpolate import CubicSpline\n\n\n# Theme\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\n# Imprint palette — 8 hues, hybrid-v3 sort, theme-independent\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — Formula 1 driver standings over an 8-race season\ndrivers = [\"Verstappen\", \"Hamilton\", \"Norris\", \"Leclerc\", \"Sainz\", \"Piastri\", \"Russell\"]\nraces = [\"Bahrain\", \"Jeddah\", \"Melbourne\", \"Suzuka\", \"Shanghai\", \"Miami\", \"Imola\", \"Monaco\"]\n\nrankings = {\n    \"Verstappen\": [1, 1, 1, 2, 3, 3, 2, 1],\n    \"Hamilton\": [4, 3, 2, 1, 1, 2, 1, 2],\n    \"Norris\": [5, 5, 4, 3, 2, 1, 3, 3],\n    \"Leclerc\": [2, 2, 3, 4, 5, 5, 4, 4],\n    \"Sainz\": [3, 4, 5, 5, 4, 4, 5, 5],\n    \"Piastri\": [6, 6, 7, 7, 6, 6, 6, 7],\n    \"Russell\": [7, 7, 6, 6, 7, 7, 7, 6],\n}\n\n# Assign Imprint palette in canonical order\ncolors = {driver: IMPRINT_PALETTE[i] for i, driver in enumerate(drivers)}\n\n# Canvas — landscape 3200 × 1800 px\nfig, ax = plt.subplots(figsize=(8, 4.5), dpi=400, facecolor=PAGE_BG)\nax.set_facecolor(PAGE_BG)\n\nx = np.arange(len(races))\n\n# Subtle background band for the P1 zone — visual emphasis replacing narrative annotations\nax.axhspan(0.5, 1.5, color=INK_MUTED, alpha=0.10, zorder=0, linewidth=0)\n\nfor driver, ranks in rankings.items():\n    ranks_arr = np.array(ranks)\n    col = colors[driver]\n\n    # Smooth S-curve bump line via cubic spline interpolation\n    cs = CubicSpline(x, ranks_arr)\n    x_smooth = np.linspace(x[0], x[-1], 300)\n    y_smooth = cs(x_smooth)\n    ax.plot(x_smooth, y_smooth, linewidth=2.0, color=col, zorder=3, solid_capstyle=\"round\")\n\n    # Markers — size encodes rank prominence: rank 1 → largest, rank 7 → smallest\n    marker_s = [max(50, 180 - (r - 1) * 22) for r in ranks_arr]\n    ax.scatter(x, ranks_arr, s=marker_s, color=col, edgecolors=PAGE_BG, linewidths=0.8, zorder=4)\n\n    # End-of-line label with PAGE_BG stroke for legibility in both themes\n    ax.text(\n        x[-1] + 0.2,\n        ranks_arr[-1],\n        driver,\n        fontsize=8,\n        fontweight=\"bold\",\n        color=col,\n        va=\"center\",\n        path_effects=[pe.withStroke(linewidth=2, foreground=PAGE_BG)],\n    )\n\n# Invert Y-axis so rank 1 is at the top\nax.set_ylim(0.3, len(drivers) + 0.7)\nax.invert_yaxis()\n\n# Labels\ntitle = \"bump-basic · python · matplotlib · anyplot.ai\"\nax.set_title(title, fontsize=13, fontweight=\"medium\", color=INK, pad=8)\nax.set_xlabel(\"Grand Prix\", fontsize=10, color=INK)\nax.set_ylabel(\"Championship Position\", fontsize=10, color=INK)\n\nax.set_xticks(x)\nax.set_xticklabels(races, rotation=30, ha=\"right\")\nax.set_yticks(range(1, len(drivers) + 1))\nax.tick_params(axis=\"both\", labelsize=8, colors=INK_SOFT, labelcolor=INK_SOFT)\n\n# Subtle horizontal grid at each rank row\nax.yaxis.grid(True, alpha=0.15, color=INK, linewidth=0.6, zorder=1)\n\n# Spines — L-frame\nax.spines[\"top\"].set_visible(False)\nax.spines[\"right\"].set_visible(False)\nax.spines[\"left\"].set_color(INK_SOFT)\nax.spines[\"bottom\"].set_color(INK_SOFT)\n\n# Extra horizontal space for end-of-line labels\nax.set_xlim(-0.5, len(races) - 1 + 2.2)\n\nfig.subplots_adjust(left=0.09, right=0.83, top=0.90, bottom=0.18)\nplt.savefig(f\"plot-{THEME}.png\", dpi=400, facecolor=PAGE_BG)\n"}