{"spec_id":"box-grouped","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nbox-grouped: Grouped Box Plot\nLibrary: pygal 3.1.3 | Python 3.13.15\nQuality: 90/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\")\n\n# Data - Clinical trial symptom-improvement scores by treatment arm and patient age group\nnp.random.seed(42)\n\ntreatment_arms = [\"Placebo\", \"Low Dose\", \"High Dose\"]\nage_groups = [\"Under 40\", \"40-59\", \"60+\"]\n\n# Generate improvement-score distributions with realistic dose/age effects\ndata = {}\n# Placebo: modest, noisy improvement regardless of age\ndata[(\"Placebo\", \"Under 40\")] = np.random.normal(18, 9, 60)\ndata[(\"Placebo\", \"40-59\")] = np.random.normal(15, 9, 60)\ndata[(\"Placebo\", \"60+\")] = np.random.normal(12, 8, 60)\n\n# Low Dose: moderate improvement, slightly weaker response in older patients\ndata[(\"Low Dose\", \"Under 40\")] = np.random.normal(42, 11, 60)\ndata[(\"Low Dose\", \"40-59\")] = np.random.normal(37, 11, 60)\ndata[(\"Low Dose\", \"60+\")] = np.random.normal(30, 10, 60)\n\n# High Dose: strongest improvement, response still tapers with age\ndata[(\"High Dose\", \"Under 40\")] = np.random.normal(68, 10, 60)\ndata[(\"High Dose\", \"40-59\")] = np.random.normal(60, 10, 60)\ndata[(\"High Dose\", \"60+\")] = np.random.normal(50, 12, 60)\n\n# Add outliers to show box plot features (non-responders and exceptional responders)\ndata[(\"Placebo\", \"Under 40\")] = np.append(data[(\"Placebo\", \"Under 40\")], [-8, 45])\ndata[(\"Low Dose\", \"60+\")] = np.append(data[(\"Low Dose\", \"60+\")], [-2, 62])\ndata[(\"High Dose\", \"40-59\")] = np.append(data[(\"High Dose\", \"40-59\")], [25, 92])\n\n# Subcategory colors: Imprint palette starting with brand green, canonical order\nsubcategory_colors = IMPRINT[:3]\n\n# Build full color tuple: repeat pattern for each treatment-arm group\nall_colors = subcategory_colors * len(treatment_arms)\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=all_colors,\n    title_font_size=66,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=44,\n    value_font_size=36,\n    opacity=0.97,\n    opacity_hover=1.0,\n    stroke_width=2.5,\n)\n\n# Create box chart with legend showing only 3 age groups\nchart = pygal.Box(\n    width=3200,\n    height=1800,\n    style=custom_style,\n    title=\"box-grouped · python · pygal · anyplot.ai\",\n    x_title=\"Treatment Arm\",\n    y_title=\"Symptom Improvement Score\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=3,\n    legend_box_size=28,\n    truncate_legend=-1,\n    truncate_label=-1,\n    show_y_guides=True,\n    show_x_guides=False,\n    margin=60,\n    box_mode=\"tukey\",\n    x_label_rotation=0,\n    yrange=(-15, 100),\n    range=(-15, 100),\n    y_labels=[-10, 0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100],\n    dots_size=9,\n)\n\n# Track which age groups have been labeled in legend\nlabeled_groups = set()\n\n# Add the 9 boxes grouped by treatment arm\nfor arm in treatment_arms:\n    for group in age_groups:\n        values = data[(arm, group)].tolist()\n        # Only first occurrence of each age group gets a legend entry\n        if group not in labeled_groups:\n            chart.add(group, values)\n            labeled_groups.add(group)\n        else:\n            # Suppress legend entry with None label\n            chart.add(None, values)\n\n# X-axis labels: show treatment arm name in center of each group\nx_labels = [\"\", \"Placebo\", \"\", \"\", \"Low Dose\", \"\", \"\", \"High Dose\", \"\"]\nchart.x_labels = x_labels\n\n# Save outputs\nchart.render_to_file(f\"plot-{THEME}.html\")\nchart.render_to_png(f\"plot-{THEME}.png\")\n"}