{"spec_id":"venn-labeled-items","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nvenn-labeled-items: Chartgeist-Style Venn Diagram with Labeled Items\nLibrary: bokeh 3.9.1 | Python 3.13.14\nQuality: 90/100 | Updated: 2026-06-25\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.core.property.vectorization import value as bk_value\nfrom bokeh.io import output_file, save\nfrom bokeh.models import ColumnDataSource, HoverTool, 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\"\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 palette — positions 1-3 for the three circles\nCOLOR_A = \"#009E73\"  # Overhyped — brand green (Imprint position 1)\nCOLOR_B = \"#C475FD\"  # Actually Useful — lavender (Imprint position 2)\nCOLOR_C = \"#4467A3\"  # Secretly Loved — blue (Imprint position 3)\n\n# Geometry — equilateral triangle of centers (apex pointing down)\nr = 1.0\ns = 1.1\nh_low = s * np.sqrt(3) / 6\ncenter_a = (-s / 2, h_low)\ncenter_b = (s / 2, h_low)\ncenter_c = (0.0, -2 * h_low)\n\n# Data — circle categories with editorial labels positioned outside each circle\ncircles = [\n    {\"name\": \"Overhyped\", \"color\": COLOR_A, \"center\": center_a, \"label_xy\": (-1.65, 1.45), \"align\": \"right\"},\n    {\"name\": \"Actually Useful\", \"color\": COLOR_B, \"center\": center_b, \"label_xy\": (1.35, 1.50), \"align\": \"left\"},\n    {\"name\": \"Secretly Loved\", \"color\": COLOR_C, \"center\": center_c, \"label_xy\": (0.0, -1.90), \"align\": \"center\"},\n]\n\n# Items across all seven zones — ColumnDataSource for idiomatic Bokeh + HoverTool\nitems_data = {\n    \"label\": [\n        # A only — Overhyped\n        \"NFTs\",\n        \"Metaverse\",\n        \"Web3\",\n        # B only — Actually Useful\n        \"Google Maps\",\n        \"Sticky Notes\",\n        # C only — Secretly Loved\n        \"Karaoke\",\n        \"Postcards\",\n        # AB — Overhyped + Actually Useful\n        \"Smartphones\",\n        \"Email\",\n        # AC — Overhyped + Secretly Loved\n        \"Crocs\",\n        \"Pumpkin Spice\",\n        # BC — Actually Useful + Secretly Loved\n        \"Spotify\",\n        \"Dolly Parton\",\n        # ABC — all three\n        \"Sourdough\",\n        \"TikTok\",\n    ],\n    \"x\": [-1.42, -1.50, -1.30, 1.42, 1.40, -0.45, 0.45, 0.00, 0.00, -0.78, -0.55, 0.78, 0.55, 0.00, 0.00],\n    \"y\": [0.88, 0.32, -0.05, 0.88, 0.30, -1.30, -1.30, 0.92, 0.62, -0.18, -0.50, -0.18, -0.50, 0.10, -0.20],\n    \"zone\": [\n        \"Overhyped only\",\n        \"Overhyped only\",\n        \"Overhyped only\",\n        \"Actually Useful only\",\n        \"Actually Useful only\",\n        \"Secretly Loved only\",\n        \"Secretly Loved only\",\n        \"Overhyped + Actually Useful\",\n        \"Overhyped + Actually Useful\",\n        \"Overhyped + Secretly Loved\",\n        \"Overhyped + Secretly Loved\",\n        \"Actually Useful + Secretly Loved\",\n        \"Actually Useful + Secretly Loved\",\n        \"All three zones\",\n        \"All three zones\",\n    ],\n}\nsource = ColumnDataSource(data=items_data)\n\n# Plot — square canvas suits the radial Venn layout\ntitle = \"venn-labeled-items · python · bokeh · anyplot.ai\"\np = figure(\n    width=2400,\n    height=2400,\n    title=title,\n    x_range=(-2.7, 2.7),\n    y_range=(-2.7, 2.7),\n    toolbar_location=None,\n    min_border_bottom=110,\n    min_border_left=110,\n    min_border_top=110,\n    min_border_right=110,\n)\n\n# Invisible scatter points for HoverTool — shows zone membership in the HTML artifact\nitem_renderer = p.scatter(x=\"x\", y=\"y\", source=source, size=30, fill_alpha=0, line_color=None)\nhover = HoverTool(renderers=[item_renderer], tooltips=[(\"Item\", \"@label\"), (\"Zone\", \"@zone\")])\np.add_tools(hover)\n\n# Three semi-transparent circles\nfor circle in circles:\n    cx, cy = circle[\"center\"]\n    p.ellipse(\n        x=cx,\n        y=cy,\n        width=2 * r,\n        height=2 * r,\n        fill_color=circle[\"color\"],\n        fill_alpha=0.22,\n        line_color=circle[\"color\"],\n        line_width=4,\n        line_alpha=0.85,\n    )\n\n# Category names outside each circle in the circle's own color\nfor circle in circles:\n    lx, ly = circle[\"label_xy\"]\n    p.add_layout(\n        Label(\n            x=lx,\n            y=ly,\n            text=circle[\"name\"],\n            text_font=\"serif\",\n            text_font_size=\"52pt\",\n            text_font_style=\"italic\",\n            text_color=circle[\"color\"],\n            text_align=circle[\"align\"],\n            text_baseline=\"middle\",\n        )\n    )\n\n# Item labels — p.text() with ColumnDataSource is the idiomatic Bokeh pattern\n# Set visual props on .glyph to pass literal values (not column refs) in Bokeh 3.x\ntext_renderer = p.text(\n    x=\"x\",\n    y=\"y\",\n    text=\"label\",\n    source=source,\n    text_font_size=\"46pt\",\n    text_color=INK,\n    text_align=\"center\",\n    text_baseline=\"middle\",\n)\ntext_renderer.glyph.text_font = bk_value(\"serif\")\n\n# Editorial titles — witty header above diagram, subtitle below\np.add_layout(\n    Label(\n        x=0.0,\n        y=2.25,\n        text=\"Tech Vibes 2026\",\n        text_font=\"serif\",\n        text_font_size=\"70pt\",\n        text_font_style=\"italic\",\n        text_color=INK,\n        text_align=\"center\",\n        text_baseline=\"middle\",\n    )\n)\np.add_layout(\n    Label(\n        x=0.0,\n        y=-2.45,\n        text=\"A field guide to fifteen things, three feelings, and seven overlapping truths\",\n        text_font=\"serif\",\n        text_font_size=\"28pt\",\n        text_font_style=\"italic\",\n        text_color=INK_MUTED,\n        text_align=\"center\",\n        text_baseline=\"middle\",\n    )\n)\n\n# Chrome — gridless editorial style\np.background_fill_color = PAGE_BG\np.border_fill_color = PAGE_BG\np.outline_line_color = INK_SOFT\n\np.title.text_font = \"serif\"\np.title.text_font_size = \"50pt\"\np.title.text_font_style = \"normal\"\np.title.text_color = INK\np.title.align = \"center\"\n\np.axis.visible = False\np.grid.visible = False\n\n# Save — HTML first, then PNG via headless Selenium (export_png is not used)\noutput_file(f\"plot-{THEME}.html\")\nsave(p)\n\nW, H = 2400, 2400\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)\n# Compensate for headless Chrome viewport offset (window size != inner viewport)\ninner_w = driver.execute_script(\"return window.innerWidth\")\ninner_h = driver.execute_script(\"return window.innerHeight\")\ndriver.set_window_size(W + (W - inner_w), H + (H - inner_h))\ndriver.get(f\"file://{Path(f'plot-{THEME}.html').resolve()}\")\ntime.sleep(3)\ndriver.save_screenshot(f\"plot-{THEME}.png\")\ndriver.quit()\n"}