{"spec_id":"errorbar-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nerrorbar-basic: Basic Error Bar Plot\nLibrary: pygal 3.1.3 | Python 3.13.14\nQuality: 84/100 | Updated: 2026-06-30\n\"\"\"\n\nimport os\nimport sys\n\n\n# Remove script's own directory from sys.path so 'import pygal' resolves the\n# installed package rather than this file.\nsys.path[:] = [p for p in sys.path if os.path.abspath(p or \".\") != os.path.dirname(os.path.abspath(__file__))]\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens (see prompts/default-style-guide.md)\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_PALETTE = (\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\")\nBRAND = IMPRINT_PALETTE[0]  # #009E73 — Imprint palette position 1, always first series\nANYPLOT_AMBER = \"#DDCC77\"  # warning / caution annotation (outside categorical pool, per pygal.md)\n\n# Data — dose-response study: vehicle control + escalating doses (mg/kg)\ncategories = [\"Vehicle\", \"1 mg/kg\", \"3 mg/kg\", \"10 mg/kg\", \"30 mg/kg\", \"100 mg/kg\"]\nmeans = [25.3, 28.4, 33.1, 38.7, 47.5, 42.0]\n# Asymmetric errors: 10 mg/kg shows notable lower-tail variability\nerr_lower = [2.1, 2.5, 3.0, 6.2, 4.8, 3.4]\nerr_upper = [2.1, 2.5, 3.0, 2.8, 2.2, 3.4]\n\nn = len(categories)\nbaseline = means[0]  # Vehicle/control mean — visual reference for treatment effects\n\n# Colors: series ordering determines color assignment\n# 1: Mean ± error → BRAND; 2: asymmetry callout → ANYPLOT_AMBER; 3: baseline → INK_MUTED; 4+: error bars → BRAND\ncolors_tuple = (BRAND, ANYPLOT_AMBER, INK_MUTED) + (BRAND,) * 62\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=colors_tuple,\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    tooltip_font_size=36,\n    stroke_width=4,\n    opacity=1.0,\n    opacity_hover=0.85,\n)\n\n# Y-axis range with breathing room\ndata_min = min(m - e for m, e in zip(means, err_lower, strict=True))\ndata_max = max(m + e for m, e in zip(means, err_upper, strict=True))\npad = (data_max - data_min) * 0.15\ny_min = max(0.0, data_min - pad)\ny_max = data_max + pad\n\nchart = pygal.XY(\n    style=custom_style,\n    width=3200,\n    height=1800,\n    dots_size=24,\n    title=\"errorbar-basic · python · pygal · anyplot.ai\",\n    x_title=\"Dose\",\n    y_title=\"Response Value (units)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    range=(y_min, y_max),\n    xrange=(0.5, n + 0.5),\n    show_x_guides=False,\n    show_y_guides=True,\n    truncate_label=-1,\n    margin=40,\n    margin_right=80,\n)\n\nchart.x_labels = [{\"label\": categories[i], \"value\": i + 1} for i in range(n)]\nchart.x_labels_major = [{\"label\": categories[i], \"value\": i + 1} for i in range(n)]\nchart.y_labels = [20, 25, 30, 35, 40, 45, 50]\n\n# Mean points — first series (BRAND #009E73) so brand green is assigned as palette position 1\nmean_points = [\n    {\"value\": (i + 1, means[i]), \"label\": f\"{categories[i]}: {means[i]:.1f} (−{err_lower[i]:.1f}/+{err_upper[i]:.1f})\"}\n    for i in range(n)\n]\nchart.add(\"Mean ± error\", mean_points, stroke=False, dots_size=28)\n\n# Asymmetric variability callout — second series (ANYPLOT_AMBER warning color)\n# Amber marker overlaid at 10 mg/kg creates a visual focal point for the chart's key insight:\n# lower-tail variability (6.2) substantially exceeds upper (2.8), indicating non-Gaussian spread\nasym_idx = 3  # 10 mg/kg\nchart.add(\n    \"↓ Asymmetric at 10 mg/kg (−6.2 / +2.8)\",\n    [\n        {\n            \"value\": (asym_idx + 1, means[asym_idx]),\n            \"label\": \"10 mg/kg: lower-tail variability (6.2) ≫ upper (2.8) — floor-effect compression near peak response\",\n        }\n    ],\n    stroke=False,\n    dots_size=38,\n)\n\n# Vehicle baseline reference line — third series (INK_MUTED) for control comparison\nchart.add(\n    \"Vehicle baseline\",\n    [(0.5, baseline), (n + 0.5, baseline)],\n    stroke=True,\n    show_dots=False,\n    stroke_style={\"width\": 3, \"dasharray\": \"18, 14\"},\n)\n\n# Error bars: vertical stem + upper and lower caps per data point\ncap_width = 0.16\nfor i in range(n):\n    x = i + 1\n    low = means[i] - err_lower[i]\n    high = means[i] + err_upper[i]\n    chart.add(None, [(x, low), (x, high)], stroke=True, show_dots=False)\n    chart.add(None, [(x - cap_width, low), (x + cap_width, low)], stroke=True, show_dots=False)\n    chart.add(None, [(x - cap_width, high), (x + cap_width, high)], stroke=True, show_dots=False)\n\n# Save\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}