{"spec_id":"raincloud-basic","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nraincloud-basic: Basic Raincloud Plot\nLibrary: letsplot 4.10.1 | Python 3.13.13\nQuality: 94/100 | Updated: 2026-05-26\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pandas as pd\nfrom lets_plot import (\n    LetsPlot,\n    aes,\n    arrow,\n    element_blank,\n    element_line,\n    element_rect,\n    element_text,\n    geom_boxplot,\n    geom_jitter,\n    geom_segment,\n    geom_text,\n    geom_violin,\n    ggplot,\n    ggsave,\n    ggsize,\n    labs,\n    position_nudge,\n    scale_color_manual,\n    scale_fill_manual,\n    scale_x_continuous,\n    scale_y_discrete,\n    theme,\n)\n\n\nLetsPlot.setup_html()\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\"\nRULE = \"rgba(26,26,23,0.15)\" if THEME == \"light\" else \"rgba(240,239,232,0.15)\"\n\n# anyplot categorical palette (canonical order, first series ALWAYS #009E73)\nBRAND = \"#009E73\"\nLAVENDER = \"#C475FD\"\nBLUE = \"#4467A3\"\n\n# Data — reaction times (ms) for three experimental conditions\nnp.random.seed(42)\n\ncontrol = np.random.normal(450, 60, 80)\ntreatment_a = np.random.normal(380, 50, 80)\ntreatment_b = np.concatenate([np.random.normal(300, 30, 50), np.random.normal(540, 35, 30)])\n\ndf = pd.DataFrame(\n    {\n        \"condition\": [\"Control\"] * len(control)\n        + [\"Treatment A\"] * len(treatment_a)\n        + [\"Treatment B\"] * len(treatment_b),\n        \"reaction_time\": np.concatenate([control, treatment_a, treatment_b]),\n    }\n)\n\npalette = {\"Control\": BRAND, \"Treatment A\": LAVENDER, \"Treatment B\": BLUE}\n\n# Display order (bottom to top of y-axis): Treatment B, Treatment A, Control\ncat_order = [\"Treatment B\", \"Treatment A\", \"Control\"]\n\nplot = (\n    ggplot(df, aes(x=\"reaction_time\", y=\"condition\", fill=\"condition\", color=\"condition\"))\n    # Half-violin (cloud) — nudged above the category baseline\n    + geom_violin(trim=False, show_half=1, size=0.8, alpha=0.7, position=position_nudge(y=0.12))\n    # Boxplot on the baseline with elevated-bg fill for contrast in both themes\n    + geom_boxplot(\n        width=0.18,\n        outlier_size=0,\n        outlier_alpha=0,\n        fill=ELEVATED_BG,\n        color=INK,\n        size=0.8,\n        alpha=0.95,\n        show_legend=False,\n    )\n    # Rain — jittered points below the baseline\n    + geom_jitter(\n        width=0,\n        height=0.05,\n        size=4.0,\n        alpha=0.5,\n        shape=21,\n        stroke=0.3,\n        show_legend=False,\n        position=position_nudge(y=-0.16),\n    )\n    # Annotation: Treatment A faster mean — text on the right (off the cloud), arrow lands just above the peak top\n    + geom_text(\n        x=690, y=1.55, label=\"~70ms faster mean\\nthan Control\", size=13, color=INK_SOFT, fontface=\"italic\", hjust=1\n    )\n    + geom_segment(x=550, y=1.45, xend=400, yend=1.70, color=INK_SOFT, size=0.5, arrow=arrow(length=8, type=\"closed\"))\n    # Annotation: Treatment B bimodal — text on the right, arrows land just above each peak top\n    + geom_text(\n        x=690, y=0.55, label=\"Two distinct\\nresponse clusters\", size=13, color=INK_SOFT, fontface=\"italic\", hjust=1\n    )\n    + geom_segment(x=550, y=0.45, xend=310, yend=0.55, color=INK_SOFT, size=0.5, arrow=arrow(length=8, type=\"closed\"))\n    + geom_segment(x=620, y=0.30, xend=555, yend=0.40, color=INK_SOFT, size=0.5, arrow=arrow(length=8, type=\"closed\"))\n    + scale_fill_manual(values=palette)\n    + scale_color_manual(values=palette)\n    + scale_y_discrete(limits=cat_order)\n    + scale_x_continuous(limits=[200, 700])\n    + labs(x=\"Reaction Time (ms)\", y=\"Experimental Condition\", title=\"raincloud-basic · python · letsplot · anyplot.ai\")\n    + theme(\n        plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        panel_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n        plot_title=element_text(size=16, face=\"bold\", color=INK),\n        axis_title_x=element_text(size=12, color=INK, margin=[12, 0, 0, 0]),\n        axis_title_y=element_text(size=12, color=INK, margin=[0, 12, 0, 0]),\n        axis_text_x=element_text(size=10, color=INK_SOFT),\n        axis_text_y=element_text(size=12, color=INK, face=\"bold\"),\n        axis_ticks=element_blank(),\n        axis_line_x=element_blank(),\n        axis_line_y=element_blank(),\n        legend_position=\"none\",\n        panel_grid_major_y=element_blank(),\n        panel_grid_minor=element_blank(),\n        panel_grid_major_x=element_line(color=RULE, size=0.4),\n        plot_margin=[40, 40, 30, 20],\n    )\n    + ggsize(800, 450)\n)\n\nggsave(plot, f\"plot-{THEME}.png\", path=\".\", scale=4)\nggsave(plot, f\"plot-{THEME}.html\", path=\".\")\n"}