{"spec_id":"errorbar-asymmetric","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nerrorbar-asymmetric: Asymmetric Error Bars Plot\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 71/100 | Updated: 2026-05-13\n\"\"\"\n\nimport numpy as np\nfrom bokeh.io import export_png\nfrom bokeh.models import ColumnDataSource, Whisker\nfrom bokeh.plotting import figure\n\n\n# Data - Quarterly revenue forecasts with asymmetric uncertainty (10th-90th percentile)\nnp.random.seed(42)\nquarters = [\"Q1 2024\", \"Q2 2024\", \"Q3 2024\", \"Q4 2024\", \"Q1 2025\", \"Q2 2025\"]\n\n# Central estimates (median forecast in millions)\ny = np.array([12.5, 14.2, 13.8, 16.5, 15.0, 17.8])\n\n# Asymmetric errors - downside risk typically larger than upside potential\nerror_lower = np.array([2.5, 3.0, 2.2, 4.0, 2.8, 3.5])  # Larger downside\nerror_upper = np.array([1.5, 2.0, 1.8, 2.5, 2.0, 2.8])  # Smaller upside\n\n# Calculate upper and lower bounds\nupper = y + error_upper\nlower = y - error_lower\n\n# Create ColumnDataSource\nsource = ColumnDataSource(data={\"y\": y, \"upper\": upper, \"lower\": lower, \"quarters\": quarters})\n\n# Create figure with categorical x-axis (no toolbar for static export)\np = figure(\n    width=4800,\n    height=2700,\n    x_range=quarters,\n    title=\"errorbar-asymmetric · bokeh · pyplots.ai\",\n    x_axis_label=\"Quarter\",\n    y_axis_label=\"Revenue Forecast ($ millions)\",\n    toolbar_location=None,\n)\n\n# Add whiskers for error bars (asymmetric)\nwhisker = Whisker(\n    source=source,\n    base=\"quarters\",\n    upper=\"upper\",\n    lower=\"lower\",\n    line_color=\"#306998\",\n    line_width=6,\n    upper_head=None,\n    lower_head=None,\n)\np.add_layout(whisker)\n\n# Add horizontal caps manually\ncap_width = 0.2\nfor i, _q in enumerate(quarters):\n    # Upper cap\n    p.line(x=[i - cap_width, i + cap_width], y=[upper[i], upper[i]], line_color=\"#306998\", line_width=6)\n    # Lower cap\n    p.line(x=[i - cap_width, i + cap_width], y=[lower[i], lower[i]], line_color=\"#306998\", line_width=6)\n\n# Plot central points\np.scatter(\n    x=\"quarters\",\n    y=\"y\",\n    source=source,\n    size=35,\n    color=\"#FFD43B\",\n    line_color=\"#306998\",\n    line_width=4,\n    legend_label=\"Median forecast (10th-90th percentile)\",\n)\n\n# Title styling\np.title.text_font_size = \"42pt\"\np.title.text_font_style = \"bold\"\n\n# Axis label styling\np.xaxis.axis_label_text_font_size = \"32pt\"\np.yaxis.axis_label_text_font_size = \"32pt\"\np.xaxis.major_label_text_font_size = \"26pt\"\np.yaxis.major_label_text_font_size = \"26pt\"\np.xaxis.major_label_orientation = 0\n\n# Axis line styling\np.xaxis.axis_line_width = 2\np.yaxis.axis_line_width = 2\np.xaxis.major_tick_line_width = 2\np.yaxis.major_tick_line_width = 2\n\n# Grid styling\np.grid.grid_line_alpha = 0.3\np.grid.grid_line_dash = \"dashed\"\n\n# Legend styling\np.legend.label_text_font_size = \"26pt\"\np.legend.location = \"top_left\"\np.legend.background_fill_alpha = 0.8\np.legend.border_line_width = 2\np.legend.padding = 15\np.legend.margin = 20\n\n# Background\np.background_fill_color = \"white\"\np.border_fill_color = \"white\"\n\n# Add some padding\np.min_border_left = 100\np.min_border_right = 50\np.min_border_top = 80\np.min_border_bottom = 100\n\n# Save output\nexport_png(p, filename=\"plot.png\")\n"}