{"spec_id":"density-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\ndensity-basic: Basic Density Plot\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-30\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_density,\n    geom_segment,\n    geom_text,\n    geom_vline,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_x_continuous,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\nfrom lets_plot.export import ggsave\n\n\nLetsPlot.setup_html()\n\n# Theme tokens — Imprint palette, 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\"\nBRAND = \"#009E73\"  # Imprint palette position 1 — always first series\n\n# Data - Marathon finish times: trimodal distribution with realistic right skew\nnp.random.seed(42)\nfinish_minutes = np.concatenate(\n    [\n        np.random.normal(240, 25, 350),  # Main pack (~4 hour runners)\n        np.random.normal(200, 15, 100),  # Competitive runners (~3:20)\n        np.random.normal(300, 20, 50),  # Casual runners (~5 hours)\n    ]\n)\nfinish_minutes = np.clip(finish_minutes, 140, 400)\ndf = pd.DataFrame({\"time\": finish_minutes})\n\n# Rug data: individual observations as small vertical ticks at the x-axis\nrug_df = pd.DataFrame({\"x\": finish_minutes, \"y0\": 0.0, \"y1\": 0.0003})\n\n# Peak annotation data for the three runner sub-populations\npeaks_df = pd.DataFrame(\n    {\n        \"x\": [200, 240, 300],\n        \"y\": [0.0065, 0.0110, 0.0030],\n        \"label\": [\"Elite\\n~3:20\", \"Main Pack\\n~4:00\", \"Casual\\n~5:00\"],\n    }\n)\n\n# Title\ntitle = \"density-basic · python · letsplot · anyplot.ai\"\n\n# Chrome theme (theme-adaptive background, text, grid)\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_border=element_blank(),\n    panel_grid_major_x=element_blank(),\n    panel_grid_major_y=element_line(color=INK_SOFT, size=0.3),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=12),\n    axis_text=element_text(color=INK_SOFT, size=10),\n    axis_ticks=element_blank(),\n    plot_title=element_text(color=INK, size=16),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n)\n\n# Plot\nplot = (\n    ggplot(df, aes(x=\"time\"))\n    + geom_vline(data=peaks_df, mapping=aes(xintercept=\"x\"), color=INK_SOFT, linetype=\"dashed\", size=0.5, alpha=0.6)\n    + geom_density(\n        fill=BRAND,\n        color=BRAND,\n        alpha=0.35,\n        size=1.5,\n        kernel=\"gaussian\",\n        adjust=0.85,\n        trim=True,\n        tooltips=layer_tooltips().line(\"density|@..density..\"),\n    )\n    + geom_segment(data=rug_df, mapping=aes(x=\"x\", y=\"y0\", xend=\"x\", yend=\"y1\"), color=BRAND, alpha=0.40, size=0.6)\n    + geom_text(data=peaks_df, mapping=aes(x=\"x\", y=\"y\", label=\"label\"), color=INK, size=3.5, vjust=0, hjust=0.5)\n    + labs(x=\"Finish Time (minutes)\", y=\"Density (×10⁻³)\", title=title)\n    + scale_x_continuous(breaks=list(range(150, 401, 50)))\n    + scale_y_continuous(\n        breaks=[0.002, 0.004, 0.006, 0.008, 0.010], labels=[\"2\", \"4\", \"6\", \"8\", \"10\"], expand=[0.02, 0, 0.15, 0]\n    )\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(800, 450)\n)\n\n# Save — theme-suffixed filenames, scale=4 → 3200×1800 px\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}