{"spec_id":"bump-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nbump-basic: Basic Bump Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_point,\n    geom_text,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_x_continuous,\n    scale_y_reverse,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\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 categorical palette (hybrid-v3 sort order)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data — Streaming platform market share rankings over 8 quarters\nplatforms = [\"StreamVue\", \"WavePlay\", \"CloudCast\", \"PixelFlix\", \"SonicNet\", \"EchoTV\"]\nquarters = [\"Q1'24\", \"Q2'24\", \"Q3'24\", \"Q4'24\", \"Q1'25\", \"Q2'25\", \"Q3'25\", \"Q4'25\"]\nn_periods = len(quarters)\n\nrankings = {\n    \"StreamVue\": [1, 1, 1, 2, 2, 3, 3, 4],\n    \"WavePlay\": [2, 3, 3, 1, 1, 1, 1, 1],\n    \"CloudCast\": [4, 2, 2, 3, 3, 2, 2, 2],\n    \"PixelFlix\": [3, 4, 4, 4, 5, 5, 4, 3],\n    \"SonicNet\": [5, 5, 5, 5, 4, 4, 5, 5],\n    \"EchoTV\": [6, 6, 6, 6, 6, 6, 6, 6],\n}\n\nrows = []\nfor platform, ranks in rankings.items():\n    for i, rank in enumerate(ranks):\n        rows.append({\"platform\": platform, \"quarter\": quarters[i], \"qnum\": i + 1, \"rank\": rank})\ndf = pd.DataFrame(rows)\n\ndf_end = df[df[\"qnum\"] == n_periods].copy()\n\n# Visual hierarchy: protagonist entities vs supporting cast\nprotagonists = [\"StreamVue\", \"WavePlay\"]\nsupporting = [\"CloudCast\", \"PixelFlix\", \"SonicNet\", \"EchoTV\"]\n\ndf_hero = df[df[\"platform\"].isin(protagonists)]\ndf_support = df[df[\"platform\"].isin(supporting)]\n\n# Crossover emphasis at Q4'24 where WavePlay overtakes StreamVue\ndf_crossover = pd.DataFrame(\n    [{\"qnum\": 4, \"rank\": 1, \"platform\": \"WavePlay\"}, {\"qnum\": 4, \"rank\": 2, \"platform\": \"StreamVue\"}]\n)\n\n# Imprint palette mapped to each platform (canonical order, position 1 = brand green first)\npalette = {\n    \"StreamVue\": IMPRINT[0],  # brand green #009E73\n    \"WavePlay\": IMPRINT[1],  # lavender #C475FD\n    \"CloudCast\": IMPRINT[2],  # blue #4467A3\n    \"PixelFlix\": IMPRINT[3],  # ochre #BD8233\n    \"SonicNet\": IMPRINT[4],  # matte red #AE3030\n    \"EchoTV\": IMPRINT[5],  # cyan #2ABCCD\n}\n\ntitle = \"bump-basic · python · plotnine · anyplot.ai\"\n\n# Plot — layered rendering for visual hierarchy (protagonist/supporting distinction)\nplot = (\n    ggplot(df, aes(x=\"qnum\", y=\"rank\", color=\"platform\", group=\"platform\"))\n    # Supporting lines: thin, muted\n    + geom_line(data=df_support, size=0.9, alpha=0.4)\n    + geom_point(data=df_support, size=2.0, alpha=0.55)\n    # Protagonist lines: bold and saturated\n    + geom_line(data=df_hero, size=2.0, alpha=0.95)\n    + geom_point(data=df_hero, size=4.0, alpha=1.0)\n    # Crossover halo at Q4'24\n    + geom_point(data=df_crossover, size=8, alpha=0.12)\n    # End labels — bold for protagonists, italic for supporting\n    + geom_text(\n        aes(label=\"platform\"),\n        data=df_end[df_end[\"platform\"].isin(protagonists)],\n        nudge_x=0.3,\n        ha=\"left\",\n        size=3.5,\n        fontweight=\"bold\",\n        color=INK,\n    )\n    + geom_text(\n        aes(label=\"platform\"),\n        data=df_end[df_end[\"platform\"].isin(supporting)],\n        nudge_x=0.3,\n        ha=\"left\",\n        size=3.0,\n        fontstyle=\"italic\",\n        color=INK_MUTED,\n    )\n    + scale_y_reverse(breaks=range(1, len(platforms) + 1))\n    + scale_x_continuous(breaks=range(1, n_periods + 1), labels=quarters, limits=(0.5, n_periods + 2))\n    + scale_color_manual(values=palette)\n    + labs(x=\"Quarter\", y=\"Market Share Ranking\", title=title)\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK_SOFT),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        axis_text_x=element_text(rotation=0),\n        plot_title=element_text(size=12, weight=\"bold\", color=INK),\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(alpha=0.15, size=0.3, color=INK),\n        panel_background=element_rect(fill=PAGE_BG, color=\"none\"),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"none\",\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    )\n)\n\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\")\n"}