{"spec_id":"cat-box-strip","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ncat-box-strip: Box Plot with Strip Overlay\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\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\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Data: Product quality scores across departments\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"Support\"]\ndata = []\n\n# Create varied distributions per department\nfor dept in departments:\n    if dept == \"Engineering\":\n        # Higher scores, tight distribution\n        values = np.random.normal(85, 6, 40)\n    elif dept == \"Marketing\":\n        # Medium scores, wider spread\n        values = np.random.normal(72, 12, 35)\n        # Add some outliers\n        values = np.append(values, [45, 48, 98])\n    elif dept == \"Sales\":\n        # Lower scores, moderate spread\n        values = np.random.normal(65, 10, 45)\n        # Add outliers\n        values = np.append(values, [35, 92, 95])\n    else:  # Support\n        # Bimodal distribution\n        values = np.concatenate([np.random.normal(60, 8, 20), np.random.normal(80, 5, 25)])\n\n    for v in values:\n        data.append({\"Department\": dept, \"Quality Score\": np.clip(v, 30, 100), \"Series\": \"Data Point\"})\n\ndf = pd.DataFrame(data)\n\n# Box plot layer\nboxplot = (\n    alt.Chart(df)\n    .mark_boxplot(size=60, color=BRAND, median={\"color\": \"#954477\", \"strokeWidth\": 3}, opacity=0.8)\n    .encode(\n        x=alt.X(\"Department:N\", title=\"Department\", axis=alt.Axis(labelFontSize=18, titleFontSize=22, labelAngle=0)),\n        y=alt.Y(\n            \"Quality Score:Q\",\n            title=\"Quality Score\",\n            scale=alt.Scale(domain=[25, 105]),\n            axis=alt.Axis(labelFontSize=18, titleFontSize=22),\n        ),\n    )\n)\n\n# Strip plot layer with jitter\nstrip = (\n    alt.Chart(df)\n    .mark_circle(size=100, color=BRAND, opacity=0.6)\n    .encode(\n        x=alt.X(\"Department:N\"),\n        y=alt.Y(\"Quality Score:Q\"),\n        xOffset=\"jitter:Q\",\n        tooltip=[\"Department:N\", alt.Tooltip(\"Quality Score:Q\", format=\".1f\")],\n    )\n    .transform_calculate(jitter=\"sqrt(-2*log(random()))*cos(2*PI*random())*15\")\n)\n\n# Combine layers\nchart = (\n    alt.layer(boxplot, strip)\n    .properties(\n        width=1600,\n        height=900,\n        background=PAGE_BG,\n        title=alt.Title(\"cat-box-strip · altair · anyplot.ai\", fontSize=28, anchor=\"middle\"),\n    )\n    .configure_title(color=INK)\n    .configure_axis(\n        labelFontSize=18,\n        titleFontSize=22,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n)\n\n# Save as PNG and HTML\nchart.save(f\"plot-{THEME}.png\", scale_factor=3.0)\nchart.save(f\"plot-{THEME}.html\")\n"}