{"spec_id":"density-basic","library":"altair","language":"python","code":"\"\"\" anyplot.ai\ndensity-basic: Basic Density Plot\nLibrary: altair 6.1.0 | Python 3.13.13\nQuality: 93/100 | Updated: 2026-05-30\n\"\"\"\n\nimport importlib\nimport os\nimport sys\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\n\n# Drop script directory from sys.path so `altair` resolves the package, not this file\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\nalt = importlib.import_module(\"altair\")\n\n# Theme tokens — Imprint palette\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — ALWAYS first series\n\n# Data - bimodal distribution showing two student groups with distinct performance\nnp.random.seed(42)\nvalues = np.concatenate(\n    [\n        np.random.normal(loc=38, scale=7, size=200),  # Group A — foundational course\n        np.random.normal(loc=72, scale=8, size=150),  # Group B — advanced course\n    ]\n)\nvalues = np.clip(values, 5, 100)\n\ndf = pd.DataFrame({\"Test Score\": values})\n\n# Peak annotations to highlight the two modes of the bimodal distribution\npeaks = pd.DataFrame(\n    {\"Test Score\": [38, 72], \"density\": [0.032, 0.021], \"label\": [\"Foundational Course\", \"Advanced Course\"]}\n)\n\n# Nearest-point selection for interactive density readout (HTML export)\nnearest = alt.selection_point(nearest=True, on=\"pointerover\", fields=[\"Test Score\"], empty=False)\n\n# Density curve with filled area\ndensity_layer = (\n    alt.Chart(df)\n    .transform_density(\"Test Score\", as_=[\"Test Score\", \"density\"], bandwidth=4)\n    .mark_area(opacity=0.40, color=BRAND, line={\"color\": BRAND, \"strokeWidth\": 2.5})\n    .encode(\n        x=alt.X(\n            \"Test Score:Q\",\n            title=\"Test Score (points)\",\n            scale=alt.Scale(domain=[10, 100]),\n            axis=alt.Axis(tickCount=10, grid=False),\n        ),\n        y=alt.Y(\"density:Q\", title=\"Probability Density\", axis=alt.Axis(format=\".3f\")),\n        tooltip=[\n            alt.Tooltip(\"Test Score:Q\", title=\"Score\", format=\".1f\"),\n            alt.Tooltip(\"density:Q\", title=\"Density\", format=\".4f\"),\n        ],\n    )\n)\n\n# Invisible points on density curve driving nearest-point selection\nhover_points = (\n    alt.Chart(df)\n    .transform_density(\"Test Score\", as_=[\"Test Score\", \"density\"], bandwidth=4)\n    .mark_point(opacity=0)\n    .encode(x=\"Test Score:Q\", y=\"density:Q\")\n    .add_params(nearest)\n)\n\n# Hover dot — conditionally visible point at cursor position\nhover_dot = (\n    alt.Chart(df)\n    .transform_density(\"Test Score\", as_=[\"Test Score\", \"density\"], bandwidth=4)\n    .mark_point(size=80, filled=True, color=BRAND)\n    .encode(x=\"Test Score:Q\", y=\"density:Q\", opacity=alt.condition(nearest, alt.value(1), alt.value(0)))\n)\n\n# Peak annotation labels\nannotations = (\n    alt.Chart(peaks)\n    .mark_text(fontSize=11, fontWeight=\"bold\", color=INK, dy=-14)\n    .encode(x=\"Test Score:Q\", y=\"density:Q\", text=\"label:N\")\n)\n\n# Rug plot — tick marks showing individual observations at density=0\nrug = (\n    alt.Chart(df)\n    .mark_tick(color=BRAND, opacity=0.3, thickness=1.5, size=14)\n    .encode(x=alt.X(\"Test Score:Q\"), y=alt.Y(datum=0))\n)\n\n# Title length-scaled font size (default 16px at 67-char baseline)\ntitle_text = \"density-basic · python · altair · anyplot.ai\"\ntitle_fs = round(16 * 67 / len(title_text)) if len(title_text) > 67 else 16\n\nchart = (\n    alt.layer(density_layer, rug, annotations, hover_points, hover_dot)\n    .properties(\n        width=620,\n        height=320,\n        background=PAGE_BG,\n        title=alt.Title(\n            text=title_text,\n            subtitle=\"Kernel density estimation of test scores across two course levels\",\n            fontSize=title_fs,\n            subtitleFontSize=13,\n            subtitleColor=INK_SOFT,\n        ),\n    )\n    .configure_view(fill=PAGE_BG, strokeWidth=0)\n    .configure_axis(\n        domainColor=INK_SOFT,\n        tickColor=INK_SOFT,\n        gridColor=INK,\n        gridOpacity=0.12,\n        labelColor=INK_SOFT,\n        labelFontSize=10,\n        titleColor=INK,\n        titleFontSize=12,\n    )\n    .configure_title(color=INK)\n)\n\n# Save PNG then pad to exact 3200 × 1800 target\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}×{_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 interactive HTML with selection-driven hover readout\nchart.save(f\"plot-{THEME}.html\")\n"}