{"spec_id":"density-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\ndensity-basic: Basic Density Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-30\n\"\"\"\n\nimport sys\n\n\n# Remove script directory from sys.path so \"import pygal\" resolves to the installed package,\n# not this file itself (which would cause a circular import on the script's own name).\nif sys.path and sys.path[0]:\n    sys.path.pop(0)\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette — first series always #009E73\nIMPRINT_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\n\n# Data — test scores with clear bimodal structure (two student groups)\nnp.random.seed(42)\nmain_scores = np.random.normal(76, 8, 200)\nsecondary_scores = np.random.normal(52, 5, 70)\nscores = np.concatenate([main_scores, secondary_scores])\n\n# KDE with Gaussian kernel and Scott's rule bandwidth\nx_range = np.linspace(scores.min() - 8, scores.max() + 8, 400)\nn = len(scores)\nbandwidth = n ** (-1 / 5) * np.std(scores)\n\n# Combined density\ndensity = np.zeros_like(x_range)\nfor xi in scores:\n    density += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2)\ndensity /= n * bandwidth * np.sqrt(2 * np.pi)\n\n# Secondary component density (weighted by proportion) for visual storytelling\ndensity_sec = np.zeros_like(x_range)\nfor xi in secondary_scores:\n    density_sec += np.exp(-0.5 * ((x_range - xi) / bandwidth) ** 2)\ndensity_sec /= n * bandwidth * np.sqrt(2 * np.pi)\n\n# Imprint palette style — theme-adaptive chrome\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=IMPRINT_PALETTE,\n    title_font_size=66,\n    label_font_size=44,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    stroke_width=4,\n    opacity=0.60,\n    opacity_hover=0.85,\n    font_family=\"'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif\",\n)\n\n# Theme-adaptive CSS overrides for spine removal and chrome\ncss_rules = [\n    \"file://style.css\",\n    f\"inline:.plot .background {{fill: {PAGE_BG} !important; stroke: none !important; stroke-width: 0 !important;}}\",\n    f\"inline:.graph > .background {{fill: {PAGE_BG} !important; stroke: none !important;}}\",\n    f\"inline:.axis .guides .line {{stroke: {INK_MUTED} !important; stroke-width: 0.8px; opacity: 0.15;}}\",\n    \"inline:.axis.x > path.line {stroke: none !important; stroke-width: 0 !important;}\",\n    \"inline:.axis.y > path.line {stroke: none !important; stroke-width: 0 !important;}\",\n    f\"inline:.axis .guides text {{fill: {INK_MUTED} !important;}}\",\n    f\"inline:text.title {{font-weight: 600 !important; fill: {INK} !important;}}\",\n    \"inline:.axis text {font-weight: 400 !important;}\",\n    f\"inline:.legends text {{font-weight: 400 !important; fill: {INK_MUTED} !important;}}\",\n]\n\n# Chart — 3200×1800 landscape canvas\nchart = pygal.XY(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"density-basic · python · pygal · anyplot.ai\",\n    x_title=\"Test Score (points)\",\n    y_title=\"Density\",\n    show_dots=False,\n    fill=True,\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_box_size=28,\n    show_y_guides=True,\n    show_x_guides=False,\n    stroke_style={\"width\": 4, \"linecap\": \"round\"},\n    truncate_label=-1,\n    margin_top=40,\n    margin_right=40,\n    margin_bottom=30,\n    margin_left=20,\n    x_value_formatter=lambda x: f\"{x:.0f}\",\n    y_value_formatter=lambda y: f\"{y:.3f}\",\n    css=css_rules,\n    js=[],\n)\n\n# Main density curve (combined) — Imprint green, prominent filled area\nxy_combined = [(float(x), float(y)) for x, y in zip(x_range, density, strict=True)]\nchart.add(\"Test score distribution\", xy_combined)\n\n# Secondary component — Imprint lavender, highlights bimodal structure\nxy_sec = [(float(x), float(y)) for x, y in zip(x_range, density_sec, strict=True)]\nchart.add(\"Lower-scoring group\", xy_sec, stroke_style={\"width\": 3, \"linecap\": \"round\"}, fill=True)\n\n# Rug plot — subsample 100 representative observations so individual ticks stay distinguishable\nrug_sample = np.sort(np.random.choice(scores, size=100, replace=False))\nrug_height = max(density) * 0.12\nrug_data = []\nfor xi in rug_sample:\n    rug_data.append((float(xi), 0.0))\n    rug_data.append((float(xi), float(rug_height)))\n    rug_data.append((float(xi), 0.0))\n\nchart.add(\"Individual scores\", rug_data, stroke_style={\"width\": 0.8, \"linecap\": \"butt\"}, show_dots=False, fill=False)\n\n# Save outputs\nchart.render_to_png(f\"plot-{THEME}.png\")\nchart.render_to_file(f\"plot-{THEME}.html\")\n"}