{"spec_id":"subplot-grid","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nsubplot-grid: Subplot Grid Layout\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 90/100 | Updated: 2026-05-13\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom bokeh.io import output_file, save\nfrom bokeh.layouts import gridplot\nfrom bokeh.models import ColumnDataSource, 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 \"Background\" + \"Theme-adaptive Chrome\")\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\"\n\n# Okabe-Ito palette (first series is always #009E73)\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\", \"#AE3030\", \"#2ABCCD\", \"#954477\"]\n\n# Data - Financial dashboard with multiple metrics\nnp.random.seed(42)\n\n# Time series data for price and volume (trading days)\nn_days = 60\ndates = pd.date_range(\"2024-01-01\", periods=n_days, freq=\"B\")\ndate_strings = [d.strftime(\"%b %d\") for d in dates]\n\n# Price data (cumulative returns creating realistic price movement)\nreturns = np.random.normal(0.001, 0.02, n_days)\nprice = 100 * np.cumprod(1 + returns)\n\n# Volume data (with some correlation to price movement)\nbase_volume = np.random.uniform(0.8, 1.2, n_days) * 1_000_000\nvolume = base_volume * (1 + np.abs(returns) * 10)\n\n# Scatter data for risk vs return\nn_assets = 40\nasset_returns = np.random.normal(8, 4, n_assets)\nasset_risk = np.abs(asset_returns) * 0.3 + np.random.uniform(2, 8, n_assets)\n\n# Histogram data - daily returns distribution\ndaily_returns = np.random.normal(0.1, 2.5, 200)\n\n# ========== SUBPLOT 1: Price Line Chart (top-left) ==========\nsource_price = ColumnDataSource(data={\"x\": list(range(n_days)), \"y\": price, \"date\": date_strings})\n\np1 = figure(\n    width=2400,\n    height=1350,\n    title=\"Stock Price Over Time\",\n    x_axis_label=\"Trading Day\",\n    y_axis_label=\"Price ($)\",\n    tools=\"\",\n    toolbar_location=None,\n)\np1.line(\"x\", \"y\", source=source_price, line_width=4, color=IMPRINT[0], alpha=0.9)\np1.scatter(\"x\", \"y\", source=source_price, size=12, color=IMPRINT[0], alpha=0.6)\n\n# Styling for p1\np1.background_fill_color = PAGE_BG\np1.border_fill_color = PAGE_BG\np1.outline_line_color = INK_SOFT\np1.title.text_font_size = \"28pt\"\np1.title.text_color = INK\np1.xaxis.axis_label_text_font_size = \"22pt\"\np1.yaxis.axis_label_text_font_size = \"22pt\"\np1.xaxis.axis_label_text_color = INK\np1.yaxis.axis_label_text_color = INK\np1.xaxis.major_label_text_font_size = \"18pt\"\np1.yaxis.major_label_text_font_size = \"18pt\"\np1.xaxis.major_label_text_color = INK_SOFT\np1.yaxis.major_label_text_color = INK_SOFT\np1.xaxis.axis_line_color = INK_SOFT\np1.yaxis.axis_line_color = INK_SOFT\np1.xaxis.major_tick_line_color = INK_SOFT\np1.yaxis.major_tick_line_color = INK_SOFT\np1.xaxis.major_label_orientation = 0.5\np1.grid.grid_line_color = INK\np1.grid.grid_line_alpha = 0.10\n\n# ========== SUBPLOT 2: Volume Bar Chart (top-right) ==========\nsource_volume = ColumnDataSource(data={\"x\": list(range(n_days)), \"y\": volume / 1_000_000, \"date\": date_strings})\n\np2 = figure(\n    width=2400,\n    height=1350,\n    title=\"Daily Trading Volume\",\n    x_axis_label=\"Trading Day\",\n    y_axis_label=\"Volume (Millions)\",\n    tools=\"\",\n    toolbar_location=None,\n)\np2.vbar(x=\"x\", top=\"y\", source=source_volume, width=0.7, color=IMPRINT[1], alpha=0.8)\n\n# Styling for p2\np2.background_fill_color = PAGE_BG\np2.border_fill_color = PAGE_BG\np2.outline_line_color = INK_SOFT\np2.title.text_font_size = \"28pt\"\np2.title.text_color = INK\np2.xaxis.axis_label_text_font_size = \"22pt\"\np2.yaxis.axis_label_text_font_size = \"22pt\"\np2.xaxis.axis_label_text_color = INK\np2.yaxis.axis_label_text_color = INK\np2.xaxis.major_label_text_font_size = \"18pt\"\np2.yaxis.major_label_text_font_size = \"18pt\"\np2.xaxis.major_label_text_color = INK_SOFT\np2.yaxis.major_label_text_color = INK_SOFT\np2.xaxis.axis_line_color = INK_SOFT\np2.yaxis.axis_line_color = INK_SOFT\np2.xaxis.major_tick_line_color = INK_SOFT\np2.yaxis.major_tick_line_color = INK_SOFT\np2.xaxis.major_label_orientation = 0.5\np2.grid.grid_line_color = INK\np2.grid.grid_line_alpha = 0.10\n\n# ========== SUBPLOT 3: Risk vs Return Scatter (bottom-left) ==========\n# Color by performance using Okabe-Ito palette\ncolors = [IMPRINT[0] if r > 8 else (IMPRINT[2] if r < 5 else IMPRINT[1]) for r in asset_returns]\n\nsource_scatter = ColumnDataSource(data={\"x\": asset_risk, \"y\": asset_returns, \"color\": colors})\n\np3 = figure(\n    width=2400,\n    height=1350,\n    title=\"Risk vs Return Analysis\",\n    x_axis_label=\"Risk (Volatility %)\",\n    y_axis_label=\"Annual Return (%)\",\n    tools=\"\",\n    toolbar_location=None,\n)\np3.scatter(\"x\", \"y\", source=source_scatter, size=18, color=\"color\", alpha=0.7)\n\n# Styling for p3\np3.background_fill_color = PAGE_BG\np3.border_fill_color = PAGE_BG\np3.outline_line_color = INK_SOFT\np3.title.text_font_size = \"28pt\"\np3.title.text_color = INK\np3.xaxis.axis_label_text_font_size = \"22pt\"\np3.yaxis.axis_label_text_font_size = \"22pt\"\np3.xaxis.axis_label_text_color = INK\np3.yaxis.axis_label_text_color = INK\np3.xaxis.major_label_text_font_size = \"18pt\"\np3.yaxis.major_label_text_font_size = \"18pt\"\np3.xaxis.major_label_text_color = INK_SOFT\np3.yaxis.major_label_text_color = INK_SOFT\np3.xaxis.axis_line_color = INK_SOFT\np3.yaxis.axis_line_color = INK_SOFT\np3.xaxis.major_tick_line_color = INK_SOFT\np3.yaxis.major_tick_line_color = INK_SOFT\np3.grid.grid_line_color = INK\np3.grid.grid_line_alpha = 0.10\n\n# ========== SUBPLOT 4: Returns Distribution Histogram (bottom-right) ==========\n# Create histogram bins\nhist, edges = np.histogram(daily_returns, bins=25)\n\nsource_hist = ColumnDataSource(data={\"top\": hist, \"left\": edges[:-1], \"right\": edges[1:]})\n\np4 = figure(\n    width=2400,\n    height=1350,\n    title=\"Daily Returns Distribution\",\n    x_axis_label=\"Daily Return (%)\",\n    y_axis_label=\"Frequency\",\n    tools=\"\",\n    toolbar_location=None,\n)\np4.quad(\n    top=\"top\",\n    bottom=0,\n    left=\"left\",\n    right=\"right\",\n    source=source_hist,\n    fill_color=IMPRINT[2],\n    line_color=PAGE_BG,\n    alpha=0.8,\n    line_width=2,\n)\n\n# Styling for p4\np4.background_fill_color = PAGE_BG\np4.border_fill_color = PAGE_BG\np4.outline_line_color = INK_SOFT\np4.title.text_font_size = \"28pt\"\np4.title.text_color = INK\np4.xaxis.axis_label_text_font_size = \"22pt\"\np4.yaxis.axis_label_text_font_size = \"22pt\"\np4.xaxis.axis_label_text_color = INK\np4.yaxis.axis_label_text_color = INK\np4.xaxis.major_label_text_font_size = \"18pt\"\np4.yaxis.major_label_text_font_size = \"18pt\"\np4.xaxis.major_label_text_color = INK_SOFT\np4.yaxis.major_label_text_color = INK_SOFT\np4.xaxis.axis_line_color = INK_SOFT\np4.yaxis.axis_line_color = INK_SOFT\np4.xaxis.major_tick_line_color = INK_SOFT\np4.yaxis.major_tick_line_color = INK_SOFT\np4.grid.grid_line_color = INK\np4.grid.grid_line_alpha = 0.10\n\n# ========== CREATE GRID LAYOUT ==========\ngrid = gridplot([[p1, p2], [p3, p4]], merge_tools=False, toolbar_location=None)\n\n# Add main title using the first plot's add_layout\nmain_title = Title(text=\"subplot-grid · bokeh · anyplot.ai\", text_font_size=\"32pt\", text_color=INK, align=\"center\")\np1.add_layout(main_title, \"above\")\n\n# Write the interactive HTML (also a required catalog artifact)\noutput_file(f\"plot-{THEME}.html\")\nsave(grid)\n\n# Screenshot it with headless Chrome — Selenium 4 / Selenium Manager\n# auto-resolves a working driver for the system Chrome.\nW, H = 4800, 2700\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()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}