{"spec_id":"parliament-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nparliament-basic: Parliament Seat Chart\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-17\n\"\"\"\n\nimport os\nimport site\nimport sys\n\n\n# Remove current directory from sys.path to avoid shadowing installed packages\nsys.path = [p for p in sys.path if p not in (\"\", \".\") and not p.endswith(\"/python\")]\n\n# Ensure site-packages are at the front\nfor sp in reversed(site.getsitepackages()):\n    sys.path.insert(0, sp)\n\nimport numpy as np\nimport plotly.graph_objects as go\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\n\n# Okabe-Ito palette (positions 1→6, first is always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\"]\n\n# Data - Fictional parliament with neutral party names\nparties = [\n    {\"name\": \"Progressive Alliance\", \"seats\": 145},\n    {\"name\": \"Civic Union\", \"seats\": 118},\n    {\"name\": \"Green Future\", \"seats\": 52},\n    {\"name\": \"Liberty Party\", \"seats\": 48},\n    {\"name\": \"Reform Coalition\", \"seats\": 35},\n    {\"name\": \"Independent Group\", \"seats\": 22},\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)\nmajority_threshold = total_seats // 2 + 1\n\n# Calculate seat positions in semicircular arrangement\nn_rows = 7 if total_seats <= 500 else 9\ninner_radius = 0.4\nrow_spacing = 0.11\n\n# Calculate seats per row (outer rows have more seats due to larger circumference)\nrow_seats = []\nfor row in range(n_rows):\n    radius = inner_radius + row * row_spacing\n    # Seats proportional to arc length (radius)\n    seats_in_row = int(total_seats * radius / sum(inner_radius + i * row_spacing for i in range(n_rows)))\n    row_seats.append(max(seats_in_row, 1))\n\n# Adjust to match total seats exactly\ndiff = total_seats - sum(row_seats)\nfor i in range(abs(diff)):\n    idx = (n_rows - 1 - i % n_rows) if diff > 0 else (i % n_rows)\n    row_seats[idx] += 1 if diff > 0 else -1\n\n# Generate all seat positions sorted by angle (left to right = pi to 0)\nall_seats = []\nfor row, n_seats in enumerate(row_seats):\n    radius = inner_radius + row * row_spacing\n    for i in range(n_seats):\n        # Angle from left (pi) to right (0) - seats go left to right\n        angle = np.pi - (i + 0.5) * np.pi / n_seats\n        all_seats.append({\"x\": radius * np.cos(angle), \"y\": radius * np.sin(angle), \"angle\": angle, \"row\": row})\n\n# Sort all seats by angle (descending = left to right in parliament view)\nall_seats.sort(key=lambda s: -s[\"angle\"])\n\n# Assign parties to seats (parties fill seats from left to right)\npositions = []\nseat_idx = 0\nfor party in parties:\n    for _ in range(party[\"seats\"]):\n        if seat_idx < len(all_seats):\n            seat = all_seats[seat_idx]\n            positions.append(\n                {\"x\": seat[\"x\"], \"y\": seat[\"y\"], \"party\": party[\"name\"], \"color\": party[\"color\"], \"row\": seat[\"row\"]}\n            )\n            seat_idx += 1\n\n# Create figure\nfig = go.Figure()\n\n# Add seats grouped by party for legend\nfor party in parties:\n    party_positions = [p for p in positions if p[\"party\"] == party[\"name\"]]\n    fig.add_trace(\n        go.Scatter(\n            x=[p[\"x\"] for p in party_positions],\n            y=[p[\"y\"] for p in party_positions],\n            mode=\"markers\",\n            marker=dict(size=14, color=party[\"color\"], line=dict(color=PAGE_BG, width=1)),\n            name=f\"{party['name']} ({party['seats']})\",\n            hovertemplate=f\"{party['name']}<br>Seats: {party['seats']}<extra></extra>\",\n        )\n    )\n\n# Add majority threshold arc (more visible with increased alpha)\nthreshold_angle = np.linspace(0, np.pi, 100)\nthreshold_radius = 0.5 + 0.12 * (len(set(p[\"row\"] for p in positions)) / 2)\nfig.add_trace(\n    go.Scatter(\n        x=threshold_radius * np.cos(threshold_angle),\n        y=threshold_radius * np.sin(threshold_angle),\n        mode=\"lines\",\n        line=dict(color=INK_SOFT, width=3, dash=\"dash\"),\n        name=f\"Majority ({majority_threshold})\",\n        hoverinfo=\"skip\",\n    )\n)\n\n# Layout\nfig.update_layout(\n    title=dict(text=\"parliament-basic · plotly · anyplot.ai\", font=dict(size=28, color=INK), x=0.5, xanchor=\"center\"),\n    xaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-1.3, 1.3], scaleanchor=\"y\", scaleratio=1),\n    yaxis=dict(showgrid=False, zeroline=False, showticklabels=False, range=[-0.15, 1.2]),\n    legend=dict(\n        orientation=\"h\",\n        yanchor=\"bottom\",\n        y=-0.15,\n        xanchor=\"center\",\n        x=0.5,\n        font=dict(size=16, color=INK_SOFT),\n        bgcolor=ELEVATED_BG,\n        bordercolor=INK_SOFT,\n        borderwidth=1,\n        itemsizing=\"constant\",\n    ),\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    margin=dict(l=50, r=50, t=100, b=120),\n)\n\n# Add annotation for total seats\nfig.add_annotation(\n    x=0, y=0.05, text=f\"<b>{total_seats}</b><br>seats\", font=dict(size=24, color=INK), showarrow=False, align=\"center\"\n)\n\n# Save outputs\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}