{"spec_id":"histogram-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\nhistogram-basic: Basic Histogram\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 88/100 | Updated: 2026-05-28\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\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\nBRAND = \"#009E73\"\n\n# Data\nnp.random.seed(42)\nprimary = np.random.normal(loc=170, scale=7, size=350)\ntaller = np.random.normal(loc=186, scale=4.5, size=150)\nvalues = np.concatenate([primary, taller])\n\ndf = pd.DataFrame({\"height\": values})\n\nprimary_peak = np.median(primary)\ntaller_peak = np.median(taller)\nmean_val = df[\"height\"].mean()\n\n# Histogram bars\nbars = (\n    alt.Chart(df)\n    .mark_bar(\n        color=BRAND, stroke=INK_SOFT, strokeWidth=0.5, cornerRadiusTopLeft=2, cornerRadiusTopRight=2, opacity=0.85\n    )\n    .encode(\n        alt.X(\"height:Q\", bin=alt.Bin(maxbins=30), title=\"Height (cm)\"),\n        alt.Y(\"count()\", title=\"Frequency\"),\n        tooltip=[\n            alt.Tooltip(\"height:Q\", bin=alt.Bin(maxbins=30), title=\"Height Range\"),\n            alt.Tooltip(\"count()\", title=\"Count\"),\n        ],\n    )\n)\n\n# Annotation data for the two peaks\npeaks_df = pd.DataFrame(\n    {\n        \"x\": [primary_peak, taller_peak],\n        \"label\": [\n            f\"Primary group (μ ≈ {primary_peak:.0f} cm, n=350)\",\n            f\"Taller subgroup (μ ≈ {taller_peak:.0f} cm, n=150)\",\n        ],\n        \"y_offset\": [50, 30],\n    }\n)\n\n# Vertical rule lines at peak locations\nrules = (\n    alt.Chart(peaks_df)\n    .mark_rule(strokeDash=[6, 4], strokeWidth=1.5, opacity=0.8)\n    .encode(x=\"x:Q\", color=alt.value(\"#DDCC77\"))\n)\n\n# Peak labels — primary on left side, taller on right side\nprimary_label = (\n    alt.Chart(peaks_df.iloc[[0]])\n    .mark_text(align=\"left\", dx=10, fontSize=10, fontWeight=\"normal\")\n    .encode(x=\"x:Q\", y=\"y_offset:Q\", text=\"label:N\", color=alt.value(INK_SOFT))\n)\n\ntaller_label = (\n    alt.Chart(peaks_df.iloc[[1]])\n    .mark_text(align=\"left\", dx=10, fontSize=10, fontWeight=\"normal\")\n    .encode(x=\"x:Q\", y=\"y_offset:Q\", text=\"label:N\", color=alt.value(INK_SOFT))\n)\n\n# Mean line\nmean_df = pd.DataFrame({\"x\": [mean_val], \"label\": [f\"Mean: {mean_val:.1f} cm\"]})\n\nmean_rule = (\n    alt.Chart(mean_df)\n    .mark_rule(strokeDash=[2, 2], strokeWidth=1.2, opacity=0.6)\n    .encode(x=\"x:Q\", color=alt.value(INK_MUTED))\n)\n\nmean_label = (\n    alt.Chart(mean_df)\n    .mark_text(align=\"left\", dx=8, fontSize=10, fontStyle=\"italic\")\n    .encode(x=\"x:Q\", y=alt.datum(52), text=\"label:N\", color=alt.value(INK_MUTED))\n)\n\n# Layer all elements\nchart = (\n    (bars + rules + primary_label + taller_label + mean_rule + mean_label)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            \"histogram-basic · python · altair · anyplot.ai\",\n            fontSize=16,\n            subtitle=\"Distribution of human heights — bimodal pattern with primary and taller subgroups\",\n            subtitleFontSize=12,\n            subtitleColor=INK_SOFT,\n            anchor=\"start\",\n            offset=12,\n            color=INK,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        labelFontSize=10,\n        titleFontSize=12,\n        titleColor=INK,\n        labelColor=INK_SOFT,\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        grid=False,\n    )\n    .configure_axisY(grid=True, gridColor=INK, gridOpacity=0.12, gridDash=[4, 4], tickCount=6)\n)\n\n# Save PNG\nchart.save(f\"plot-{THEME}.png\", scale_factor=4.0)\n\n# Pad to exact canvas target 3200 × 1800\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}×{_h}, exceeds target {TW}×{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\n# Save HTML\nchart.save(f\"plot-{THEME}.html\")\n"}