{"spec_id":"ridgeline-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nridgeline-basic: Basic Ridgeline Plot\nLibrary: plotnine 0.15.7 | Python 3.13.14\nQuality: 89/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_line,\n    geom_ribbon,\n    geom_text,\n    ggplot,\n    labs,\n    scale_fill_gradient,\n    scale_y_continuous,\n    theme,\n    theme_minimal,\n)\nfrom scipy import stats\n\n\n# Theme tokens\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\"\nACCENT = \"#009E73\"  # Imprint palette position 1 — focal annotation accent\n\n# Data - Monthly temperature distributions for a temperate climate\nnp.random.seed(42)\n\nmonths = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\n\ntemp_params = {\n    \"Jan\": (2, 3),\n    \"Feb\": (4, 3),\n    \"Mar\": (8, 5),\n    \"Apr\": (13, 4),\n    \"May\": (18, 4),\n    \"Jun\": (22, 3),\n    \"Jul\": (25, 3),\n    \"Aug\": (24, 3),\n    \"Sep\": (20, 4.5),\n    \"Oct\": (14, 4),\n    \"Nov\": (8, 4),\n    \"Dec\": (4, 3),\n}\n\n# Generate raw samples for KDE\ndata = []\nfor month in months:\n    mean, std = temp_params[month]\n    values = np.random.normal(mean, std, 200)\n    for v in values:\n        data.append({\"month\": month, \"temp\": v})\n\ndf = pd.DataFrame(data)\n\n# Compute KDE density curves for ridgeline layout\nx_range = np.linspace(-10, 40, 300)\nridge_scale = 2.5\n\n# Trim each month's near-zero-density tails before building ridge_df so the\n# ribbon fill (and the top-edge line drawn separately below) only cover the\n# visible bump, instead of a sliver extending across the full x_range. The\n# threshold mask alone isn't guaranteed contiguous -- gaussian_kde's tail\n# estimate can ripple back above a lenient threshold far from the mode -- so\n# walk outward from the density peak and stop at the first drop below\n# threshold, keeping only that single contiguous run for geom_line to trace.\ndensity_data = []\nfor i, month in enumerate(months):\n    month_data = df[df[\"month\"] == month][\"temp\"]\n    kde = stats.gaussian_kde(month_data)\n    density = kde(x_range)\n    density_scaled = density / density.max() * ridge_scale\n\n    threshold = 0.05 * ridge_scale\n    peak_idx = int(np.argmax(density_scaled))\n    left = peak_idx\n    while left > 0 and density_scaled[left - 1] > threshold:\n        left -= 1\n    right = peak_idx\n    while right < len(density_scaled) - 1 and density_scaled[right + 1] > threshold:\n        right += 1\n\n    x_visible = x_range[left : right + 1]\n    density_visible = density_scaled[left : right + 1]\n\n    for x, d in zip(x_visible, density_visible, strict=True):\n        density_data.append(\n            {\"x\": x, \"ymin\": float(i), \"ymax\": float(i) + d, \"group\": month, \"month_idx\": float(i) / 11.0}\n        )\n\nridge_df = pd.DataFrame(density_data)\nridge_df[\"group\"] = pd.Categorical(ridge_df[\"group\"], categories=months, ordered=True)\n\n# Peak label data: placed at July's baseline level (y=jul_idx) to the right\n# of where the Jul ridge tapers off — clearly within July's y-band on the axis\njul_idx = months.index(\"Jul\")\npeak_df = pd.DataFrame([{\"x\": 34.5, \"y\": float(jul_idx) + 0.5, \"label\": \"Peak: Jul ≈ 25°C\"}])\n\n# Plot — month order is a continuous temporal axis, so the ridges use the\n# Imprint sequential gradient (imprint_seq: brand green -> blue) rather than\n# a categorical palette; this keeps January anchored at #009E73.\nplot = (\n    ggplot(ridge_df, aes(x=\"x\", ymin=\"ymin\", ymax=\"ymax\", fill=\"month_idx\", group=\"group\"))\n    # No ribbon outline: the ymin edge would stroke a flat line across each\n    # ridge's full visible x-span, cutting through neighboring ridges. Instead\n    # only the top density curve (ymax) is stroked, drawn as a separate line.\n    + geom_ribbon(alpha=0.85, color=None)\n    + geom_line(aes(y=\"ymax\"), color=INK_SOFT, size=0.5)\n    + scale_fill_gradient(low=\"#009E73\", high=\"#4467A3\")\n    # geom_text from a separate dataframe anchored to July's y-band (showcases multi-layer grammar)\n    + geom_text(\n        data=peak_df,\n        mapping=aes(x=\"x\", y=\"y\", label=\"label\"),\n        inherit_aes=False,\n        color=ACCENT,\n        size=3.5,\n        fontweight=\"bold\",\n        ha=\"right\",\n        va=\"center\",\n    )\n    # Diagonal leader segment from label anchor to July's density peak at (25, jul_idx+ridge_scale)\n    + annotate(\n        \"segment\", x=25.5, xend=33.5, y=jul_idx + ridge_scale - 0.3, yend=float(jul_idx) + 0.5, color=ACCENT, size=0.8\n    )\n    + scale_y_continuous(breaks=list(range(12)), labels=months, limits=(-0.5, 13.8))\n    + labs(\n        x=\"Temperature (°C)\",\n        y=\"Month\",\n        title=\"ridgeline-basic · python · plotnine · anyplot.ai\",\n        subtitle=\"Monthly temperature distributions — Northern Hemisphere temperate climate\",\n    )\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\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        text=element_text(size=7, color=INK_SOFT),\n        axis_title=element_text(size=10, color=INK),\n        axis_text=element_text(size=8, color=INK_SOFT),\n        plot_title=element_text(size=12, color=INK, fontweight=\"bold\"),\n        plot_subtitle=element_text(size=8, color=INK_SOFT),\n        plot_margin=0.03,\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_x=element_line(color=INK, size=0.3, alpha=0.10),\n        legend_position=\"none\",\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}