{"spec_id":"facet-grid","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nfacet-grid: Faceted Grid Plot\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 87/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 column, gridplot\nfrom bokeh.models import ColumnDataSource, Div, HoverTool\nfrom bokeh.plotting import figure\nfrom selenium import webdriver\nfrom selenium.webdriver.chrome.options import Options\n\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\"\n\n# Okabe-Ito palette\nIMPRINT = [\n    \"#009E73\",  # bluish green (brand)\n    \"#C475FD\",  # vermillion\n    \"#4467A3\",  # blue\n]\n\n# Data - Product performance across regions and seasons\nnp.random.seed(42)\n\nregions = [\"North\", \"South\", \"East\"]\nseasons = [\"Spring\", \"Summer\", \"Fall\", \"Winter\"]\n\ndata = []\nfor region in regions:\n    for season in seasons:\n        n_points = 25\n        # Base values vary by region\n        base_x = {\"North\": 20, \"South\": 30, \"East\": 25}[region]\n        base_y = {\"North\": 50, \"South\": 70, \"East\": 60}[region]\n\n        # Seasonal adjustments\n        season_adj = {\"Spring\": 1.0, \"Summer\": 1.3, \"Fall\": 0.9, \"Winter\": 0.7}[season]\n\n        x = np.random.normal(base_x, 5, n_points)\n        y = base_y * season_adj + x * 0.8 + np.random.normal(0, 8, n_points)\n\n        for xi, yi in zip(x, y, strict=True):\n            data.append({\"marketing_spend\": xi, \"sales\": yi, \"region\": region, \"season\": season})\n\ndf = pd.DataFrame(data)\n\n# Map regions to colors\ncolor_map = {\"North\": IMPRINT[0], \"South\": IMPRINT[1], \"East\": IMPRINT[2]}\n\n# Create grid of plots (rows=seasons, cols=regions)\nplots = []\n\nfor season in seasons:\n    row_plots = []\n    for region in regions:\n        subset = df[(df[\"region\"] == region) & (df[\"season\"] == season)]\n        source = ColumnDataSource(\n            data={\"x\": subset[\"marketing_spend\"], \"y\": subset[\"sales\"], \"region\": [region] * len(subset)}\n        )\n\n        # Create figure for each cell\n        p = figure(\n            width=1600, height=620, x_range=(5, 50), y_range=(20, 130), tools=\"\", title=\"\", toolbar_location=None\n        )\n\n        # Add scatter points\n        scatter = p.scatter(\n            x=\"x\", y=\"y\", source=source, size=18, color=color_map[region], alpha=0.7, line_color=PAGE_BG, line_width=1\n        )\n\n        # Add hover tooltip\n        hover = HoverTool(\n            renderers=[scatter], tooltips=[(\"Region\", \"@region\"), (\"Spend\", \"@x{0.0}\"), (\"Sales\", \"@y{0.0}\")]\n        )\n        p.add_tools(hover)\n\n        # Add facet label in top-left corner\n        p.text(\n            x=[8], y=[122], text=[f\"{region} · {season}\"], text_font_size=\"18pt\", text_color=INK, text_font_style=\"bold\"\n        )\n\n        # Style axes - only show labels on edges\n        p.xaxis.axis_label = \"Marketing Spend ($K)\" if season == seasons[-1] else \"\"\n        p.yaxis.axis_label = \"Sales ($K)\" if region == regions[0] else \"\"\n        p.xaxis.axis_label_text_font_size = \"22pt\"\n        p.yaxis.axis_label_text_font_size = \"22pt\"\n        p.xaxis.major_label_text_font_size = \"18pt\"\n        p.yaxis.major_label_text_font_size = \"18pt\"\n\n        # Theme-adaptive styling\n        p.background_fill_color = PAGE_BG\n        p.border_fill_color = PAGE_BG\n        p.outline_line_color = INK_SOFT\n        p.title.text_color = INK\n        p.xaxis.axis_label_text_color = INK\n        p.yaxis.axis_label_text_color = INK\n        p.xaxis.major_label_text_color = INK_SOFT\n        p.yaxis.major_label_text_color = INK_SOFT\n        p.xaxis.axis_line_color = INK_SOFT\n        p.yaxis.axis_line_color = INK_SOFT\n\n        # Grid styling\n        p.xgrid.grid_line_color = INK\n        p.ygrid.grid_line_color = INK\n        p.xgrid.grid_line_alpha = 0.15\n        p.ygrid.grid_line_alpha = 0.15\n\n        row_plots.append(p)\n    plots.append(row_plots)\n\n# Create gridplot layout\ngrid = gridplot(plots, toolbar_location=None, merge_tools=False)\n\n# Add overall title using Div\ntitle_div = Div(\n    text=f\"<h1 style='text-align: center; color: {INK}; font-size: 32pt; margin: 20px 0;'>\"\n    f\"facet-grid · bokeh · anyplot.ai</h1>\",\n    width=4800,\n    height=120,\n)\n\n# Create legend using Div for region-color mapping\nlegend_html = (\n    f\"<div style='text-align: center; font-size: 18pt; padding: 20px 0; color: {INK};'>\"\n    f\"<span style='font-weight: bold; margin-right: 30px;'>Region:</span>\"\n)\nfor region in regions:\n    color = color_map[region]\n    legend_html += (\n        f\"<span style='margin-right: 40px;'>\"\n        f\"<span style='display: inline-block; width: 20px; height: 20px; \"\n        f\"background-color: {color}; border: 1px solid {INK_SOFT}; border-radius: 50%; \"\n        f\"vertical-align: middle; margin-right: 8px;'></span>\"\n        f\"<span style='color: {INK_SOFT};'>{region}</span></span>\"\n    )\nlegend_html += \"</div>\"\n\nlegend_div = Div(text=legend_html, width=4800, height=100)\n\n# Combine title, grid, and legend using column layout\nfinal_layout = column(title_div, grid, legend_div)\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(final_layout)\n\n# Screenshot with headless 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"}