{"spec_id":"bump-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nbump-basic: Basic Bump Chart\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-29\n\"\"\"\n\nimport os\n\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — semantic assignment for F1 teams:\n# McLaren (protagonist, rising) → brand green; Ferrari → matte-red (iconic);\n# Red Bull → blue (close to livery); others → canonical tail positions\nSERIES_COLORS = (\n    \"#009E73\",  # McLaren — brand green, first series\n    \"#AE3030\",  # Ferrari — matte red, semantic: iconic Ferrari red\n    \"#4467A3\",  # Red Bull Racing — blue, close to livery\n    \"#BD8233\",  # Mercedes — ochre\n    \"#C475FD\",  # Aston Martin — lavender\n)\n\n# Data — F1 constructor standings across 6 Grand Prix weekends\nentities = [\"McLaren\", \"Ferrari\", \"Red Bull Racing\", \"Mercedes\", \"Aston Martin\"]\nperiods = [\"Bahrain\", \"Jeddah\", \"Melbourne\", \"Imola\", \"Monaco\", \"Barcelona\"]\n\n# Rankings (1 = leading); McLaren surges from P4 to lead by Melbourne\nrankings = {\n    \"McLaren\": [4, 3, 1, 1, 1, 1],\n    \"Ferrari\": [2, 2, 3, 3, 2, 2],\n    \"Red Bull Racing\": [1, 1, 2, 2, 3, 3],\n    \"Mercedes\": [3, 4, 4, 4, 4, 4],\n    \"Aston Martin\": [5, 5, 5, 5, 5, 5],\n}\n\nmax_rank = len(entities)\n\n# Y-axis is inverted: rank 1 at top, rank 5 at bottom\ninverted_rankings = {e: [max_rank + 1 - r for r in ranks] for e, ranks in rankings.items()}\n\n# Title font size — mandated format is ~40 chars (<67 baseline), no shrink needed\ntitle_str = \"bump-basic · python · pygal · anyplot.ai\"\nn = len(title_str)\nratio = 67 / n if n > 67 else 1.0\ntitle_font_size = max(44, round(66 * ratio))\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=SERIES_COLORS,\n    title_font_size=title_font_size,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=6,\n    opacity=1.0,\n    opacity_hover=1.0,\n    transition=\"200ms ease-in\",\n)\n\n# Per-series stroke and dot sizes — visual hierarchy: protagonist boldest\nstroke_widths = {\"McLaren\": 18, \"Ferrari\": 12, \"Red Bull Racing\": 14, \"Mercedes\": 10, \"Aston Martin\": 12}\ndot_sizes_map = {\"McLaren\": 22, \"Ferrari\": 16, \"Red Bull Racing\": 18, \"Mercedes\": 14, \"Aston Martin\": 16}\n\nchart = pygal.Line(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=title_str,\n    x_title=\"Grand Prix\",\n    y_title=\"Constructor Standing\",\n    show_dots=True,\n    dots_size=14,\n    show_x_guides=False,\n    show_y_guides=False,\n    x_label_rotation=0,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=5,\n    truncate_legend=-1,\n    show_legend=True,\n    interpolate=None,\n    min_scale=1,\n    max_scale=max_rank,\n    margin_top=80,\n    margin_right=160,\n    margin_bottom=40,\n    margin_left=80,\n    value_formatter=lambda v: f\"P{max_rank + 1 - int(v)}\" if v == int(v) else \"\",\n    tooltip_border_radius=10,\n    tooltip_fancy_mode=True,\n    human_readable=True,\n    pretty_print=True,\n)\n\nchart.x_labels = periods\n\n# Dict-format y_labels map inverted values to human-readable rank positions (P1–P5)\nchart.y_labels = [{\"value\": max_rank + 1 - i, \"label\": f\"P{i}\"} for i in range(1, max_rank + 1)]\n\nfor entity in entities:\n    chart.add(\n        entity,\n        inverted_rankings[entity],\n        stroke_style={\"width\": stroke_widths[entity]},\n        dots_size=dot_sizes_map[entity],\n    )\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}