{"spec_id":"point-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\npoint-basic: Point Estimate Plot\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 87/100 | Updated: 2026-05-11\n\"\"\"\n\nimport os\n\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Data: Sensor calibration accuracy with 95% confidence intervals\ncategories = [\"Sensor A\", \"Sensor B\", \"Sensor C\", \"Sensor D\", \"Sensor E\"]\nestimates = [0.3, 0.8, -0.2, 1.1, 0.5]\nlower_bounds = [-0.4, 0.1, -0.9, 0.3, -0.1]\nupper_bounds = [1.0, 1.5, 0.5, 1.9, 1.1]\n\n# Custom style for 4800x2700 canvas with theme support\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_SOFT,\n    colors=(BRAND, \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"),\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    guide_stroke_color=\"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\",\n)\n\n# Create XY chart for point estimates with confidence intervals\nchart = pygal.XY(\n    width=4800,\n    height=2700,\n    style=custom_style,\n    title=\"point-basic · pygal · anyplot.ai\",\n    x_title=\"Calibration Error (μV)\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=3,\n    show_y_guides=False,\n    show_x_guides=True,\n    dots_size=24,\n    stroke=False,\n    margin_left=120,\n    margin_right=120,\n    margin_top=150,\n    margin_bottom=180,\n    xrange=(-1.5, 2.5),\n    range=(0, 6),\n)\n\n# Map categories to y-values (numeric) - reversed for top-to-bottom display\ny_positions = list(range(len(categories), 0, -1))\n\n# Add reference line at zero (null hypothesis)\nref_line = [(0, 0.3), (0, 5.7)]\nchart.add(\"Reference (zero error)\", ref_line, stroke=True, show_dots=False, stroke_width=4)\n\n# Add each CI as a separate series with caps\nfor i, (low, high, y) in enumerate(zip(lower_bounds, upper_bounds, y_positions, strict=True)):\n    # CI line with caps (drawn as separate segments)\n    cap_height = 0.15\n    ci_data = [\n        (low, y - cap_height),  # left cap bottom\n        (low, y + cap_height),  # left cap top\n        (low, y),  # start of CI line\n        (high, y),  # end of CI line\n        (high, y - cap_height),  # right cap bottom\n        (high, y + cap_height),  # right cap top\n    ]\n    if i == 0:\n        chart.add(\"95% CI\", ci_data, stroke=True, show_dots=False, stroke_width=5)\n    else:\n        chart.add(None, ci_data, stroke=True, show_dots=False, stroke_width=5)\n\n# Add point estimates (on top of CI lines)\npoint_data = [(est, y) for est, y in zip(estimates, y_positions, strict=True)]\nchart.add(\"Point Estimate\", point_data, dots_size=28, stroke=False)\n\n# Custom y-axis labels with category names\nchart.y_labels_major = [5, 4, 3, 2, 1]\nchart.y_labels = [\n    {\"value\": 5, \"label\": \"Sensor A\"},\n    {\"value\": 4, \"label\": \"Sensor B\"},\n    {\"value\": 3, \"label\": \"Sensor C\"},\n    {\"value\": 2, \"label\": \"Sensor D\"},\n    {\"value\": 1, \"label\": \"Sensor E\"},\n]\n\n# Save as PNG and HTML with theme suffix\nchart.render_to_png(f\"plot-{THEME}.png\")\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(chart.render())\n"}