{"spec_id":"raincloud-basic","library":"plotnine","language":"python","code":"\"\"\" anyplot.ai\nraincloud-basic: Basic Raincloud Plot\nLibrary: plotnine 0.15.4 | Python 3.13.13\nQuality: 89/100 | Updated: 2026-05-26\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom plotnine import (\n    aes,\n    annotate,\n    coord_flip,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_boxplot,\n    geom_jitter,\n    geom_violin,\n    ggplot,\n    labs,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_discrete,\n    scale_y_continuous,\n    stage,\n    theme,\n    theme_minimal,\n)\n\n\n# Theme tokens\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — first series ALWAYS #009E73\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\n\n# Data — reaction times (ms) for three experimental conditions\nnp.random.seed(42)\ncontrol = np.random.normal(450, 60, 80)\ntreatment_a = np.random.normal(380, 50, 80)\ntreatment_b = np.concatenate([np.random.normal(350, 40, 50), np.random.normal(500, 45, 30)])\n\ndf = pd.DataFrame(\n    {\n        \"condition\": (\n            [\"Control\"] * len(control) + [\"Treatment A\"] * len(treatment_a) + [\"Treatment B\"] * len(treatment_b)\n        ),\n        \"reaction_time\": np.concatenate([control, treatment_a, treatment_b]),\n    }\n)\ndf[\"condition\"] = pd.Categorical(df[\"condition\"], categories=[\"Treatment B\", \"Treatment A\", \"Control\"], ordered=True)\n\n# Category → Imprint palette positions 1, 2, 3 (brand green is Control, the reference group)\ncolors = {\"Control\": IMPRINT_PALETTE[0], \"Treatment A\": IMPRINT_PALETTE[1], \"Treatment B\": IMPRINT_PALETTE[2]}\n\ncloud_shift = 0.15\n\n# Plot — horizontal raincloud via coord_flip\n# Pre-flip: x=condition (categorical), y=reaction_time. After flip: categories on y, values on x.\nplot = (\n    ggplot(df, aes(x=\"condition\", y=\"reaction_time\", fill=\"condition\", color=\"condition\"))\n    # Cloud (half-violin) — style=\"right\" extends positive x = upward after flip\n    + geom_violin(\n        aes(x=stage(\"condition\", after_scale=\"x+{0}\".format(cloud_shift))),\n        style=\"right\",\n        trim=True,\n        scale=\"width\",\n        size=0.3,\n        alpha=0.78,\n        show_legend=False,\n    )\n    # Boxplot — centered on category baseline, theme-adaptive fill/edge\n    + geom_boxplot(width=0.06, outlier_shape=\"\", fill=ELEVATED_BG, color=INK, size=0.5, alpha=0.95, show_legend=False)\n    # Rain (jittered points) — nudged negative x = downward after flip\n    + geom_jitter(\n        aes(x=stage(\"condition\", after_scale=\"x-0.18\")), width=0.06, height=0, size=3.0, alpha=0.6, show_legend=False\n    )\n    # Annotation: bimodal callout for Treatment B (category index 1, 0-based)\n    + annotate(\n        \"text\",\n        x=0.35,\n        y=425,\n        label=\"Bimodal: two distinct response clusters\",\n        size=13,\n        color=INK_SOFT,\n        ha=\"center\",\n        fontstyle=\"italic\",\n    )\n    # Arrows land inside the half-violin's two humps (above baseline x=1.15, at peak y=350 / y=500)\n    + annotate(\"segment\", x=0.5, xend=1.28, y=420, yend=350, size=0.5, color=INK_MUTED, linetype=\"dashed\")\n    + annotate(\"segment\", x=0.5, xend=1.28, y=430, yend=500, size=0.5, color=INK_MUTED, linetype=\"dashed\")\n    # Annotation: Treatment A shifted left\n    + annotate(\n        \"text\",\n        x=1.55,\n        y=290,\n        label=\"Faster responses\\nvs. Control →\",\n        size=12,\n        color=INK_SOFT,\n        ha=\"left\",\n        fontstyle=\"italic\",\n    )\n    + scale_fill_manual(values=colors)\n    + scale_color_manual(values=colors)\n    # Extra bottom padding (after coord_flip, the lower x = lower vertical) for the bimodal annotation\n    + scale_x_discrete(expand=(0, 0.95, 0, 0.6))\n    + scale_y_continuous(expand=(0.02, 0, 0.08, 0))\n    + coord_flip()\n    + labs(x=\"Experimental Condition\", y=\"Reaction Time (ms)\", title=\"raincloud-basic · python · plotnine · anyplot.ai\")\n    + theme_minimal()\n    + theme(\n        figure_size=(8, 4.5),\n        text=element_text(size=7, color=INK),\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=14, color=INK),\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.15),\n        panel_border=element_blank(),\n        axis_line=element_line(color=INK_SOFT, size=0.4),\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        legend_position=\"none\",\n        plot_margin=0.02,\n    )\n)\n\n# Save\nplot.save(f\"plot-{THEME}.png\", dpi=400, width=8, height=4.5, units=\"in\", verbose=False)\n"}