{"spec_id":"histogram-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nhistogram-basic: Basic Histogram\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-28\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_histogram,\n    geom_label,\n    geom_vline,\n    ggplot,\n    ggsize,\n    labs,\n    layer_tooltips,\n    scale_fill_manual,\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\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\"\nGRID = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\nLEGEND_BORDER = PAGE_BG if THEME == \"light\" else INK_SOFT\n\n# Imprint palette positions 1 and 2\nBRAND = \"#009E73\"\nCOLOR_B = \"#C475FD\"\n\n# Data — two cherry cultivars: Yoshino (symmetric), Kwanzan (right-skewed, outlier tail)\nnp.random.seed(42)\ncultivar_a = np.random.normal(28.5, 3.8, 350)  # Yoshino: symmetric distribution\n\ncultivar_b_main = np.random.normal(37.0, 4.0, 220)  # Kwanzan: main cluster\ncultivar_b_tail = np.random.uniform(48.0, 58.0, 30)  # outlier tail — large specimens\ncultivar_b = np.concatenate([cultivar_b_main, cultivar_b_tail])\n\ndf = pd.DataFrame(\n    {\"diameter\": np.concatenate([cultivar_a, cultivar_b]), \"cultivar\": [\"Yoshino\"] * 350 + [\"Kwanzan\"] * 250}\n)\n\nmean_a = float(np.mean(cultivar_a))\nmean_b = float(np.mean(cultivar_b))\n\n# Axis limits from percentiles — clips extremes gracefully\nall_diameters = np.concatenate([cultivar_a, cultivar_b])\nx_min = float(np.floor(np.percentile(all_diameters, 0.5)) - 1)\nx_max = float(np.ceil(np.percentile(all_diameters, 99.5)) + 1)\n\n# Annotation y at 93% of peak bar height — above histogram body, clear of bars\ncounts_a, _ = np.histogram(cultivar_a, bins=30, range=(x_min, x_max))\ncounts_b, _ = np.histogram(cultivar_b, bins=30, range=(x_min, x_max))\nannot_y = round(max(counts_a.max(), counts_b.max()) * 0.93)\n\nannotations_a = pd.DataFrame({\"x\": [mean_a + 2.5], \"y\": [annot_y], \"label\": [f\"Yoshino avg {mean_a:.1f} cm\"]})\nannotations_b = pd.DataFrame({\"x\": [mean_b + 2.0], \"y\": [annot_y], \"label\": [f\"Kwanzan avg {mean_b:.1f} cm\"]})\n\n# Plot\ntitle = \"histogram-basic · python · letsplot · anyplot.ai\"\n\nplot = (\n    ggplot(df, aes(x=\"diameter\", fill=\"cultivar\"))\n    + geom_histogram(\n        bins=30,\n        alpha=0.65,\n        size=0.2,\n        color=\"white\",\n        position=\"identity\",\n        tooltips=layer_tooltips()\n        .format(\"..count..\", \"d\")\n        .format(\"^x\", \".1f\")\n        .line(\"@|cultivar\")\n        .line(\"Count|@..count..\"),\n    )\n    + scale_fill_manual(values={\"Yoshino\": BRAND, \"Kwanzan\": COLOR_B}, name=\"Cultivar\")\n    + geom_vline(xintercept=mean_a, color=BRAND, size=1.2, linetype=\"dashed\")\n    + geom_vline(xintercept=mean_b, color=COLOR_B, size=1.2, linetype=\"dashed\")\n    + geom_label(\n        data=annotations_a,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        size=7,\n        color=BRAND,\n        fill=ELEVATED_BG,\n        label_padding=0.3,\n        fontface=\"bold\",\n        inherit_aes=False,\n    )\n    + geom_label(\n        data=annotations_b,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        size=7,\n        color=COLOR_B,\n        fill=ELEVATED_BG,\n        label_padding=0.3,\n        fontface=\"bold\",\n        inherit_aes=False,\n    )\n    + scale_x_continuous(name=\"Trunk Diameter (cm)\", format=\".0f\", limits=[x_min, x_max])\n    + scale_y_continuous(name=\"Frequency\", format=\"d\")\n    + labs(title=title, subtitle=\"Yoshino (symmetric) vs. Kwanzan (right-skewed, outlier tail) — same orchard\")\n    + theme_minimal()\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        plot_subtitle=element_text(size=12, color=INK_SOFT),\n        axis_title=element_text(size=12, color=INK),\n        axis_text=element_text(size=10, color=INK_SOFT),\n        legend_background=element_rect(fill=ELEVATED_BG, color=LEGEND_BORDER),\n        legend_title=element_text(size=10, face=\"bold\", color=INK),\n        legend_text=element_text(size=10, color=INK_SOFT),\n        legend_position=[0.88, 0.82],\n        panel_grid_major_x=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_y=element_line(color=GRID, size=0.5),\n    )\n    + ggsize(800, 450)\n)\n\n# Save\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}