{"spec_id":"upset-basic","library":"bokeh","language":"python","code":"\"\"\" anyplot.ai\nupset-basic: UpSet Plot for Multi-Set Intersection Analysis\nLibrary: bokeh 3.9.0 | Python 3.13.13\nQuality: 86/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nimport time\nfrom pathlib import Path\n\nimport numpy as np\nfrom bokeh.io import output_file, save\nfrom bokeh.layouts import gridplot\nfrom bokeh.models import FixedTicker, Range1d\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\"\nBRAND = \"#009E73\"\n\n# Data: genes from 6 genomic differential expression experiments\nnp.random.seed(42)\nn_genes = 800\nset_names = [\"Exp A\", \"Exp B\", \"Exp C\", \"Exp D\", \"Exp E\", \"Exp F\"]\nn_sets = len(set_names)\n\nmembership_probs = [0.38, 0.33, 0.27, 0.30, 0.24, 0.21]\nmembership = np.zeros((n_genes, n_sets), dtype=bool)\nfor j, prob in enumerate(membership_probs):\n    membership[:, j] = np.random.rand(n_genes) < prob\nfor i in range(n_genes):\n    if not membership[i].any():\n        membership[i, np.random.randint(n_sets)] = True\n\nset_sizes = membership.sum(axis=0)\n\n# Compute intersection signatures and counts\nsig_counts = {}\nfor i in range(n_genes):\n    sig = frozenset(int(j) for j in np.where(membership[i])[0])\n    sig_counts[sig] = sig_counts.get(sig, 0) + 1\n\nintersections = sorted(sig_counts.items(), key=lambda x: -x[1])[:12]\nn_inter = len(intersections)\ninter_counts = [count for _, count in intersections]\nmax_count = max(inter_counts)\n\n# y positions: Exp A at top (y=n_sets-1), Exp F at bottom (y=0)\nset_y = {name: n_sets - 1 - i for i, name in enumerate(set_names)}\n\n# Shared axis ranges for panel alignment\nx_range = Range1d(-0.5, n_inter - 0.5)\ny_range = Range1d(-0.5, n_sets - 0.5)\n\nW_SETS, W_MATRIX = 1100, 3700\nH_BAR, H_MATRIX = 900, 1500\n\n# === DOT MATRIX (bottom right) ===\np_matrix = figure(width=W_MATRIX, height=H_MATRIX, x_range=x_range, y_range=y_range, toolbar_location=None)\np_matrix.background_fill_color = PAGE_BG\np_matrix.border_fill_color = PAGE_BG\np_matrix.outline_line_color = None\n\n# Inactive (hollow) dots\ninactive_x, inactive_y = [], []\nfor col_idx, (sig, _) in enumerate(intersections):\n    for set_idx in range(n_sets):\n        if set_idx not in sig:\n            inactive_x.append(col_idx)\n            inactive_y.append(set_y[set_names[set_idx]])\n\np_matrix.scatter(\n    x=inactive_x,\n    y=inactive_y,\n    size=26,\n    marker=\"circle\",\n    fill_alpha=0,\n    line_color=INK_MUTED,\n    line_alpha=0.40,\n    line_width=2,\n)\n\n# Connecting lines between active sets in each column\nseg_x0, seg_x1, seg_y0, seg_y1 = [], [], [], []\nfor col_idx, (sig, _) in enumerate(intersections):\n    if len(sig) > 1:\n        ys = sorted(set_y[set_names[j]] for j in sig)\n        seg_x0.append(col_idx)\n        seg_x1.append(col_idx)\n        seg_y0.append(ys[0])\n        seg_y1.append(ys[-1])\n\np_matrix.segment(x0=seg_x0, x1=seg_x1, y0=seg_y0, y1=seg_y1, line_color=BRAND, line_width=14, alpha=0.75)\n\n# Active (filled) dots on top\nactive_x, active_y = [], []\nfor col_idx, (sig, _) in enumerate(intersections):\n    for set_idx in sig:\n        active_x.append(col_idx)\n        active_y.append(set_y[set_names[set_idx]])\n\np_matrix.scatter(\n    x=active_x, y=active_y, size=34, marker=\"circle\", fill_color=BRAND, line_color=PAGE_BG, line_width=2, alpha=0.95\n)\n\ny_ticks = list(range(n_sets))\np_matrix.yaxis.ticker = FixedTicker(ticks=y_ticks)\np_matrix.yaxis.major_label_overrides = {set_y[name]: name for name in set_names}\np_matrix.yaxis.major_label_text_font_size = \"20pt\"\np_matrix.yaxis.major_label_text_color = INK_SOFT\np_matrix.yaxis.axis_line_color = INK_SOFT\np_matrix.yaxis.major_tick_line_color = None\np_matrix.yaxis.minor_tick_line_color = None\n\np_matrix.xaxis.ticker = FixedTicker(ticks=list(range(n_inter)))\np_matrix.xaxis.major_label_text_font_size = \"0pt\"\np_matrix.xaxis.axis_line_color = INK_SOFT\np_matrix.xaxis.major_tick_line_color = None\np_matrix.xaxis.minor_tick_line_color = None\n\np_matrix.xgrid.grid_line_color = None\np_matrix.ygrid.ticker = FixedTicker(ticks=y_ticks)\np_matrix.ygrid.grid_line_color = INK\np_matrix.ygrid.grid_line_alpha = 0.08\n\n\n# === INTERSECTION BAR CHART (top right) ===\np_bar = figure(\n    width=W_MATRIX,\n    height=H_BAR,\n    x_range=x_range,\n    y_range=Range1d(0, max_count * 1.18),\n    toolbar_location=None,\n    title=\"upset-basic · bokeh · anyplot.ai\",\n)\np_bar.background_fill_color = PAGE_BG\np_bar.border_fill_color = PAGE_BG\np_bar.outline_line_color = None\n\np_bar.vbar(\n    x=list(range(n_inter)), top=inter_counts, width=0.55, color=BRAND, alpha=0.85, line_color=PAGE_BG, line_width=1.5\n)\n\nfor xi, h in enumerate(inter_counts):\n    p_bar.text(\n        x=[xi],\n        y=[h + max_count * 0.03],\n        text=[str(h)],\n        text_align=\"center\",\n        text_baseline=\"bottom\",\n        text_font_size=\"17pt\",\n        text_color=INK_SOFT,\n    )\n\np_bar.title.text_font_size = \"28pt\"\np_bar.title.text_color = INK\np_bar.title.align = \"center\"\n\np_bar.yaxis.axis_label = \"Intersection Size\"\np_bar.yaxis.axis_label_text_font_size = \"20pt\"\np_bar.yaxis.axis_label_text_color = INK\np_bar.yaxis.major_label_text_font_size = \"16pt\"\np_bar.yaxis.major_label_text_color = INK_SOFT\np_bar.yaxis.axis_line_color = INK_SOFT\np_bar.yaxis.major_tick_line_color = INK_SOFT\np_bar.yaxis.minor_tick_line_color = None\n\np_bar.xaxis.ticker = FixedTicker(ticks=list(range(n_inter)))\np_bar.xaxis.major_label_text_font_size = \"0pt\"\np_bar.xaxis.axis_line_color = None\np_bar.xaxis.major_tick_line_color = None\np_bar.xaxis.minor_tick_line_color = None\n\np_bar.xgrid.grid_line_color = None\np_bar.ygrid.grid_line_color = INK\np_bar.ygrid.grid_line_alpha = 0.10\n\n\n# === SET SIZE BAR CHART (bottom left) ===\nmax_set_size = int(set_sizes.max())\np_sets = figure(\n    width=W_SETS, height=H_MATRIX, x_range=Range1d(max_set_size * 1.20, 0), y_range=y_range, toolbar_location=None\n)\np_sets.background_fill_color = PAGE_BG\np_sets.border_fill_color = PAGE_BG\np_sets.outline_line_color = None\n\np_sets.hbar(\n    y=[set_y[name] for name in set_names],\n    right=list(set_sizes),\n    height=0.5,\n    color=BRAND,\n    alpha=0.80,\n    line_color=PAGE_BG,\n    line_width=1.5,\n)\n\np_sets.xaxis.axis_label = \"Set Size\"\np_sets.xaxis.axis_label_text_font_size = \"18pt\"\np_sets.xaxis.axis_label_text_color = INK\np_sets.xaxis.major_label_text_font_size = \"16pt\"\np_sets.xaxis.major_label_text_color = INK_SOFT\np_sets.xaxis.axis_line_color = INK_SOFT\np_sets.xaxis.major_tick_line_color = INK_SOFT\np_sets.xaxis.minor_tick_line_color = None\n\np_sets.yaxis.visible = False\np_sets.xgrid.grid_line_color = None\np_sets.ygrid.grid_line_color = None\n\n\n# === CORNER SPACER (top left) ===\np_corner = figure(width=W_SETS, height=H_BAR, x_range=Range1d(0, 1), y_range=Range1d(0, 1), toolbar_location=None)\np_corner.background_fill_color = PAGE_BG\np_corner.border_fill_color = PAGE_BG\np_corner.outline_line_color = None\np_corner.xaxis.visible = False\np_corner.yaxis.visible = False\np_corner.xgrid.grid_line_color = None\np_corner.ygrid.grid_line_color = None\np_corner.rect(x=[0.5], y=[0.5], width=[0], height=[0], fill_alpha=0, line_alpha=0)\n\n\n# === Grid Layout ===\ngrid = gridplot([[p_corner, p_bar], [p_sets, p_matrix]], merge_tools=False, toolbar_location=None)\n\noutput_file(f\"plot-{THEME}.html\")\nsave(grid)\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)\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"}