{"spec_id":"strip-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nstrip-basic: Basic Strip Plot\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\n\nimport altair as alt\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\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\"\n\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\nACCENT = \"#C475FD\"  # Imprint palette position 2 — mean markers\n\n# Data — survey response scores by department\nnp.random.seed(42)\n\ndepartments = [\"Engineering\", \"Marketing\", \"Sales\", \"Support\"]\ndistributions = {\"Engineering\": (75, 10), \"Marketing\": (68, 15), \"Sales\": (72, 12), \"Support\": (65, 18)}\n\nrows = []\nfor dept in departments:\n    mean, std = distributions[dept]\n    n = np.random.randint(35, 50)\n    scores = np.clip(np.random.normal(mean, std, n), 20, 100)\n    for score in scores:\n        rows.append({\"Department\": dept, \"Response Score\": score})\n\ndf = pd.DataFrame(rows)\n\nmeans = df.groupby(\"Department\")[\"Response Score\"].mean().reset_index()\nmeans.columns = [\"Department\", \"Mean\"]\nmeans[\"Label\"] = \"Group Mean\"\n\ntop_dept = means.loc[means[\"Mean\"].idxmax()]\nsubtitle = f\"{top_dept['Department']} leads with the highest mean score ({top_dept['Mean']:.0f})\"\n\n# Strip chart with Gaussian jitter via transform_calculate\nstrip = (\n    alt.Chart(df)\n    .mark_circle(size=70, opacity=0.55, color=BRAND)\n    .encode(\n        x=alt.X(\"Department:N\", title=\"Department\", axis=alt.Axis(labelAngle=0)),\n        y=alt.Y(\"Response Score:Q\", title=\"Response Score (0–100)\", scale=alt.Scale(domain=[30, 105])),\n        xOffset=\"jitter:Q\",\n        tooltip=[\"Department:N\", alt.Tooltip(\"Response Score:Q\", format=\".1f\")],\n    )\n    .transform_calculate(jitter=\"sqrt(-2*log(random()))*cos(2*PI*random())*0.27\")\n)\n\n# Mean reference ticks with legend entry\nmean_ticks = (\n    alt.Chart(means)\n    .mark_tick(thickness=2, size=16)\n    .encode(\n        x=alt.X(\"Department:N\"),\n        y=alt.Y(\"Mean:Q\"),\n        color=alt.Color(\n            \"Label:N\",\n            scale=alt.Scale(domain=[\"Group Mean\"], range=[ACCENT]),\n            legend=alt.Legend(\n                title=\"\",\n                orient=\"bottom\",\n                direction=\"horizontal\",\n                labelFontSize=10,\n                symbolType=\"stroke\",\n                symbolStrokeWidth=2,\n                symbolSize=90,\n            ),\n        ),\n        tooltip=[alt.Tooltip(\"Mean:Q\", format=\".1f\", title=\"Group Mean\")],\n    )\n)\n\n# Combine and apply theme-adaptive chrome\nchart = (\n    alt.layer(strip, mean_ticks)\n    .properties(\n        width=620,\n        height=320,\n        padding={\"left\": 0, \"right\": 0, \"top\": 0, \"bottom\": 0},\n        title=alt.Title(\n            \"strip-basic · python · altair · anyplot.ai\",\n            subtitle=subtitle,\n            fontSize=16,\n            subtitleFontSize=11,\n            subtitleColor=INK_SOFT,\n        ),\n        background=PAGE_BG,\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0, continuousWidth=620, continuousHeight=320)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelColor=INK_SOFT,\n        titleColor=INK,\n        labelFontSize=10,\n        titleFontSize=12,\n    )\n    .configure_title(color=INK)\n    .configure_legend(fillColor=ELEVATED_BG, labelColor=INK_SOFT, titleColor=INK, titleFontSize=10)\n)\n\n# Save — pad to the canonical 3200x1800 target (vl-convert never overshoots this small a view)\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTW, TH = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TW or _h > TH:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}x{_h}, exceeds target {TW}x{TH}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TW or _h < TH:\n    _canvas = Image.new(\"RGB\", (TW, TH), PAGE_BG)\n    _canvas.paste(_img, ((TW - _w) // 2, (TH - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}