{"spec_id":"histogram-kde","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nhistogram-kde: Histogram with KDE Overlay\nLibrary: bokeh 3.9.2 | Python 3.13.14\nQuality: 91/100 | Updated: 2026-08-05\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\nimport numpy as np\n\n\n# Remove current directory from path to avoid importing bokeh.py instead of bokeh package\nscript_dir = os.path.dirname(os.path.abspath(__file__)) if \"__file__\" in globals() else os.getcwd()\nif sys.path and sys.path[0] in (\"\", \".\", script_dir):\n    sys.path.pop(0)\n\n# Change to script directory to save output files there\nos.chdir(script_dir)\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, Label, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme setup\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\"\n\n# Imprint palette (first series always #009E73)\nHISTOGRAM_COLOR = \"#009E73\"  # Imprint palette position 1\nKDE_COLOR = \"#C475FD\"  # Imprint palette position 2\n\n# Data - Simulating stock returns distribution (realistic financial data)\nnp.random.seed(42)\n# Mix of normal market conditions and some fat-tail events\nmain_returns = np.random.normal(0.05, 2.5, 800)  # Daily returns in %\ntail_events = np.concatenate(\n    [\n        np.random.normal(-8, 1.5, 50),  # Negative tail events\n        np.random.normal(10, 2, 50),  # Positive tail events\n    ]\n)\nvalues = np.concatenate([main_returns, tail_events])\n\n# Histogram computation (density-normalized)\nbin_count = 40\nhist, bin_edges = np.histogram(values, bins=bin_count, density=True)\n\n# KDE computation using Gaussian kernel (Scott's rule bandwidth)\nx_kde = np.linspace(values.min() - 2, values.max() + 2, 500)\nbandwidth = 1.06 * np.std(values) * len(values) ** (-1 / 5)\ny_kde = np.zeros_like(x_kde)\nfor xi in values:\n    y_kde += np.exp(-0.5 * ((x_kde - xi) / bandwidth) ** 2)\ny_kde /= len(values) * bandwidth * np.sqrt(2 * np.pi)\nmean_return = float(np.mean(values))\n\n# Create figure — toolbar_location=None prevents extra height above the canvas\nhist_hover = HoverTool(tooltips=[(\"Range\", \"@left{0.00} - @right{0.00}\"), (\"Density\", \"@top{0.00}\")])\np = figure(\n    width=3200,\n    height=1800,\n    title=\"histogram-kde · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Daily Return (%)\",\n    y_axis_label=\"Density\",\n    toolbar_location=None,\n    tools=[hist_hover],\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=60,\n)\n\n# Histogram using quad glyphs\nhist_source = ColumnDataSource(\n    data={\"left\": bin_edges[:-1], \"right\": bin_edges[1:], \"top\": hist, \"bottom\": [0] * len(hist)}\n)\n\np.quad(\n    left=\"left\",\n    right=\"right\",\n    top=\"top\",\n    bottom=\"bottom\",\n    source=hist_source,\n    fill_color=HISTOGRAM_COLOR,\n    fill_alpha=0.5,\n    line_color=HISTOGRAM_COLOR,\n    line_alpha=0.8,\n    line_width=3,\n    legend_label=\"Histogram\",\n)\n\n# KDE curve — subtle area fill beneath the line adds depth without hiding the histogram\nkde_source = ColumnDataSource(data={\"x\": x_kde, \"y\": y_kde})\np.varea(x=\"x\", y1=0, y2=\"y\", source=kde_source, fill_color=KDE_COLOR, fill_alpha=0.12)\np.line(x=\"x\", y=\"y\", source=kde_source, line_color=KDE_COLOR, line_width=4, legend_label=\"KDE\")\n\n# Mean marker — dashed reference line gives the reader an immediate focal point\nmean_span = Span(location=mean_return, dimension=\"height\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2)\np.add_layout(mean_span)\nmean_label = Label(\n    x=mean_return,\n    y=float(y_kde.max()) * 1.02,\n    text=f\"Mean: {mean_return:.2f}%\",\n    text_font_size=\"24pt\",\n    text_color=INK_SOFT,\n    x_offset=10,\n)\np.add_layout(mean_label)\n\n# Fat-tail annotation — calls out the positive tail bump to sharpen the\n# visual story beyond the single mean-line focal point\ntail_mask = x_kde > 8\ntail_peak_idx = np.where(tail_mask)[0][np.argmax(y_kde[tail_mask])]\ntail_peak_x = float(x_kde[tail_peak_idx])\ntail_peak_y = float(y_kde[tail_peak_idx])\ntail_label = Label(\n    x=tail_peak_x,\n    y=tail_peak_y + float(y_kde.max()) * 0.08,\n    text=\"Fat-tail events\",\n    text_font_size=\"22pt\",\n    text_font_style=\"italic\",\n    text_color=INK_SOFT,\n    text_align=\"center\",\n)\np.add_layout(tail_label)\n\n# Add hover tool for KDE curve\nkde_hover = HoverTool(tooltips=[(\"Return (%)\", \"@x{0.00}\"), (\"Density\", \"@y{0.0000}\")])\np.add_tools(kde_hover)\n\n# Title styling\np.title.text_font_size = \"50pt\"\np.title.text_color = INK\n\n# Axis styling (canonical 3200x1800 sizing)\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\n# Grid styling - horizontal only, subtle (vertical grid adds no value across histogram bins)\np.xgrid.grid_line_color = None\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.12\n\n# Legend styling\np.legend.label_text_font_size = \"34pt\"\np.legend.location = \"top_right\"\np.legend.background_fill_color = ELEVATED_BG\np.legend.border_line_color = INK_SOFT\np.legend.label_text_color = INK_SOFT\n\n# Axis and border colors — no outline box, L-shaped frame via left/bottom axis lines only\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Save as HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome — Selenium 4 / Selenium Manager auto-resolves the driver\nW, H = 3200, 1800\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n    \"--force-device-scale-factor=1\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Headless Chrome's --window-size sets the OUTER window, which still reserves a\n# phantom title-bar height even headless — pin the viewport exactly via CDP.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}