{"spec_id":"histogram-overlapping","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nhistogram-overlapping: Overlapping Histograms\nLibrary: altair 6.2.2 | Python 3.13.15\nQuality: 86/100 | Updated: 2026-08-18\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 (see prompts/default-style-guide.md \"Background\" + \"Theme-adaptive Chrome\")\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\n# Imprint palette (position 1 is ALWAYS #009E73 — see default-style-guide.md)\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nDEPARTMENTS = [\"Engineering\", \"Sales\", \"Support\"]\nCOLOR_SCALE = alt.Scale(domain=DEPARTMENTS, range=IMPRINT_PALETTE[:3])\n\n# Data: support-ticket response times (ms) by department\nnp.random.seed(42)\nengineering = np.random.normal(loc=350, scale=80, size=150)\nsales = np.random.normal(loc=420, scale=100, size=150)\nsupport = np.random.normal(loc=280, scale=60, size=150)\n\ndf = pd.DataFrame(\n    {\n        \"Response Time (ms)\": np.concatenate([engineering, sales, support]),\n        \"Department\": [\"Engineering\"] * 150 + [\"Sales\"] * 150 + [\"Support\"] * 150,\n    }\n)\n\n# Legend-bound selection — click a swatch to isolate that department's bars/rule\n# (Altair-distinctive declarative interaction; empty selection == everything selected,\n# so the static PNG render is unaffected and matches the un-highlighted default state)\nlegend_highlight = alt.selection_point(fields=[\"Department\"], bind=\"legend\", empty=True)\n\n# Plot — overlapping semi-transparent histograms; one shared x encoding keeps bin edges aligned.\n# Fill stays semi-transparent so overlaps blend, but each bar's own-color stroke (full opacity)\n# traces its boundary through the blended region for pairwise disambiguation without the legend.\nhistograms = (\n    alt.Chart(df)\n    .mark_bar(binSpacing=0, cornerRadiusTopLeft=2, cornerRadiusTopRight=2, strokeWidth=1.1)\n    .encode(\n        x=alt.X(\n            \"Response Time (ms):Q\",\n            bin=alt.Bin(maxbins=24),\n            title=\"Response Time (ms)\",\n            axis=alt.Axis(labelFontSize=11, titleFontSize=13, grid=False),\n        ),\n        y=alt.Y(\n            \"count():Q\",\n            title=\"Frequency\",\n            stack=None,\n            axis=alt.Axis(labelFontSize=11, titleFontSize=13, gridColor=INK, gridOpacity=0.15),\n        ),\n        color=alt.Color(\n            \"Department:N\",\n            scale=COLOR_SCALE,\n            legend=alt.Legend(\n                title=\"Department\",\n                titleFontSize=10,\n                labelFontSize=10,\n                orient=\"top-right\",\n                symbolSize=140,\n                symbolStrokeWidth=0,\n            ),\n        ),\n        stroke=alt.Stroke(\"Department:N\", scale=COLOR_SCALE, legend=None),\n        fillOpacity=alt.condition(legend_highlight, alt.value(0.55), alt.value(0.08)),\n        tooltip=[alt.Tooltip(\"Department:N\"), alt.Tooltip(\"count():Q\", title=\"Count\")],\n    )\n    .add_params(legend_highlight)\n)\n\n# Layer — dashed mean-rule per department, using Altair's inline aggregate encoding shorthand\nmean_rules = (\n    alt.Chart(df)\n    .mark_rule(strokeDash=[6, 4], strokeWidth=2.5)\n    .encode(\n        x=alt.X(\"mean(Response Time (ms)):Q\"),\n        color=alt.Color(\"Department:N\", scale=COLOR_SCALE, legend=None),\n        opacity=alt.condition(legend_highlight, alt.value(0.9), alt.value(0.08)),\n    )\n)\n\ntitle = \"histogram-overlapping · python · altair · anyplot.ai\"\nchart = (\n    alt.layer(histograms, mean_rules)\n    .properties(\n        width=620, height=320, background=PAGE_BG, title=alt.Title(title, fontSize=16, anchor=\"middle\", color=INK)\n    )\n    .configure_view(fill=PAGE_BG, stroke=None)\n    .configure_axis(domainColor=INK_SOFT, tickColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n    .configure_legend(fillColor=ELEVATED_BG, strokeColor=INK_SOFT, labelColor=INK_SOFT, titleColor=INK)\n)\n\n# Save — PNG padded to the canonical 3200×1800 canvas (see prompts/library/altair.md \"Canvas\")\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\nTARGET_W, TARGET_H = 3200, 1800\n_img = Image.open(f\"plot-{THEME}.png\").convert(\"RGB\")\n_w, _h = _img.size\nif _w > TARGET_W or _h > TARGET_H:\n    raise SystemExit(\n        f\"altair vl-convert produced {_w}x{_h}, exceeds target {TARGET_W}x{TARGET_H}. \"\n        f\"Shrink chart .properties(width=, height=) values and re-render.\"\n    )\nif _w < TARGET_W or _h < TARGET_H:\n    _canvas = Image.new(\"RGB\", (TARGET_W, TARGET_H), PAGE_BG)\n    _canvas.paste(_img, ((TARGET_W - _w) // 2, (TARGET_H - _h) // 2))\n    _canvas.save(f\"plot-{THEME}.png\")\n\nchart.save(f\"plot-{THEME}.html\")\n"}