{"spec_id":"heatmap-clustered","library":"plotly","language":"python","code":"\"\"\" anyplot.ai\nheatmap-clustered: Clustered Heatmap\nLibrary: plotly 6.7.0 | Python 3.13.13\nQuality: 96/100 | Updated: 2026-05-09\n\"\"\"\n\nimport os\n\nimport numpy as np\nimport plotly.graph_objects as go\nfrom plotly.subplots import make_subplots\nfrom scipy.cluster.hierarchy import dendrogram, linkage\nfrom scipy.spatial.distance import pdist\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\"\nDENDROGRAM_COLOR = \"#306998\" if THEME == \"light\" else \"#6BA3D4\"\nPATHWAY_COLORS = [\"#E8CCCC\", \"#CCE8CC\", \"#CCCCFF\", \"#FFCCCC\"]\n\n# Data: Gene expression analysis (20 genes x 12 samples)\nnp.random.seed(42)\nn_genes = 20\nn_samples = 12\n\n# Gene names representing biological pathways\ngene_labels = [\n    \"CDK1\",\n    \"CCNB1\",\n    \"PLK1\",\n    \"AURKA\",\n    \"BUB1\",  # Cell cycle\n    \"GAPDH\",\n    \"LDHA\",\n    \"PKM\",\n    \"HK2\",\n    \"ENO1\",  # Metabolism\n    \"IL6\",\n    \"TNF\",\n    \"IFNG\",\n    \"IL1B\",\n    \"CXCL8\",  # Immune response\n    \"MYC\",\n    \"TP53\",\n    \"BRCA1\",\n    \"EGFR\",\n    \"VEGFA\",  # Cancer-related\n]\n\n# Gene pathway annotations (0=cell cycle, 1=metabolism, 2=immune, 3=cancer)\ngene_pathway = [0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3]\npathway_names = [\"Cell Cycle\", \"Metabolism\", \"Immune\", \"Cancer\"]\n\n# Sample names (tumor vs normal comparisons)\nsample_labels = [\n    \"T1_A\",\n    \"T1_B\",\n    \"T1_C\",\n    \"T2_A\",\n    \"T2_B\",\n    \"T2_C\",  # Tumor\n    \"N1_A\",\n    \"N1_B\",\n    \"N1_C\",\n    \"N2_A\",\n    \"N2_B\",\n    \"N2_C\",  # Normal\n]\n\n# Sample type annotations (0=tumor, 1=normal)\nsample_type = [0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1]\nsample_type_names = [\"Tumor\", \"Normal\"]\nsample_type_colors = [\"#FFE8E8\", \"#E8E8FF\"]\n\n# Generate expression data with cluster structure\ndata = np.random.randn(n_genes, n_samples) * 0.5\n\n# Cell cycle genes upregulated in tumors\ndata[0:5, 0:6] += 2.0\ndata[0:5, 6:12] -= 1.5\n\n# Metabolism genes moderately upregulated in tumors\ndata[5:10, 0:6] += 1.2\ndata[5:10, 6:12] -= 0.8\n\n# Immune genes show mixed pattern\ndata[10:15, 0:3] += 1.5\ndata[10:15, 3:6] -= 0.5\ndata[10:15, 6:9] += 0.8\ndata[10:15, 9:12] -= 1.2\n\n# Cancer-related genes upregulated in tumors\ndata[15:20, 0:6] += 1.8\ndata[15:20, 6:12] -= 1.0\n\n# Hierarchical clustering\nrow_linkage = linkage(pdist(data, metric=\"euclidean\"), method=\"ward\")\ncol_linkage = linkage(pdist(data.T, metric=\"euclidean\"), method=\"ward\")\n\n# Get dendrogram order\nrow_dendro = dendrogram(row_linkage, no_plot=True)\ncol_dendro = dendrogram(col_linkage, no_plot=True)\nrow_order = row_dendro[\"leaves\"]\ncol_order = col_dendro[\"leaves\"]\n\n# Reorder data and labels\ndata_ordered = data[row_order, :][:, col_order]\nrow_labels_ordered = [gene_labels[i] for i in row_order]\ncol_labels_ordered = [sample_labels[i] for i in col_order]\nrow_pathway_ordered = [gene_pathway[i] for i in row_order]\ncol_type_ordered = [sample_type[i] for i in col_order]\n\n# Create subplots: top dendrogram, left dendrogram, main heatmap, colorbar space\nfig = make_subplots(\n    rows=2,\n    cols=3,\n    column_widths=[0.08, 0.02, 0.90],\n    row_heights=[0.15, 0.85],\n    horizontal_spacing=0.003,\n    vertical_spacing=0.005,\n    specs=[[None, None, {}], [{}, {}, {}]],\n)\n\n# Add top dendrogram (column clustering)\ncol_icoord = np.array(col_dendro[\"icoord\"])\ncol_dcoord = np.array(col_dendro[\"dcoord\"])\nfor i in range(len(col_icoord)):\n    fig.add_trace(\n        go.Scatter(\n            x=col_icoord[i],\n            y=col_dcoord[i],\n            mode=\"lines\",\n            line={\"color\": DENDROGRAM_COLOR, \"width\": 1.5},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=1,\n        col=3,\n    )\n\n# Add left dendrogram (row clustering)\nrow_icoord = np.array(row_dendro[\"icoord\"])\nrow_dcoord = np.array(row_dendro[\"dcoord\"])\nfor i in range(len(row_icoord)):\n    fig.add_trace(\n        go.Scatter(\n            x=row_dcoord[i],\n            y=row_icoord[i],\n            mode=\"lines\",\n            line={\"color\": DENDROGRAM_COLOR, \"width\": 1.5},\n            showlegend=False,\n            hoverinfo=\"skip\",\n        ),\n        row=2,\n        col=1,\n    )\n\n# Add row pathway color bar\nfig.add_trace(\n    go.Heatmap(\n        z=[[row_pathway_ordered]],\n        x=[\"Pathway\"],\n        y=row_labels_ordered,\n        colorscale=[(i / 3, PATHWAY_COLORS[i]) for i in range(4)],\n        showscale=False,\n        hoverinfo=\"skip\",\n    ),\n    row=2,\n    col=2,\n)\n\n# Add heatmap\nfig.add_trace(\n    go.Heatmap(\n        z=data_ordered,\n        x=col_labels_ordered,\n        y=row_labels_ordered,\n        colorscale=\"RdBu_r\",\n        zmid=0,\n        colorbar={\n            \"title\": {\"text\": \"Expression<br>(z-score)\", \"font\": {\"size\": 20, \"color\": INK}},\n            \"tickfont\": {\"size\": 16, \"color\": INK_SOFT},\n            \"len\": 0.75,\n            \"thickness\": 25,\n            \"x\": 1.02,\n            \"bgcolor\": PAGE_BG,\n        },\n        hovertemplate=\"%{y}<br>%{x}<br>Value: %{z:.2f}<extra></extra>\",\n    ),\n    row=2,\n    col=3,\n)\n\n# Update axes for top dendrogram\nfig.update_xaxes(\n    showticklabels=False,\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    range=[0, max(col_dendro[\"icoord\"][-1])],\n    row=1,\n    col=3,\n)\nfig.update_yaxes(\n    showticklabels=False,\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    range=[0, max(col_dendro[\"dcoord\"][-1]) * 1.05],\n    row=1,\n    col=3,\n)\n\n# Update axes for left dendrogram\nfig.update_xaxes(\n    showticklabels=False,\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    range=[max(row_dendro[\"dcoord\"][-1]) * 1.05, 0],\n    row=2,\n    col=1,\n)\nfig.update_yaxes(\n    showticklabels=False,\n    showgrid=False,\n    zeroline=False,\n    showline=False,\n    range=[0, max(row_dendro[\"icoord\"][-1])],\n    row=2,\n    col=1,\n)\n\n# Update axes for row pathway color bar\nfig.update_xaxes(showticklabels=False, showgrid=False, zeroline=False, showline=False, row=2, col=2)\nfig.update_yaxes(showticklabels=False, showgrid=False, zeroline=False, showline=False, row=2, col=2)\n\n# Update heatmap axes with labels\nfig.update_xaxes(\n    title={\"text\": \"Samples\", \"font\": {\"size\": 22, \"color\": INK}},\n    tickfont={\"size\": 16, \"color\": INK_SOFT},\n    tickangle=45,\n    side=\"bottom\",\n    row=2,\n    col=3,\n)\nfig.update_yaxes(\n    title={\"text\": \"Genes\", \"font\": {\"size\": 22, \"color\": INK}},\n    tickfont={\"size\": 16, \"color\": INK_SOFT},\n    row=2,\n    col=3,\n)\n\n# Update layout\nfig.update_layout(\n    title={\n        \"text\": \"heatmap-clustered · plotly · anyplot.ai\",\n        \"font\": {\"size\": 28, \"color\": INK},\n        \"x\": 0.5,\n        \"xanchor\": \"center\",\n    },\n    paper_bgcolor=PAGE_BG,\n    plot_bgcolor=PAGE_BG,\n    font={\"color\": INK, \"family\": \"sans-serif\"},\n    showlegend=False,\n    margin={\"l\": 150, \"r\": 120, \"t\": 120, \"b\": 120},\n    hovermode=\"closest\",\n)\n\n# Save outputs\nfig.write_image(f\"plot-{THEME}.png\", width=1600, height=900, scale=3)\nfig.write_html(f\"plot-{THEME}.html\", include_plotlyjs=\"cdn\")\n"}