{"spec_id":"parliament-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nparliament-basic: Parliament Seat Chart\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    coord_fixed,\n    element_rect,\n    element_text,\n    geom_point,\n    ggplot,\n    guide_legend,\n    guides,\n    labs,\n    scale_color_manual,\n    theme,\n    theme_void,\n)\n\n\n# Theme tokens\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# Okabe-Ito palette - first series is always #009E73\nIMPRINT = [\n    \"#009E73\",  # bluish green (brand)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n    \"#BD8233\",  # reddish purple\n    \"#AE3030\",  # orange\n    \"#2ABCCD\",  # sky blue\n]\n\n# Data - fictional parliament with neutral party names\nparties = [\n    {\"party\": \"Progressive Alliance\", \"seats\": 85},\n    {\"party\": \"Center Coalition\", \"seats\": 72},\n    {\"party\": \"Conservative Union\", \"seats\": 68},\n    {\"party\": \"Green Future\", \"seats\": 42},\n    {\"party\": \"Liberal Democrats\", \"seats\": 35},\n    {\"party\": \"Independent Group\", \"seats\": 18},\n]\n\n# Assign Okabe-Ito colors to parties\nfor i, party in enumerate(parties):\n    party[\"color\"] = IMPRINT[i % len(IMPRINT)]\n\ntotal_seats = sum(p[\"seats\"] for p in parties)\n\n# Calculate seat positions in semicircular arcs\nn_rows = 8\ninner_radius = 2.0\nrow_spacing = 1.0\n\n# Calculate seats per row (more seats in outer rows)\nseats_per_row = []\nfor i in range(n_rows):\n    radius = inner_radius + i * row_spacing\n    row_seats = int(np.ceil(radius * 3.5))\n    seats_per_row.append(row_seats)\n\n# Adjust to match total seats\ntotal_calc = sum(seats_per_row)\nscale = total_seats / total_calc\nseats_per_row = [max(3, int(round(s * scale))) for s in seats_per_row]\n\n# Fine-tune to exact total\ndiff = total_seats - sum(seats_per_row)\nfor i in range(abs(diff)):\n    idx = i % n_rows\n    if diff > 0:\n        seats_per_row[n_rows - 1 - idx] += 1\n    else:\n        seats_per_row[n_rows - 1 - idx] -= 1\n\n# Generate all seat positions with angles\nall_seats = []\nfor row_idx, num_seats in enumerate(seats_per_row):\n    radius = inner_radius + row_idx * row_spacing\n    angles = np.linspace(np.pi * 0.95, np.pi * 0.05, num_seats)\n    for angle in angles:\n        all_seats.append({\"angle\": angle, \"radius\": radius, \"x\": radius * np.cos(angle), \"y\": radius * np.sin(angle)})\n\n# Sort seats by angle (left to right) for party assignment\nall_seats.sort(key=lambda s: -s[\"angle\"])\n\n# Assign parties to seats (left to right across the hemicycle)\nseat_data = []\ncumulative = 0\nfor seat in all_seats:\n    running_total = 0\n    for party in parties:\n        running_total += party[\"seats\"]\n        if cumulative < running_total:\n            seat_data.append({\"x\": seat[\"x\"], \"y\": seat[\"y\"], \"party\": party[\"party\"]})\n            break\n    cumulative += 1\n\ndf = pd.DataFrame(seat_data)\n\n# Create color mapping and legend labels\nparty_colors = {p[\"party\"]: p[\"color\"] for p in parties}\nparty_order = [p[\"party\"] for p in parties]\nseat_counts = {p[\"party\"]: p[\"seats\"] for p in parties}\nlegend_labels = {p: f\"{p} ({seat_counts[p]})\" for p in party_order}\n\n# Convert party to categorical with order\ndf[\"party\"] = pd.Categorical(df[\"party\"], categories=party_order, ordered=True)\n\n# Create plot\nplot = (\n    ggplot(df, aes(x=\"x\", y=\"y\", color=\"party\"))\n    + geom_point(size=6, alpha=0.95)\n    + scale_color_manual(values=party_colors, labels=lambda x: [legend_labels.get(p, p) for p in x])\n    + labs(title=\"parliament-basic · plotnine · anyplot.ai\", color=\"\")\n    + coord_fixed(ratio=1)\n    + theme_void()\n    + theme(\n        figure_size=(16, 9),\n        plot_title=element_text(size=26, ha=\"center\", weight=\"bold\", color=INK),\n        legend_title=element_text(size=16, color=INK),\n        legend_text=element_text(size=13, color=INK_SOFT),\n        legend_position=\"bottom\",\n        legend_direction=\"horizontal\",\n        legend_key_size=20,\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n        plot_margin=0.1,\n    )\n    + guides(color=guide_legend(nrow=2, override_aes={\"size\": 8}))\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=300, width=16, height=9, verbose=False)\n"}