{"spec_id":"violin-grouped-swarm","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nviolin-grouped-swarm: Grouped Violin Plot with Swarm Overlay\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-18\n\"\"\"\n\nimport os\nimport sys\nfrom importlib.machinery import SourceFileLoader\n\nimport numpy as np\nimport pandas as pd\n\n\nvenv_path = sys.executable\nsite_packages = os.path.join(os.path.dirname(venv_path), \"..\", \"lib\", \"python3.13\", \"site-packages\")\naltair_init = os.path.join(site_packages, \"altair\", \"__init__.py\")\n\nloader = SourceFileLoader(\"altair\", altair_init)\nalt = loader.load_module()\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\"\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\"]\n\n# Data - Response times across task types and expertise levels\nnp.random.seed(42)\n\ncategories = [\"Simple\", \"Moderate\", \"Complex\"]\ngroups = [\"Novice\", \"Expert\"]\nn_per_group = 40\n\ndata = []\nfor cat in categories:\n    for grp in groups:\n        base = {\"Simple\": 2.0, \"Moderate\": 4.5, \"Complex\": 8.0}[cat]\n        if grp == \"Expert\":\n            base *= 0.6\n            spread = 0.8\n        else:\n            spread = 1.5\n\n        values = np.random.normal(base, spread, n_per_group)\n        values = np.clip(values, 0.5, 15)\n\n        for v in values:\n            data.append({\"Task Type\": cat, \"Expertise\": grp, \"Response Time (s)\": v})\n\ndf = pd.DataFrame(data)\n\n# Create base chart with Okabe-Ito colors\nbase = alt.Chart(df).encode(\n    color=alt.Color(\n        \"Expertise:N\",\n        scale=alt.Scale(domain=[\"Novice\", \"Expert\"], range=IMPRINT[:2]),\n        legend=alt.Legend(\n            title=\"Expertise\", titleFontSize=22, labelFontSize=20, orient=\"right\", symbolSize=400, offset=20\n        ),\n    )\n)\n\n# Violin plot using density transform\nviolin = (\n    base.transform_density(\n        \"Response Time (s)\", as_=[\"Response Time (s)\", \"density\"], groupby=[\"Task Type\", \"Expertise\"]\n    )\n    .mark_area(orient=\"horizontal\", opacity=0.5)\n    .encode(\n        x=alt.X(\n            \"density:Q\",\n            stack=\"center\",\n            impute=None,\n            title=None,\n            axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False),\n        ),\n        y=alt.Y(\"Response Time (s):Q\", title=\"Response Time (s)\", axis=alt.Axis(grid=True, gridOpacity=0.15)),\n    )\n)\n\n# Swarm points with jitter\nswarm = (\n    base.mark_circle(opacity=0.85, size=120)\n    .encode(\n        x=alt.X(\n            \"jitter:Q\",\n            title=None,\n            axis=alt.Axis(labels=False, values=[0], grid=False, ticks=False),\n            scale=alt.Scale(domain=[-1, 1]),\n        ),\n        y=alt.Y(\"Response Time (s):Q\"),\n        color=alt.Color(\"Expertise:N\", scale=alt.Scale(domain=[\"Novice\", \"Expert\"], range=IMPRINT[:2]), legend=None),\n    )\n    .transform_calculate(jitter='(random() - 0.5) * 0.15 + (datum.Expertise === \"Novice\" ? -0.3 : 0.3)')\n)\n\n# Layer and facet\nchart = (\n    alt.layer(violin, swarm)\n    .facet(\n        column=alt.Column(\n            \"Task Type:N\",\n            sort=categories,\n            header=alt.Header(title=\"Task Type\", titleFontSize=24, labelFontSize=22, labelOrient=\"bottom\"),\n        )\n    )\n    .resolve_scale(x=\"independent\")\n    .properties(\n        title=alt.Title(\n            \"violin-grouped-swarm · Python · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", offset=20, color=INK\n        ),\n        background=PAGE_BG,\n    )\n    .configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0, continuousWidth=400, continuousHeight=700)\n    .configure_facet(spacing=40)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save PNG and HTML with theme-suffixed filenames\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}