{"spec_id":"horizon-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nhorizon-basic: Horizon Chart\nLibrary: bokeh 3.9.2 | Python 3.13.15\nQuality: 91/100 | Updated: 2026-08-18\n\"\"\"\n\nimport os\nimport sys\nimport time\nfrom pathlib import Path\n\n\n# Remove current directory from path FIRST to avoid conflict with local bokeh.py filename\n# This must happen before any imports that might add \".\" back to sys.path\nwhile \"\" in sys.path:\n    sys.path.remove(\"\")\nwhile \".\" in sys.path:\n    sys.path.remove(\".\")\n# Also clear any bokeh module already in sys.modules\nif \"bokeh\" in sys.modules:\n    del sys.modules[\"bokeh\"]\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.layouts import column\nfrom bokeh.models import ColumnDataSource, CrosshairTool, HoverTool, Label, Range1d, Title\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\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\"\nELEVATED_BG = \"#FFFDF6\" if THEME == \"light\" else \"#242420\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_SOFT = \"#4A4A44\" if THEME == \"light\" else \"#B8B7B0\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint categorical palette (canonical order) — see prompts/default-style-guide.md\nIMPRINT_PALETTE = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\", \"#99B314\"]\nIMPRINT_BLUE = IMPRINT_PALETTE[2]  # positive-magnitude pole of the Imprint diverging ramp\nIMPRINT_RED = IMPRINT_PALETTE[4]  # negative-magnitude pole of the Imprint diverging ramp\n\n\ndef _lerp_hex(c0, c1, t):\n    r0, g0, b0 = (int(c0[i : i + 2], 16) for i in (1, 3, 5))\n    r1, g1, b1 = (int(c1[i : i + 2], 16) for i in (1, 3, 5))\n    r, g, b = (int(round(a + (b - a) * t)) for a, b in ((r0, r1), (g0, g1), (b0, b1)))\n    return f\"#{r:02X}{g:02X}{b:02X}\"\n\n\n# Data - Server metrics over 24 hours for 6 servers\nnp.random.seed(42)\n\nn_points = 200\nn_series = 6\nserver_names = [\"Web Server 1\", \"Web Server 2\", \"Database\", \"Cache Server\", \"API Gateway\", \"Load Balancer\"]\n\n# Create time series data with different patterns\nhours = np.linspace(0, 24, n_points)\n\n# Each server has a different pattern\nseries_data = []\nfor i, name in enumerate(server_names):\n    # Base pattern with some periodicity\n    base = np.sin(hours * np.pi / 6 + i * 0.5) * 20\n    # Add some noise and trends\n    noise = np.random.randn(n_points) * 10\n    trend = np.sin(hours * np.pi / 12) * 15 * (1 + i * 0.2)\n    # Add some spikes for realism\n    spikes = np.zeros(n_points)\n    spike_locations = np.random.choice(n_points, size=5, replace=False)\n    spikes[spike_locations] = np.random.randn(5) * 30\n\n    values = base + noise + trend + spikes\n    series_data.append({\"name\": name, \"hours\": hours, \"values\": values})\n\n# Order rows by peak volatility (most eventful server first) — gives the stack a\n# deliberate reading order instead of an arbitrary alphabetical/index list.\nseries_data.sort(key=lambda d: np.max(np.abs(d[\"values\"])), reverse=True)\n\n# Horizon chart parameters\nn_bands = 3  # Number of positive/negative bands\n\n# Canvas — hard rule (prompts/library/bokeh.md): landscape 3200x1800 exactly.\nchart_width = 3200\ntotal_height = 1800\nLEGEND_HEIGHT = 150\nBASE_PANEL_HEIGHT = 240  # panels without an x-axis (all but the last row)\nLAST_PANEL_HEIGHT = 450  # bottom row reserves extra height for the x-axis stack\n# LEGEND_HEIGHT + 5*BASE_PANEL_HEIGHT + LAST_PANEL_HEIGHT == total_height (1800), no dead strip\n\n# Imprint diverging ramp, sampled at 3 stops per pole (near-neutral -> saturated\n# pole) instead of hand-picked hex values — keeps the intensity bands on-brand\n# and perceptually ordered. Midpoint is the theme-adaptive plot background.\n_midpoint = PAGE_BG\npos_colors = [_lerp_hex(_midpoint, IMPRINT_BLUE, t) for t in (0.45, 0.72, 1.0)]\nneg_colors = [_lerp_hex(_midpoint, IMPRINT_RED, t) for t in (0.45, 0.72, 1.0)]\n\n# Shared x-range instance: every panel below zooms/pans in lock-step, a\n# distinctly Bokeh feature (linked ranges) that a static-only library can't offer.\nx_range_shared = Range1d(0, 24)\n\n# Create individual horizon plots\nplots = []\n\nfor idx, data in enumerate(series_data):\n    values = data[\"values\"]\n    x = data[\"hours\"]\n    name = data[\"name\"]\n    is_last = idx == len(series_data) - 1\n\n    # Normalize values to fit in bands\n    max_abs = np.max(np.abs(values))\n    band_size = max_abs / n_bands\n\n    panel_height = LAST_PANEL_HEIGHT if is_last else BASE_PANEL_HEIGHT\n\n    # Create figure for this series\n    p = figure(\n        width=chart_width,\n        height=panel_height,\n        x_range=x_range_shared,\n        y_range=Range1d(0, band_size),\n        tools=\"\",\n        toolbar_location=None,  # hard rule: default toolbar adds ~30-50px to the PNG\n        min_border_left=30,\n        min_border_right=30,\n        min_border_top=6,\n        min_border_bottom=170 if is_last else 6,\n    )\n\n    # Zebra-striped rows (alternating elevated background) give the stack a\n    # subtle rhythm and make it easier to trace a row across its full width.\n    row_bg = ELEVATED_BG if idx % 2 == 1 else PAGE_BG\n    p.background_fill_color = row_bg\n    p.border_fill_color = row_bg\n    p.outline_line_color = None\n\n    # Configure axes\n    if not is_last:\n        p.xaxis.visible = False\n    else:\n        p.xaxis.axis_label = \"Hour of Day (0-24h)\"\n        p.xaxis.axis_label_text_font_size = \"42pt\"\n        p.xaxis.major_label_text_font_size = \"34pt\"\n        p.xaxis.axis_label_text_color = INK\n        p.xaxis.major_label_text_color = INK_SOFT\n        p.xaxis.axis_line_color = INK_SOFT\n        p.xaxis.major_tick_line_color = INK_SOFT\n\n    p.yaxis.visible = False\n    p.grid.visible = False\n\n    # Add series name as label on the left\n    label = Label(\n        x=0.3,\n        y=band_size * 0.5,\n        text=name,\n        text_font_size=\"30pt\",\n        text_font_style=\"bold\",\n        text_align=\"left\",\n        text_baseline=\"middle\",\n        text_color=INK,\n    )\n    p.add_layout(label)\n\n    # Peak-magnitude readout on the right — a quick numeric anchor for the row's\n    # most extreme excursion, so the stack tells a story beyond raw shape.\n    peak_val = values[np.argmax(np.abs(values))]\n    peak_label = Label(\n        x=23.7,\n        y=band_size * 0.5,\n        text=f\"peak {peak_val:+.1f}\",\n        text_font_size=\"18pt\",\n        text_align=\"right\",\n        text_baseline=\"middle\",\n        text_color=INK_MUTED,\n    )\n    p.add_layout(peak_label)\n\n    # Add customized HoverTool showing actual values and crosshair for better interactivity\n    hover = HoverTool(tooltips=[(\"Server\", name), (\"Hour\", \"@x{0.1}\"), (\"Value\", \"@original{0.1}\")], mode=\"vline\")\n    p.add_tools(hover)\n\n    # Add crosshair tool for precision reading\n    crosshair = CrosshairTool(dimensions=\"both\", line_color=INK_SOFT, line_alpha=0.4)\n    p.add_tools(crosshair)\n\n    # Draw horizon bands (folded areas)\n    for band_idx in range(n_bands):\n        band_min = band_idx * band_size\n\n        # Positive values for this band\n        pos_vals = np.clip(values - band_min, 0, band_size)\n        pos_vals = np.where(values > band_min, pos_vals, 0)\n\n        # Negative values for this band (mirrored)\n        neg_vals = np.clip(-values - band_min, 0, band_size)\n        neg_vals = np.where(values < -band_min, neg_vals, 0)\n\n        # Create patches for positive band\n        if np.any(pos_vals > 0):\n            source_pos = ColumnDataSource(data={\"x\": x, \"y\": pos_vals, \"original\": values})\n            p.varea(x=\"x\", y1=0, y2=\"y\", source=source_pos, fill_color=pos_colors[band_idx], fill_alpha=0.9)\n\n        # Create patches for negative band\n        if np.any(neg_vals > 0):\n            source_neg = ColumnDataSource(data={\"x\": x, \"y\": neg_vals, \"original\": values})\n            p.varea(x=\"x\", y1=0, y2=\"y\", source=source_neg, fill_color=neg_colors[band_idx], fill_alpha=0.9)\n\n    plots.append(p)\n\n# Add main title to the first plot with enhanced styling for visual hierarchy\ntitle = Title(\n    text=\"Server Metrics: Hourly Performance Across 24 Hours\", text_font_size=\"50pt\", align=\"center\", text_color=INK\n)\nplots[0].add_layout(title, \"above\")\n\n# Add subtitle with library and source attribution\nsubtitle = Title(\n    text=\"horizon-basic · python · bokeh · anyplot.ai\", text_font_size=\"24pt\", align=\"center\", text_color=INK_SOFT\n)\nplots[0].add_layout(subtitle, \"above\")\n\n# Create legend figure explaining color bands - refined styling with elevated background\nlegend_fig = figure(\n    width=chart_width,\n    height=LEGEND_HEIGHT,\n    x_range=Range1d(0, 100),\n    y_range=Range1d(0, 10),\n    tools=\"\",\n    toolbar_location=None,\n)\nlegend_fig.xaxis.visible = False\nlegend_fig.yaxis.visible = False\nlegend_fig.grid.visible = False\n# Use elevated background for better visual distinction\nlegend_fig.background_fill_color = ELEVATED_BG\nlegend_fig.border_fill_color = ELEVATED_BG\nlegend_fig.outline_line_color = None\n\n# Add legend title with enhanced styling\nlegend_fig.add_layout(\n    Label(\n        x=3,\n        y=9.0,\n        text=\"Color Bands & Intensity Levels\",\n        text_font_size=\"22pt\",\n        text_font_style=\"bold\",\n        text_color=INK,\n        text_baseline=\"top\",\n    )\n)\n\n# Positive bands legend (left side) - enhanced visual styling\nlegend_fig.add_layout(\n    Label(\n        x=20,\n        y=7.6,\n        text=\"Positive Values (above zero):\",\n        text_font_size=\"16pt\",\n        text_font_style=\"bold\",\n        text_color=INK,\n        text_baseline=\"top\",\n    )\n)\nfor i, (color, label_text) in enumerate(zip(pos_colors, [\"Low (+)\", \"Medium (+)\", \"High (+)\"], strict=True)):\n    legend_fig.rect(x=22 + i * 10, y=4.7, width=9, height=5, fill_color=color, line_color=None, fill_alpha=0.95)\n    legend_fig.add_layout(\n        Label(x=22 + i * 10, y=2, text=label_text, text_font_size=\"14pt\", text_align=\"center\", text_color=INK_SOFT)\n    )\n\n# Negative bands legend (right side) - enhanced visual styling\nlegend_fig.add_layout(\n    Label(\n        x=56,\n        y=7.6,\n        text=\"Negative Values (below zero):\",\n        text_font_size=\"16pt\",\n        text_font_style=\"bold\",\n        text_color=INK,\n        text_baseline=\"top\",\n    )\n)\nfor i, (color, label_text) in enumerate(zip(neg_colors, [\"Low (−)\", \"Medium (−)\", \"High (−)\"], strict=True)):\n    legend_fig.rect(x=58 + i * 10, y=4.7, width=9, height=5, fill_color=color, line_color=None, fill_alpha=0.95)\n    legend_fig.add_layout(\n        Label(x=58 + i * 10, y=2, text=label_text, text_font_size=\"14pt\", text_align=\"center\", text_color=INK_SOFT)\n    )\n\n# Combine all plots vertically with legend at top\nlayout = column(legend_fig, *plots)\n\n# Save as HTML (interactive)\noutput_file(f\"plot-{THEME}.html\")\nsave(layout)\n\n# Screenshot with headless Chrome for PNG\nW, H = chart_width, total_height\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# Headless Chrome's --window-size sets the OUTER window, which still reserves a\n# phantom title-bar height even headless — pin the viewport exactly via CDP so\n# the screenshot lands at exactly W x H.\ndriver.execute_cdp_cmd(\n    \"Emulation.setDeviceMetricsOverride\", {\"width\": W, \"height\": H, \"deviceScaleFactor\": 1, \"mobile\": False}\n)\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}