{"spec_id":"upset-basic","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nupset-basic: UpSet Plot for Multi-Set Intersection Analysis\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 90/100 | Created: 2026-05-13\n\"\"\"\n\nimport os\nfrom collections import Counter\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\n\n\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\"\nGRID = \"rgba(26,26,23,0.10)\" if THEME == \"light\" else \"rgba(240,239,232,0.10)\"\nBRAND = \"#009E73\"\nDOT_INACTIVE = \"rgba(150,148,140,0.30)\" if THEME == \"light\" else \"rgba(110,108,100,0.45)\"\n\n# Data — gene overlap across 6 genomic experiments\nnp.random.seed(42)\nset_names = [\"Experiment A\", \"Experiment B\", \"Experiment C\", \"Experiment D\", \"Experiment E\", \"Experiment F\"]\nn_sets = len(set_names)\nn_genes = 1200\nprobs = [0.52, 0.44, 0.37, 0.29, 0.23, 0.17]\n\nmembership = np.column_stack([np.random.random(n_genes) < p for p in probs])\nmembership = membership[membership.any(axis=1)]\n\nset_sizes = membership.sum(axis=0)\nset_order = np.argsort(set_sizes)  # ascending: largest set at top (highest y)\nset_names_ord = [set_names[i] for i in set_order]\nset_sizes_ord = set_sizes[set_order]\nmembership_ord = membership[:, set_order]\n\npatterns = Counter(map(tuple, membership_ord.tolist()))\ntop_n = 15\nsorted_patterns = sorted(patterns.items(), key=lambda x: -x[1])[:top_n]\ninter_sizes = [v for _, v in sorted_patterns]\ninter_patterns = [k for k, _ in sorted_patterns]\nn_inter = len(inter_patterns)\n\n# Dot matrix and connecting lines\nactive_x, active_y, inactive_x, inactive_y = [], [], [], []\nline_x, line_y = [], []\nfor col_i, pattern in enumerate(inter_patterns):\n    active_rows = [r for r, m in enumerate(pattern) if m]\n    for r, m in enumerate(pattern):\n        if m:\n            active_x.append(col_i)\n            active_y.append(r)\n        else:\n            inactive_x.append(col_i)\n            inactive_y.append(r)\n    if len(active_rows) > 1:\n        line_x.extend([col_i, col_i, None])\n        line_y.extend([min(active_rows), max(active_rows), None])\n\n# Plot — 2×2 grid: [empty | intersection bars] / [set bars | dot matrix]\nfig = make_subplots(\n    rows=2,\n    cols=2,\n    row_heights=[0.55, 0.45],\n    column_widths=[0.22, 0.78],\n    shared_xaxes=True,  # links (1,2) and (2,2) x-axes for column alignment\n    shared_yaxes=False,\n    vertical_spacing=0.03,\n    horizontal_spacing=0.02,\n)\n\n# [1,2] Intersection size bars\nfig.add_trace(\n    go.Bar(\n        x=list(range(n_inter)),\n        y=inter_sizes,\n        marker_color=BRAND,\n        marker_line_width=0,\n        showlegend=False,\n        hovertemplate=\"Size: %{y} genes<extra></extra>\",\n    ),\n    row=1,\n    col=2,\n)\n\n# [2,1] Set size bars (horizontal, axis reversed → bars point toward matrix)\nfig.add_trace(\n    go.Bar(\n        x=set_sizes_ord,\n        y=list(range(n_sets)),\n        orientation=\"h\",\n        marker_color=BRAND,\n        marker_line_width=0,\n        showlegend=False,\n        customdata=set_names_ord,\n        hovertemplate=\"%{customdata}: %{x} genes<extra></extra>\",\n    ),\n    row=2,\n    col=1,\n)\n\n# [2,2] Inactive dots (non-member sets)\nfig.add_trace(\n    go.Scatter(\n        x=inactive_x,\n        y=inactive_y,\n        mode=\"markers\",\n        marker={\"color\": DOT_INACTIVE, \"size\": 22, \"symbol\": \"circle\"},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    ),\n    row=2,\n    col=2,\n)\n\n# [2,2] Connecting lines (linking active dots in each intersection column)\nfig.add_trace(\n    go.Scatter(x=line_x, y=line_y, mode=\"lines\", line={\"color\": BRAND, \"width\": 6}, showlegend=False, hoverinfo=\"skip\"),\n    row=2,\n    col=2,\n)\n\n# [2,2] Active dots (member sets) — drawn on top of lines\nfig.add_trace(\n    go.Scatter(\n        x=active_x,\n        y=active_y,\n        mode=\"markers\",\n        marker={\"color\": BRAND, \"size\": 22, \"symbol\": \"circle\"},\n        showlegend=False,\n        hoverinfo=\"skip\",\n    ),\n    row=2,\n    col=2,\n)\n\n# Global layout\nfig.update_layout(\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"size\": 16},\n    title={\n        \"text\": \"Gene Overlap Across 6 Experiments · upset-basic · plotly · anyplot.ai\",\n        \"font\": {\"size\": 24, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n        \"y\": 0.98,\n    },\n    showlegend=False,\n    bargap=0.35,\n    margin={\"l\": 20, \"r\": 30, \"t\": 70, \"b\": 20},\n)\n\n# [1,1] Empty panel — suppress all chrome\nfig.update_xaxes(showgrid=False, zeroline=False, showticklabels=False, showline=False, row=1, col=1)\nfig.update_yaxes(showgrid=False, zeroline=False, showticklabels=False, showline=False, row=1, col=1)\n\n# [1,2] Intersection bars — y-axis only\nfig.update_yaxes(\n    title={\"text\": \"Intersection Size\", \"font\": {\"size\": 20, \"color\": INK}},\n    tickfont={\"size\": 16, \"color\": INK_SOFT},\n    gridcolor=GRID,\n    zeroline=False,\n    showline=False,\n    row=1,\n    col=2,\n)\nfig.update_xaxes(showgrid=False, zeroline=False, showticklabels=False, showline=False, row=1, col=2)\n\n# [2,1] Set size bars — reversed x, no tick labels on y\nfig.update_xaxes(\n    autorange=\"reversed\",\n    title={\"text\": \"Set Size\", \"font\": {\"size\": 18, \"color\": INK_SOFT}},\n    tickfont={\"size\": 13, \"color\": INK_MUTED},\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    row=2,\n    col=1,\n)\nfig.update_yaxes(\n    showticklabels=False, showgrid=False, zeroline=False, showline=False, range=[-0.5, n_sets - 0.5], row=2, col=1\n)\n\n# [2,2] Dot matrix — set names on y-axis, clean x-axis\nfig.update_xaxes(\n    showgrid=False, zeroline=False, showticklabels=False, showline=False, range=[-0.5, n_inter - 0.5], row=2, col=2\n)\nfig.update_yaxes(\n    tickmode=\"array\",\n    tickvals=list(range(n_sets)),\n    ticktext=set_names_ord,\n    tickfont={\"size\": 18, \"color\": INK_SOFT},\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    range=[-0.5, n_sets - 0.5],\n    row=2,\n    col=2,\n)\n\n# Save\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}