{"spec_id":"violin-grouped-swarm","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nviolin-grouped-swarm: Grouped Violin Plot with Swarm Overlay\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\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# Data: Response times across task types and expertise levels\nnp.random.seed(42)\n\ncategories = [\"Simple\", \"Moderate\", \"Complex\"]\ngroups = [\"Novice\", \"Expert\"]\n\ndata = []\nfor cat in categories:\n    for grp in groups:\n        n = 40\n        if cat == \"Simple\":\n            base = 200 if grp == \"Novice\" else 150\n            spread = 40 if grp == \"Novice\" else 25\n        elif cat == \"Moderate\":\n            base = 450 if grp == \"Novice\" else 300\n            spread = 80 if grp == \"Novice\" else 50\n        else:  # Complex\n            base = 800 if grp == \"Novice\" else 500\n            spread = 150 if grp == \"Novice\" else 80\n\n        values = np.random.normal(base, spread, n)\n        values = np.clip(values, 50, 1200)  # Keep values realistic\n\n        for v in values:\n            data.append({\"category\": cat, \"group\": grp, \"value\": v})\n\ndf = pd.DataFrame(data)\n\n# Okabe-Ito colors\ncolors = {\"Novice\": \"#009E73\", \"Expert\": \"#C475FD\"}\n\n# Create figure\nfig = go.Figure()\n\n# Add violins and scatter points for each category-group combination\nx_positions = {\"Simple\": 0, \"Moderate\": 1, \"Complex\": 2}\noffsets = {\"Novice\": -0.2, \"Expert\": 0.2}\n\nfor grp in groups:\n    grp_data = df[df[\"group\"] == grp]\n\n    # Add violin for this group\n    fig.add_trace(\n        go.Violin(\n            x=[x_positions[cat] + offsets[grp] for cat in grp_data[\"category\"]],\n            y=grp_data[\"value\"],\n            name=grp,\n            legendgroup=grp,\n            fillcolor=colors[grp],\n            line={\"color\": colors[grp], \"width\": 2},\n            opacity=0.5,\n            width=0.35,\n            meanline_visible=True,\n            showlegend=True,\n            points=False,  # We'll add swarm separately\n        )\n    )\n\n# Add swarm-like scatter points\nfor grp in groups:\n    for cat in categories:\n        subset = df[(df[\"group\"] == grp) & (df[\"category\"] == cat)]\n        values = subset[\"value\"].values\n        n = len(values)\n\n        # Create swarm-like horizontal jitter based on density\n        base_x = x_positions[cat] + offsets[grp]\n\n        # Sort values and assign jitter based on local density\n        sorted_indices = np.argsort(values)\n        jitter = np.zeros(n)\n\n        # Create alternating positions within bands\n        for i, idx in enumerate(sorted_indices):\n            # Alternate left/right within the violin\n            side = 1 if i % 2 == 0 else -1\n            jitter[idx] = side * np.random.uniform(0.02, 0.12)\n\n        x_vals = base_x + jitter\n\n        fig.add_trace(\n            go.Scatter(\n                x=x_vals,\n                y=values,\n                mode=\"markers\",\n                marker={\"size\": 8, \"color\": colors[grp], \"opacity\": 0.8, \"line\": {\"width\": 1, \"color\": \"white\"}},\n                name=grp,\n                legendgroup=grp,\n                showlegend=False,\n                hovertemplate=f\"{grp}<br>{cat}<br>Response Time: %{{y:.0f}} ms<extra></extra>\",\n            )\n        )\n\n# Update layout with theme-adaptive styling\nfig.update_layout(\n    title={\n        \"text\": \"violin-grouped-swarm · plotly · pyplots.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    xaxis={\n        \"title\": {\"text\": \"Task Complexity\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"tickmode\": \"array\",\n        \"tickvals\": [0, 1, 2],\n        \"ticktext\": [\"Simple\", \"Moderate\", \"Complex\"],\n        \"range\": [-0.6, 2.6],\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    yaxis={\n        \"title\": {\"text\": \"Response Time (ms)\", \"font\": {\"size\": 22, \"color\": INK}},\n        \"tickfont\": {\"size\": 18, \"color\": INK_SOFT},\n        \"gridcolor\": GRID,\n        \"gridwidth\": 1,\n        \"linecolor\": INK_SOFT,\n        \"zerolinecolor\": INK_SOFT,\n    },\n    legend={\n        \"title\": {\"text\": \"Expertise Level\", \"font\": {\"size\": 18, \"color\": INK}},\n        \"font\": {\"size\": 16, \"color\": INK_SOFT},\n        \"x\": 0.98,\n        \"y\": 0.98,\n        \"xanchor\": \"right\",\n        \"yanchor\": \"top\",\n        \"bgcolor\": ELEVATED_BG,\n        \"bordercolor\": INK_SOFT,\n        \"borderwidth\": 1,\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK},\n    margin={\"l\": 100, \"r\": 100, \"t\": 120, \"b\": 100},\n)\n\n# Save as PNG (4800 x 2700 px) and HTML\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}