{"spec_id":"histogram-kde","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nhistogram-kde: Histogram with KDE Overlay\nLibrary: altair 6.2.2 | Python 3.13.14\nQuality: 88/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\nfrom scipy.stats import gaussian_kde\n\n\n# Theme-adaptive 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\n# Imprint palette\nBRAND = \"#009E73\"  # Position 1 - first series\nACCENT = \"#C475FD\"  # Position 2 - for KDE line\n\n# Data - bimodal distribution for interesting KDE demonstration, clipped to the\n# conventional 0-100 test-score range\nnp.random.seed(42)\nvalues = np.concatenate([np.random.normal(loc=45, scale=8, size=300), np.random.normal(loc=72, scale=10, size=200)])\nvalues = np.clip(values, 0, 100)\n\n# Calculate histogram bins for density\nhist, bin_edges = np.histogram(values, bins=30, density=True)\nhist_df = pd.DataFrame(\n    {\n        \"bin_start\": bin_edges[:-1],\n        \"bin_end\": bin_edges[1:],\n        \"density\": hist,\n        \"base\": 0.0,\n        \"bin_label\": [f\"{s:.1f}–{e:.1f}\" for s, e in zip(bin_edges[:-1], bin_edges[1:], strict=True)],\n    }\n)\n\n# Calculate KDE\nkde = gaussian_kde(values, bw_method=\"scott\")\nx_kde = np.linspace(values.min() - 5, values.max() + 5, 200)\ny_kde = kde(x_kde)\nkde_df = pd.DataFrame({\"x\": x_kde, \"density\": y_kde})\n\n# Histogram bars using Imprint brand color, softly rounded for a less blocky feel\nhistogram = (\n    alt.Chart(hist_df)\n    .mark_bar(opacity=0.6, color=BRAND, cornerRadiusTopLeft=2, cornerRadiusTopRight=2)\n    .encode(\n        x=alt.X(\"bin_start:Q\", title=\"Test Score\", scale=alt.Scale(zero=False)),\n        x2=\"bin_end:Q\",\n        y=alt.Y(\"density:Q\", title=\"Density\"),\n        y2=\"base:Q\",\n        tooltip=[\n            alt.Tooltip(\"bin_label:N\", title=\"Score range\"),\n            alt.Tooltip(\"density:Q\", title=\"Density\", format=\".4f\"),\n        ],\n    )\n)\n\n# KDE line using Imprint position 2, monotone interpolation for a smoother contrast to the discrete bars\nkde_line = (\n    alt.Chart(kde_df)\n    .mark_line(color=ACCENT, strokeWidth=4, interpolate=\"monotone\")\n    .encode(\n        x=alt.X(\"x:Q\"),\n        y=alt.Y(\"density:Q\"),\n        tooltip=[\n            alt.Tooltip(\"x:Q\", title=\"Test Score\", format=\".1f\"),\n            alt.Tooltip(\"density:Q\", title=\"KDE density\", format=\".4f\"),\n        ],\n    )\n)\n\n# Combine and configure with theme-adaptive styling\n# Inner view sized small (Canvas table) so vl-convert's title/axis padding still\n# lands the saved PNG within the 3200x1800 landscape target.\nchart = (\n    (histogram + kde_line)\n    .properties(\n        width=620, height=320, background=PAGE_BG, title=alt.Title(\"histogram-kde · altair · anyplot.ai\", fontSize=28)\n    )\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.10,\n        labelFontSize=18,\n        labelColor=INK_SOFT,\n        titleFontSize=22,\n        titleColor=INK,\n    )\n    .configure_view(strokeWidth=0, fill=PAGE_BG)\n    .configure_title(color=INK)\n)\n\n# Save PNG and HTML with theme suffix\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\nchart.save(f\"plot-{THEME}.html\")\n\n# Pad the saved PNG up to the exact canonical target (3200x1800). Never crop —\n# cropping would clip title/axis-label content at the edges.\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"}