{"spec_id":"forest-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nforest-basic: Meta-Analysis Forest Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 92/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\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\"\nBRAND = \"#009E73\"\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=(BRAND, BRAND, BRAND, BRAND),\n    title_font_size=28,\n    label_font_size=22,\n    major_label_font_size=18,\n    legend_font_size=16,\n    value_font_size=14,\n    stroke_width=3,\n)\n\nstudies = [\n    (\"Smith 2023\", -0.35, -0.72, 0.02, 10.1),\n    (\"Johnson 2023\", -0.52, -0.95, -0.09, 12.5),\n    (\"Williams 2022\", -0.18, -0.58, 0.22, 9.8),\n    (\"Brown 2022\", -0.67, -1.15, -0.19, 10.3),\n    (\"Davis 2022\", -0.41, -0.78, -0.04, 14.2),\n    (\"Miller 2021\", -0.29, -0.65, 0.07, 11.7),\n    (\"Wilson 2021\", -0.55, -0.98, -0.12, 9.1),\n    (\"Moore 2021\", -0.38, -0.71, -0.05, 13.8),\n    (\"Taylor 2020\", -0.61, -1.08, -0.14, 7.6),\n    (\"Anderson 2020\", -0.44, -0.82, -0.06, 12.8),\n]\n\npooled_effect = -0.43\npooled_ci_lower = -0.58\npooled_ci_upper = -0.28\n\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    title=\"forest-basic · pygal · anyplot.ai\",\n    x_title=\"Mean Difference (95% CI)\",\n    style=custom_style,\n    show_legend=False,\n    stroke=False,\n    show_y_guides=False,\n    show_x_guides=True,\n    range=(-1.3, 0.4),\n    margin=120,\n)\n\nwhisker_data = []\nfor i, (_, _, ci_low, ci_high, _) in enumerate(studies):\n    y_pos = len(studies) - i\n    whisker_data.append((ci_low, y_pos))\n    whisker_data.append((ci_high, y_pos))\n\nchart.add(\"Studies\", whisker_data, stroke=True, show_dots=False, stroke_style={\"width\": 6})\n\npoint_data = [(effect, len(studies) - i) for i, (_, effect, _, _, _) in enumerate(studies)]\nchart.add(\"Effect\", point_data, dots_size=14, stroke=False)\n\nchart.add(\n    \"Null\",\n    [(0, -0.5), (0, len(studies) + 0.5)],\n    stroke=True,\n    show_dots=False,\n    stroke_style={\"width\": 3, \"dasharray\": \"12, 6\"},\n)\n\ndiamond_half_height = 0.4\ndiamond_edges = [\n    (pooled_ci_lower, 0),\n    (pooled_effect, diamond_half_height),\n    (pooled_ci_upper, 0),\n    (pooled_effect, -diamond_half_height),\n]\nchart.add(\"Pooled\", diamond_edges, stroke=True, show_dots=False, stroke_style={\"width\": 4})\n\ny_labels = []\nfor i, (study, _, ci_low, ci_high, _) in enumerate(studies):\n    y_labels.append({\"value\": len(studies) - i, \"label\": f\"{study} [{ci_low:.2f}, {ci_high:.2f}]\"})\ny_labels.append({\"value\": 0, \"label\": f\"Pooled [{pooled_ci_lower:.2f}, {pooled_ci_upper:.2f}]\"})\nchart.y_labels = y_labels\n\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}