{"spec_id":"parliament-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nparliament-basic: Parliament Seat Chart\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport site\nimport sys\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\n\n\n# Ensure the real altair library is imported, not this script\nsite_packages = [p for p in site.getsitepackages() if \"site-packages\" in p]\nif site_packages:\n    sys.path.insert(0, site_packages[0])\n\nimport altair as alt\n\n\n# Get the directory where this script is located\nSCRIPT_DIR = Path(__file__).parent\n\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\nnp.random.seed(42)\n\n# Data: Fictional parliament with 300 seats\n# Using Okabe-Ito palette, starting with #009E73\nparties = [\n    {\"party\": \"Progressive\", \"seats\": 95, \"color\": \"#009E73\"},\n    {\"party\": \"Conservative\", \"seats\": 82, \"color\": \"#C475FD\"},\n    {\"party\": \"Green\", \"seats\": 45, \"color\": \"#4467A3\"},\n    {\"party\": \"Liberal\", \"seats\": 38, \"color\": \"#BD8233\"},\n    {\"party\": \"Social Dem.\", \"seats\": 28, \"color\": \"#AE3030\"},\n    {\"party\": \"Independent\", \"seats\": 12, \"color\": \"#2ABCCD\"},\n]\n\ntotal_seats = sum(p[\"seats\"] for p in parties)\n\n# Generate seat positions in semicircular arcs\nn_rows = 5\ninner_radius = 3.0\nrow_spacing = 1.0\n\n# Calculate seats per row (more seats in outer rows)\nrow_weights = [(inner_radius + i * row_spacing) for i in range(n_rows)]\ntotal_weight = sum(row_weights)\nseats_per_row = [max(1, int(total_seats * w / total_weight)) for w in row_weights]\n\n# Adjust to match total exactly\ndiff = total_seats - sum(seats_per_row)\nfor i in range(abs(diff)):\n    if diff > 0:\n        seats_per_row[-(i % n_rows) - 1] += 1\n    else:\n        seats_per_row[i % n_rows] -= 1\n\n# Generate positions for each seat\nall_seats = []\nseat_idx = 0\n\nfor row_idx, n_seats_in_row in enumerate(seats_per_row):\n    radius = inner_radius + row_idx * row_spacing\n    angles = np.linspace(np.pi, 0, n_seats_in_row)\n    for angle in angles:\n        x = radius * np.cos(angle)\n        y = radius * np.sin(angle)\n        all_seats.append({\"x\": x, \"y\": y, \"seat_idx\": seat_idx, \"angle\": angle})\n        seat_idx += 1\n\n# Sort seats by angle descending (pi to 0 = left to right)\nseats_sorted = sorted(all_seats, key=lambda s: -s[\"angle\"])\n\n# Assign parties to seats in order\nseat_data = []\ncurrent_idx = 0\nfor party_info in parties:\n    party_name = party_info[\"party\"]\n    party_color = party_info[\"color\"]\n    n_seats = party_info[\"seats\"]\n    for _ in range(n_seats):\n        if current_idx < len(seats_sorted):\n            seat = seats_sorted[current_idx]\n            seat_data.append(\n                {\"x\": seat[\"x\"], \"y\": seat[\"y\"], \"party\": party_name, \"color\": party_color, \"seats_count\": n_seats}\n            )\n            current_idx += 1\n\ndf = pd.DataFrame(seat_data)\n\n# Create color scale from party data\nparty_colors = {p[\"party\"]: p[\"color\"] for p in parties}\ncolor_domain = list(party_colors.keys())\ncolor_range = list(party_colors.values())\n\n# Create legend labels with seat counts\nlegend_labels = {p[\"party\"]: f\"{p['party']} ({p['seats']})\" for p in parties}\ndf[\"party_label\"] = df[\"party\"].map(legend_labels)\n\n# Create the parliament chart\nchart = (\n    alt.Chart(df)\n    .mark_circle(size=250, opacity=0.9)\n    .encode(\n        x=alt.X(\"x:Q\", axis=None),\n        y=alt.Y(\"y:Q\", axis=None, scale=alt.Scale(domain=[-0.5, inner_radius + n_rows * row_spacing])),\n        color=alt.Color(\n            \"party_label:N\",\n            scale=alt.Scale(domain=[legend_labels[p[\"party\"]] for p in parties], range=color_range),\n            legend=alt.Legend(\n                title=\"Parties\",\n                titleFontSize=20,\n                labelFontSize=16,\n                labelLimit=200,\n                symbolSize=300,\n                orient=\"bottom\",\n                direction=\"horizontal\",\n                columns=3,\n                titleOrient=\"top\",\n                titleAnchor=\"middle\",\n            ),\n        ),\n        tooltip=[\"party:N\", \"seats_count:Q\"],\n    )\n    .properties(\n        width=1600,\n        height=900,\n        title=alt.Title(\"parliament-basic · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, stroke=INK_SOFT, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT, tickColor=INK_SOFT, gridColor=INK, gridOpacity=0.10, labelColor=INK_SOFT, titleColor=INK\n    )\n    .configure_title(color=INK, fontSize=28)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\nchart.save(str(SCRIPT_DIR / f\"plot-{THEME}.png\"), scale_factor=3.0)\nchart.save(str(SCRIPT_DIR / f\"plot-{THEME}.html\"))\n"}