{"spec_id":"step-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nstep-basic: Basic Step Plot\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 85/100 | Updated: 2026-07-25\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nfrom bokeh.io import output_file, save\nfrom bokeh.models import BoxAnnotation, ColumnDataSource, HoverTool, Span\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\n\n# Theme - Imprint palette theme-adaptive chrome 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\"\n\n# Data - monthly cumulative sales showing discrete jumps\nmonths = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]\nmonth_names = [\"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\", \"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\"]\ncumulative_sales = [15, 28, 42, 55, 71, 89, 102, 118, 135, 156, 172, 195]\ntarget = 150  # milestone: full-year sales target crossed mid-Q4\n\nsource = ColumnDataSource(data={\"month\": months, \"month_name\": month_names, \"sales\": cumulative_sales})\n\n# Plot\nW, H = 3200, 1800\np = figure(\n    width=W,\n    height=H,\n    title=\"step-basic · python · bokeh · anyplot.ai\",\n    x_axis_label=\"Month\",\n    y_axis_label=\"Cumulative Sales (units)\",\n    tools=\"\",\n    toolbar_location=None,\n    min_border_bottom=160,\n    min_border_left=180,\n    min_border_top=110,\n    min_border_right=50,\n)\n\n# Above-target shading - a distinctive bokeh BoxAnnotation that highlights the\n# region where cumulative sales have cleared the full-year milestone.\nabove_target = BoxAnnotation(bottom=target, fill_color=BRAND, fill_alpha=0.06)\np.add_layout(above_target)\n\n# Reference line - annotates the milestone where cumulative sales crossed the\n# full-year target, giving the step pattern a storytelling focal point.\ntarget_line = Span(location=target, dimension=\"width\", line_color=INK_SOFT, line_dash=\"dashed\", line_width=2)\np.add_layout(target_line)\np.text(\n    x=[1],\n    y=[target],\n    text=[\"Target: 150 units\"],\n    text_font_size=\"20pt\",\n    text_color=INK_SOFT,\n    text_baseline=\"bottom\",\n    text_align=\"left\",\n    y_offset=-8,\n)\n\n# Step line (after/post mode - value holds until the next change occurs)\np.step(x=\"month\", y=\"sales\", source=source, line_width=4, color=BRAND, mode=\"after\")\n\n# Markers at data points to highlight where changes occur\nmarker_glyph = p.scatter(x=\"month\", y=\"sales\", source=source, size=14, color=BRAND, line_color=PAGE_BG, line_width=3)\n\n# Hover - Bokeh's signature interactive feature, surfaces exact month/value pairs\nhover = HoverTool(\n    renderers=[marker_glyph], tooltips=[(\"Month\", \"@month_name\"), (\"Cumulative sales\", \"@sales units\")], mode=\"mouse\"\n)\np.add_tools(hover)\n\n# Style - text sizes for 3200x1800 px canvas\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"bold\"\np.title.text_color = INK\np.xaxis.axis_label_text_font_size = \"42pt\"\np.yaxis.axis_label_text_font_size = \"42pt\"\np.xaxis.axis_label_text_color = INK\np.yaxis.axis_label_text_color = INK\np.xaxis.major_label_text_font_size = \"34pt\"\np.yaxis.major_label_text_font_size = \"34pt\"\np.xaxis.major_label_text_color = INK_SOFT\np.yaxis.major_label_text_color = INK_SOFT\n\n# Chrome - axes and ticks\np.xaxis.axis_line_color = INK_SOFT\np.yaxis.axis_line_color = INK_SOFT\np.xaxis.major_tick_line_color = INK_SOFT\np.yaxis.major_tick_line_color = INK_SOFT\n\n# Grid - subtle solid lines\np.xgrid.grid_line_color = INK\np.xgrid.grid_line_alpha = 0.10\np.ygrid.grid_line_color = INK\np.ygrid.grid_line_alpha = 0.10\n\n# Background - no outline frame, keeps the composition clean\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\n# Save - write the interactive HTML, then screenshot it with headless Chrome\n# (bokeh.io.export_png probes a chromedriver snap shim that fails on this box)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\nopts = Options()\nfor arg in (\n    \"--headless=new\",\n    \"--no-sandbox\",\n    \"--disable-dev-shm-usage\",\n    \"--disable-gpu\",\n    f\"--window-size={W},{H}\",\n    \"--hide-scrollbars\",\n):\n    opts.add_argument(arg)\ndriver = webdriver.Chrome(options=opts)\ndriver.set_window_size(W, H)\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\n# Pin the viewport exactly via CDP - headless Chrome's --window-size sets the\n# OUTER window, which still reserves a phantom title-bar height even headless.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)  # let bokeh's JS render the canvas\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}