{"spec_id":"errorbar-asymmetric","library":"letsplot","language":"python","code":"\"\"\" anyplot.ai\nerrorbar-asymmetric: Asymmetric Error Bars Plot\nLibrary: letsplot 4.9.0 | Python 3.13.13\nQuality: 86/100 | Updated: 2026-05-13\n\"\"\"\n# ruff: noqa: F405\n\nimport os\n\nimport pandas as pd\nfrom lets_plot import *\nfrom lets_plot.export import ggsave\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\"\nBRAND = \"#009E73\"  # Okabe-Ito position 1\n\n# Data - Quarterly sales performance with asymmetric confidence intervals\nquarters = [\"Q1\", \"Q2\", \"Q3\", \"Q4\"]\nsales = [85, 92, 78, 105]\nerror_lower = [8, 5, 12, 7]  # Lower error (downside risk)\nerror_upper = [15, 18, 6, 22]  # Upper error (upside potential)\n\ndf = pd.DataFrame(\n    {\n        \"quarter\": quarters,\n        \"sales\": sales,\n        \"ymin\": [s - el for s, el in zip(sales, error_lower, strict=True)],\n        \"ymax\": [s + eu for s, eu in zip(sales, error_upper, strict=True)],\n    }\n)\n\n# Plot\nanyplot_theme = theme(\n    plot_background=element_rect(fill=PAGE_BG, color=PAGE_BG),\n    panel_background=element_rect(fill=PAGE_BG),\n    panel_grid_major=element_line(color=INK_SOFT, size=0.2),\n    panel_grid_minor=element_blank(),\n    axis_title=element_text(color=INK, size=20),\n    axis_text=element_text(color=INK_SOFT, size=16),\n    axis_line=element_line(color=INK_SOFT, size=0.5),\n    plot_title=element_text(color=INK, size=24, face=\"bold\"),\n    plot_caption=element_text(color=INK_SOFT, size=14),\n    legend_background=element_rect(fill=ELEVATED_BG, color=INK_SOFT),\n    legend_text=element_text(color=INK_SOFT, size=16),\n)\n\nplot = (\n    ggplot(df, aes(x=\"quarter\", y=\"sales\"))\n    + geom_point(\n        size=6,\n        color=BRAND,\n        alpha=0.9,\n        tooltips=layer_tooltips().line(\"Quarter|@quarter\").line(\"Sales|@sales\").line(\"Min|@ymin\").line(\"Max|@ymax\"),\n    )\n    + geom_errorbar(aes(ymin=\"ymin\", ymax=\"ymax\"), width=0.3, size=1.5, color=BRAND)\n    + labs(\n        x=\"Quarter\",\n        y=\"Sales (thousands USD)\",\n        title=\"errorbar-asymmetric · letsplot · anyplot.ai\",\n        caption=\"Error bars represent 10th-90th percentile forecast range\",\n    )\n    + scale_y_continuous(limits=[50, 140])\n    + theme_minimal()\n    + anyplot_theme\n    + ggsize(1600, 900)\n)\n\n# Save\nggsave(plot, filename=f\"plot-{THEME}.png\", path=\".\")\nggsave(plot, filename=f\"plot-{THEME}.html\", path=\".\")\n"}