{"spec_id":"bump-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nbump-basic: Basic Bump Chart\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent self-import: this file is named altair.py; remove the script directory\n# from sys.path so `import altair` finds the installed package, not this file.\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nsys.path = [p for p in sys.path if not p or os.path.abspath(p) != _this_dir]\n\nimport altair as alt\nimport pandas as pd\nfrom PIL import Image\n\n\n# Theme tokens — Imprint palette 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\"\n\n# Imprint categorical palette (hybrid-v3 order) — first series always #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data — Premier League standings over 6 match weeks (deterministic)\nteams = [\"Arsenal\", \"Chelsea\", \"Liverpool\", \"Man City\", \"Man United\", \"Tottenham\"]\nweeks = [\"Week 1\", \"Week 2\", \"Week 3\", \"Week 4\", \"Week 5\", \"Week 6\"]\nranks = [\n    # Arsenal: starts 3rd, rises to 1st — featured narrative\n    3,\n    2,\n    1,\n    1,\n    2,\n    1,\n    # Chelsea: mid-table consistency\n    4,\n    4,\n    3,\n    4,\n    3,\n    3,\n    # Liverpool: starts 1st, drops mid-season, recovers\n    1,\n    1,\n    2,\n    3,\n    1,\n    2,\n    # Man City: volatile mid-table\n    5,\n    5,\n    4,\n    2,\n    4,\n    4,\n    # Man United: declines from 2nd to 5th\n    2,\n    3,\n    5,\n    5,\n    5,\n    5,\n    # Tottenham: bottom position throughout\n    6,\n    6,\n    6,\n    6,\n    6,\n    6,\n]\n\ndf = pd.DataFrame({\"Team\": [t for t in teams for _ in weeks], \"Week\": weeks * len(teams), \"Rank\": ranks})\n\n# Interactive highlight on hover — distinctive Altair capability\nhighlight = alt.selection_point(fields=[\"Team\"], on=\"pointerover\")\n\n# Arsenal predicate for visual hierarchy (data storytelling)\nis_arsenal = alt.datum.Team == \"Arsenal\"\n\n# Lines — Arsenal emphasized with thicker stroke\nlines = (\n    alt.Chart(df)\n    .mark_line()\n    .encode(\n        x=alt.X(\"Week:O\", title=\"Match Week\", axis=alt.Axis(labelAngle=0)),\n        y=alt.Y(\n            \"Rank:Q\",\n            title=\"League Position\",\n            scale=alt.Scale(domain=[1, 6], reverse=True),\n            axis=alt.Axis(tickMinStep=1, values=[1, 2, 3, 4, 5, 6]),\n        ),\n        color=alt.Color(\"Team:N\", scale=alt.Scale(domain=teams, range=IMPRINT_PALETTE), legend=None),\n        strokeWidth=alt.condition(is_arsenal, alt.value(4), alt.value(2)),\n        opacity=alt.condition(highlight, alt.value(1.0), alt.value(0.3)),\n    )\n    .add_params(highlight)\n)\n\n# Points — Arsenal gets larger markers for emphasis\npoints = (\n    alt.Chart(df)\n    .mark_point(filled=True)\n    .encode(\n        x=alt.X(\"Week:O\"),\n        y=alt.Y(\"Rank:Q\", scale=alt.Scale(domain=[1, 6], reverse=True)),\n        color=alt.Color(\"Team:N\", scale=alt.Scale(domain=teams, range=IMPRINT_PALETTE)),\n        size=alt.condition(is_arsenal, alt.value(200), alt.value(80)),\n        opacity=alt.condition(highlight, alt.value(1.0), alt.value(0.3)),\n        tooltip=[\"Team:N\", \"Week:O\", \"Rank:Q\"],\n    )\n)\n\n# End-of-line labels — direct identification replacing legend\nlast_week = df[df[\"Week\"] == \"Week 6\"]\nlabels = (\n    alt.Chart(last_week)\n    .mark_text(align=\"left\", dx=12, fontSize=11)\n    .encode(\n        x=alt.X(\"Week:O\"),\n        y=alt.Y(\"Rank:Q\", scale=alt.Scale(domain=[1, 6], reverse=True)),\n        text=\"Team:N\",\n        color=alt.Color(\"Team:N\", scale=alt.Scale(domain=teams, range=IMPRINT_PALETTE)),\n    )\n)\n\n# Callout annotation — Arsenal reaches 1st at Week 3 (data storytelling)\nannotation_df = pd.DataFrame({\"Week\": [\"Week 3\"], \"Rank\": [1], \"label\": [\"↑ Arsenal: 1st\"]})\nannotation = (\n    alt.Chart(annotation_df)\n    .mark_text(align=\"left\", dx=12, dy=18, fontSize=10, fontWeight=\"bold\", color=\"#009E73\")\n    .encode(x=alt.X(\"Week:O\"), y=alt.Y(\"Rank:Q\", scale=alt.Scale(domain=[1, 6], reverse=True)), text=\"label:N\")\n)\n\ntitle = \"bump-basic · python · altair · anyplot.ai\"\nchart = (\n    (lines + points + labels + annotation)\n    .properties(width=620, height=320, background=PAGE_BG, title=alt.Title(title, fontSize=16))\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.15,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad-only to exact 3200×1800 target (do NOT crop — AR-09)\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}×{_h}, exceeds target {TW}×{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}