{"spec_id":"bump-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nbump-basic: Basic Bump Chart\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_label,\n    geom_line,\n    geom_point,\n    geom_text,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_alpha_manual,\n    scale_color_manual,\n    scale_size_manual,\n    scale_x_continuous,\n    scale_y_reverse,\n    theme,\n    theme_minimal,\n)\n\n\nLetsPlot.setup_html()\n\n# Theme tokens — Imprint palette 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# Approximate 15% opacity grid: blend INK into PAGE_BG at 15%\nGRID_COLOR = \"#D7D6D3\" if THEME == \"light\" else \"#3A3935\"\n\n# Imprint categorical palette — canonical order, 5 series\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data - Tech company market share rankings over 6 quarters\n# Story: Alpha Corp reclaims top; Beta Inc meteoric rise then collapse;\n# Gamma Tech volatile; Delta Systems steady climber; Epsilon Labs at the bottom\ndata = {\n    \"entity\": (\n        [\"Alpha Corp\"] * 6 + [\"Beta Inc\"] * 6 + [\"Gamma Tech\"] * 6 + [\"Delta Systems\"] * 6 + [\"Epsilon Labs\"] * 6\n    ),\n    \"period\": [\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"Q5\", \"Q6\"] * 5,\n    \"period_num\": [1, 2, 3, 4, 5, 6] * 5,\n    \"rank\": [\n        1,\n        2,\n        3,\n        2,\n        1,\n        1,  # Alpha Corp — drops mid-year, reclaims #1\n        3,\n        1,\n        1,\n        3,\n        4,\n        5,  # Beta Inc — meteoric rise to #1, then collapses\n        2,\n        3,\n        2,\n        1,\n        2,\n        3,  # Gamma Tech — volatile, briefly reaches #1 in Q4\n        4,\n        4,\n        5,\n        4,\n        3,\n        2,  # Delta Systems — steady climber from bottom half\n        5,\n        5,\n        4,\n        5,\n        5,\n        4,  # Epsilon Labs — mostly bottom, slight improvement\n    ],\n}\ndf = pd.DataFrame(data)\n\n# Hero entity: Beta Inc has the most dramatic arc — emphasize via mapped aesthetics\nhero = \"Beta Inc\"\nHERO_COLOR = IMPRINT_PALETTE[1]  # lavender — Beta Inc's Imprint color\ndf[\"role\"] = df[\"entity\"].apply(lambda x: \"hero\" if x == hero else \"rest\")\ndf_labels = df[df[\"period_num\"] == 6].copy()\n\n# Annotation dataframe — highlight Beta Inc's peak reign (Q2–Q3) using geom_label\ndf_annot = pd.DataFrame({\"period_num\": [2.5], \"rank\": [0.8], \"label\": [\"Beta Inc: Rank #1\\n(Q2–Q3 peak)\"]})\n\n# Tooltip config for the interactive HTML output\ntooltip_cfg = layer_tooltips().title(\"@entity\").line(\"@|@period\").line(\"Rank|@rank\")\n\n# Title length: 44 chars < 67 baseline → keep default size 16\ntitle = \"bump-basic · python · letsplot · anyplot.ai\"\n\nplot = (\n    ggplot(df, aes(x=\"period_num\", y=\"rank\", color=\"entity\", group=\"entity\"))\n    # Lines: dramatic size/alpha split — hero 3.0 vs rest 0.8 for unmistakable focal point\n    + geom_line(aes(size=\"role\", alpha=\"role\"), tooltips=tooltip_cfg)\n    # Dots at each rank position; alpha mapped for hero prominence\n    + geom_point(aes(alpha=\"role\"), size=4, tooltips=tooltip_cfg)\n    # End-of-line entity labels — entity color inherited from global aes\n    + geom_text(aes(label=\"entity\"), data=df_labels, nudge_x=0.3, hjust=0, size=5)\n    # Peak annotation via geom_label — letsplot's labeled text box with background fill\n    + geom_label(\n        aes(x=\"period_num\", y=\"rank\", label=\"label\"),\n        data=df_annot,\n        color=HERO_COLOR,\n        fill=PAGE_BG,\n        size=3.5,\n        hjust=0.5,\n        inherit_aes=False,\n        label_size=0.5,\n    )\n    + scale_y_reverse(breaks=[1, 2, 3, 4, 5])\n    + scale_x_continuous(breaks=[1, 2, 3, 4, 5, 6], labels=[\"Q1\", \"Q2\", \"Q3\", \"Q4\", \"Q5\", \"Q6\"], limits=[0.5, 8.5])\n    + scale_color_manual(values=IMPRINT_PALETTE)\n    + scale_size_manual(name=\"\", values={\"hero\": 3.0, \"rest\": 0.8}, guide=\"none\")\n    + scale_alpha_manual(name=\"\", values={\"hero\": 1.0, \"rest\": 0.55}, guide=\"none\")\n    + labs(x=\"Quarterly Period\", y=\"Market Rank\", title=title)\n    + theme_minimal()\n    + theme(\n        plot_title=element_text(size=16, color=INK),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        axis_line=element_line(color=INK_SOFT),\n        axis_ticks=element_blank(),\n        legend_position=\"none\",\n        # Y-axis grid only — cleaner look for bump charts\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor_x=element_blank(),\n        panel_grid_major_y=element_line(color=GRID_COLOR, size=0.5),\n        panel_grid_minor_y=element_blank(),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n    )\n    + ggsize(800, 450)\n)\n\n# Save PNG (scale=4 → 3200×1800 px) and HTML (interactive tooltips)\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}