{"spec_id":"bar-3d-categorical","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nbar-3d-categorical: 3D Bar Chart for Categorical Comparison\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 86/100 | Created: 2026-05-15\n\"\"\"\n\nimport os\nimport sys\n\n\n# Prevent this file (bokeh.py) from shadowing the installed bokeh package\n_this_dir = os.path.dirname(os.path.abspath(__file__))\nif _this_dir in sys.path:\n    sys.path.remove(_this_dir)\n\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, Label\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\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\nIMPRINT = [\"#009E73\", \"#C475FD\", \"#4467A3\", \"#BD8233\"]\n\n# Data: retail sales by product category and quarter (units in thousands)\nnp.random.seed(42)\n\nproducts = [\"Electronics\", \"Apparel\", \"Food & Bev\", \"Books\", \"Sports\"]\nquarters = [\"Q1\", \"Q2\", \"Q3\", \"Q4\"]\n\nn_prod = len(products)\nn_qtr = len(quarters)\n\nsales = np.array(\n    [\n        [118, 92, 105, 134],  # Electronics\n        [72, 88, 78, 65],  # Apparel\n        [60, 68, 74, 58],  # Food & Bev\n        [42, 38, 44, 52],  # Books\n        [85, 76, 92, 80],  # Sports\n    ]\n)\n\nv_max = sales.max()\nH_SCALE = 3.8  # max bar height in projected coordinate units\nDX = 0.55  # oblique x-offset per depth unit\nDY = 0.28  # oblique y-offset per depth unit\n\n\ndef project(x, y, z):\n    return x + y * DX, z + y * DY\n\n\ndef hex_darken(hex_color, f=0.60):\n    h = hex_color.lstrip(\"#\")\n    r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)\n    return f\"#{int(r * f):02x}{int(g * f):02x}{int(b * f):02x}\"\n\n\ndef hex_lighten(hex_color, f=1.35):\n    h = hex_color.lstrip(\"#\")\n    r, g, b = int(h[0:2], 16), int(h[2:4], 16), int(h[4:6], 16)\n    return f\"#{min(255, int(r * f)):02x}{min(255, int(g * f)):02x}{min(255, int(b * f)):02x}\"\n\n\nbar_w = 0.72\ngap = (1.0 - bar_w) / 2.0\n\n# Build base-plane grid patches (subtle)\ngrid_xs, grid_ys, grid_cs = [], [], []\n\nGRID_COLOR = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\n\n# Base plane grid lines (as thin quads)\ngrid_thick = 0.012\nfor j in range(n_qtr + 1):\n    # Horizontal grid line at depth j (from i=0 to i=n_prod)\n    c0 = project(0, j, 0)\n    c1 = project(n_prod, j, 0)\n    c2 = project(n_prod, j + grid_thick, 0)\n    c3 = project(0, j + grid_thick, 0)\n    grid_xs.append([c0[0], c1[0], c2[0], c3[0]])\n    grid_ys.append([c0[1], c1[1], c2[1], c3[1]])\n    grid_cs.append(GRID_COLOR)\n\nfor i in range(n_prod + 1):\n    # Depth grid line at product i\n    c0 = project(i, 0, 0)\n    c1 = project(i, n_qtr, 0)\n    c2 = project(i + grid_thick, n_qtr, 0)\n    c3 = project(i + grid_thick, 0, 0)\n    grid_xs.append([c0[0], c1[0], c2[0], c3[0]])\n    grid_ys.append([c0[1], c1[1], c2[1], c3[1]])\n    grid_cs.append(GRID_COLOR)\n\n# Build bar patches — painter's algorithm: back (large j) to front (small j)\nbar_xs, bar_ys, bar_cs = [], [], []\n\n\ndef add_patch(corners, color):\n    bar_xs.append([project(*c)[0] for c in corners])\n    bar_ys.append([project(*c)[1] for c in corners])\n    bar_cs.append(color)\n\n\nfor j in range(n_qtr - 1, -1, -1):\n    for i in range(n_prod):\n        v = sales[i, j]\n        h = v / v_max * H_SCALE\n        color = IMPRINT[j]\n        x0, x1 = i + gap, i + gap + bar_w\n        y0, y1 = j + gap, j + gap + bar_w\n\n        # Right (side) face — darker shade\n        add_patch([(x1, y0, 0), (x1, y1, 0), (x1, y1, h), (x1, y0, h)], hex_darken(color))\n        # Front face — base color\n        add_patch([(x0, y0, 0), (x1, y0, 0), (x1, y0, h), (x0, y0, h)], color)\n        # Top face — lighter shade\n        add_patch([(x0, y0, h), (x1, y0, h), (x1, y1, h), (x0, y1, h)], hex_lighten(color))\n\n# Figure coordinate ranges\nx_max = n_prod + n_qtr * DX + 3.5\ny_min = -1.2\ny_max = H_SCALE + n_qtr * DY + 1.0\n\np = figure(\n    width=4800,\n    height=2700,\n    title=\"Retail Sales by Product & Quarter  ·  bar-3d-categorical · bokeh · anyplot.ai\",\n    x_range=(-2.0, x_max),\n    y_range=(y_min, y_max),\n    toolbar_location=None,\n)\n\n# Draw base-plane grid first (behind bars)\nsrc_grid = ColumnDataSource({\"xs\": grid_xs, \"ys\": grid_ys, \"cs\": grid_cs})\np.patches(\"xs\", \"ys\", source=src_grid, color=\"cs\", alpha=0.12 if THEME == \"light\" else 0.15, line_color=None)\n\n# Draw bars\nsrc_bars = ColumnDataSource({\"xs\": bar_xs, \"ys\": bar_ys, \"cs\": bar_cs})\np.patches(\"xs\", \"ys\", source=src_bars, color=\"cs\", line_color=PAGE_BG, line_width=1.0, alpha=0.95)\n\n# Value axis vertical line\nax_x0, ax_y0 = project(0, 0, 0)\nax_x1, ax_y1 = project(0, 0, H_SCALE)\np.line([ax_x0, ax_x1], [ax_y0, ax_y1], line_color=INK_SOFT, line_width=2)\n\n# Value axis ticks and labels\nfor v_tick in [30, 60, 90, 120]:\n    h_tick = v_tick / v_max * H_SCALE\n    tx, ty = project(0, 0, h_tick)\n    # Tick mark\n    p.line([tx - 0.12, tx], [ty, ty], line_color=INK_SOFT, line_width=1.5)\n    # Label\n    lbl = Label(\n        x=tx - 0.18,\n        y=ty,\n        text=str(v_tick),\n        text_align=\"right\",\n        text_baseline=\"middle\",\n        text_color=INK_MUTED,\n        text_font_size=\"16pt\",\n    )\n    p.add_layout(lbl)\n\n# Axis title (rotated) — placed manually\nfor dy_step, ch in enumerate(\"Sales (k units)\"):\n    lbl = Label(\n        x=-1.6,\n        y=0.05 + H_SCALE * 0.85 - dy_step * 0.27,\n        text=ch,\n        text_align=\"center\",\n        text_baseline=\"middle\",\n        text_color=INK_SOFT,\n        text_font_size=\"17pt\",\n    )\n    p.add_layout(lbl)\n\n# Product labels (front axis)\nfor i, prod in enumerate(products):\n    px, py = project(i + 0.5, 0, 0)\n    lbl = Label(\n        x=px,\n        y=py - 0.38,\n        text=prod,\n        text_align=\"center\",\n        text_baseline=\"top\",\n        text_color=INK_SOFT,\n        text_font_size=\"18pt\",\n    )\n    p.add_layout(lbl)\n\n# Quarter labels (depth axis, right side, colored per series)\nfor j, qtr in enumerate(quarters):\n    px, py = project(n_prod + 0.25, j + 0.5, 0)\n    lbl = Label(\n        x=px,\n        y=py,\n        text=qtr,\n        text_align=\"left\",\n        text_baseline=\"middle\",\n        text_color=IMPRINT[j],\n        text_font_size=\"22pt\",\n        text_font_style=\"bold\",\n    )\n    p.add_layout(lbl)\n\n# Theme chrome\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = None\n\np.title.text_color = INK\np.title.text_font_size = \"26pt\"\np.title.text_font_style = \"normal\"\np.title.align = \"center\"\n\np.xaxis.visible = False\np.yaxis.visible = False\np.xgrid.visible = False\np.ygrid.visible = False\n\n# Save HTML\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\n# Screenshot with headless Chrome via Selenium\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)\n\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"}