{"spec_id":"violin-box","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nviolin-box: Violin Plot with Embedded Box Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-12\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\n\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\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data - Generate realistic response time data for different server tiers\nnp.random.seed(42)\n\ngroups = [\"Basic\", \"Standard\", \"Premium\", \"Enterprise\"]\nn_per_group = 80\n\ndata = []\n# Basic tier - higher latency, more variance\ndata.extend([(np.random.exponential(120) + 80, \"Basic\") for _ in range(n_per_group)])\n# Standard tier - moderate latency\ndata.extend([(np.random.normal(100, 25), \"Standard\") for _ in range(n_per_group)])\n# Premium tier - lower latency, tighter distribution\ndata.extend([(np.random.normal(60, 15), \"Premium\") for _ in range(n_per_group)])\n# Enterprise tier - lowest latency, bimodal (some cached, some not)\nenterprise_cached = np.random.normal(25, 8, n_per_group // 2)\nenterprise_uncached = np.random.normal(55, 12, n_per_group // 2)\ndata.extend([(v, \"Enterprise\") for v in np.concatenate([enterprise_cached, enterprise_uncached])])\n\ndf = pd.DataFrame(data, columns=[\"Response Time (ms)\", \"Server Tier\"])\ndf[\"Response Time (ms)\"] = df[\"Response Time (ms)\"].clip(lower=5)\n\n# Create violin plot layer using transform_density\nviolin = (\n    alt.Chart(df)\n    .transform_density(\"Response Time (ms)\", as_=[\"Response Time (ms)\", \"density\"], groupby=[\"Server Tier\"])\n    .mark_area(orient=\"horizontal\", opacity=0.6)\n    .encode(\n        y=alt.Y(\"Response Time (ms):Q\"),\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        color=alt.Color(\"Server Tier:N\", scale=alt.Scale(domain=groups, range=IMPRINT)),\n    )\n)\n\n# Create box plot layer\nboxplot = (\n    alt.Chart(df)\n    .mark_boxplot(\n        extent=\"min-max\",\n        size=25,\n        median={\"stroke\": INK_SOFT, \"strokeWidth\": 2},\n        box={\"fill\": INK_SOFT, \"fillOpacity\": 0.3},\n        outliers={\"size\": 60, \"strokeWidth\": 2, \"stroke\": INK_SOFT},\n    )\n    .encode(y=alt.Y(\"Response Time (ms):Q\", title=\"Response Time (ms)\"), x=alt.value(0), color=alt.value(INK_SOFT))\n)\n\n# Layer violin and box plots first, then facet\nlayered = alt.layer(violin, boxplot).properties(width=280, height=600)\n\n# Apply faceting after layering\nchart = (\n    layered.facet(\n        column=alt.Column(\n            \"Server Tier:N\",\n            header=alt.Header(titleFontSize=20, labelFontSize=18, labelOrient=\"bottom\"),\n            title=None,\n            sort=groups,\n        )\n    )\n    .properties(\n        title=alt.Title(\"violin-box · altair · anyplot.ai\", fontSize=28, anchor=\"middle\", offset=20), background=PAGE_BG\n    )\n    .configure_axis(\n        labelFontSize=16,\n        titleFontSize=20,\n        gridOpacity=0.0,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n    )\n    .configure_view(stroke=None, fill=PAGE_BG)\n    .configure_legend(\n        titleFontSize=18, labelFontSize=16, symbolSize=200, orient=\"right\", titleColor=INK, labelColor=INK_SOFT\n    )\n    .configure_title(color=INK)\n    .resolve_scale(x=\"independent\")\n)\n\n# Save outputs\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}