Skip to content

Clustered Heatmap with Dendrograms

This example combines a heatmap with clustering trees on both axes and aligned row annotations. It displays expression for 72 genes across 60 cancer cell lines from ten lineages. Shared index scales keep the matrix, dendrogram leaves, labels, and annotation strips aligned while panning and zooming.

{
  "description": [
    "Clustered Heatmap with Dendrograms",
    "Aligns a gene-expression heatmap, two precomputed clustering trees, and cell-line annotations on shared index scales. Pan and zoom to inspect expression patterns; hover for gene and cancer-subtype metadata.",
    "Data source: Broad DepMap (2024), DepMap 24Q4 Public, https://doi.org/10.25452/figshare.plus.27993248.v1. CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/). Processed subset of 60 cell lines and 72 genes; expression standardized per gene and clustered with Ward linkage."
  ],

  "title": {
    "text": "DepMap cancer cell-line expression",
    "subtitle": "60 cell lines / 72 variable genes - Ward clustering - DepMap 24Q4",
    "anchor": "start",
    "fontSize": 22,
    "subtitleFontSize": 12,
    "offset": 16
  },

  "spacing": 8,

  "resolve": {
    "legend": { "color": "collected" }
  },

  "scales": {
    "x": {
      "name": "featureColumns",
      "type": "index",
      "domain": [0, 71],
      "paddingInner": 0.03,
      "paddingOuter": 0.08,
      "gapPositions": [29, 36, 41, 46, 53],
      "gapWidth": 5
    },
    "y": {
      "name": "sampleRows",
      "type": "index",
      "domain": [0, 59],
      "reverse": true,
      "paddingInner": 0.03,
      "paddingOuter": 0.08,
      "gapPositions": [24, 42, 52],
      "gapWidth": 5
    }
  },

  "surround": {
    "top": [
      {
        "name": "columnDendrogram",
        "height": { "grow": 0.2 },
        "data": {
          "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/gene-tree.json"
        },
        "transform": [
          {
            "type": "lookup",
            "from": {
              "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/genes.csv",
              "format": { "parse": { "column": "number" } }
            },
            "key": "feature",
            "fields": "id",
            "values": ["column"],
            "description": "Join stable leaf IDs to the heatmap's precomputed column positions."
          },
          {
            "type": "dendrogram",
            "key": "id",
            "parentKey": "parent",
            "height": "height",
            "leafIndex": "column",
            "description": "Convert the supplied hierarchy into link endpoints while preserving merge heights and leaf order."
          }
        ],
        "mark": {
          "type": "link",
          "linkShape": "orthogonal",
          "orient": "vertical",
          "size": 0.9,
          "minPickingSize": 5,
          "clip": true
        },
        "encoding": {
          "x": {
            "field": "sourceIndex",
            "type": "index",
            "band": 0.5,
            "axis": null
          },
          "x2": { "field": "targetIndex", "band": 0.5 },
          "xOffset": {
            "field": "sourceOffset",
            "type": "quantitative",
            "scale": {
              "domain": [0, 1],
              "range": [0, { "expr": "width * bandstep('x', width)" }]
            }
          },
          "x2Offset": { "field": "targetOffset" },
          "y": {
            "field": "sourceHeight",
            "type": "quantitative",
            "scale": {
              "domain": { "source": "viewport", "mode": "contain" },
              "padding": 0.02,
              "zero": true,
              "nice": false,
              "zoom": false
            },
            "axis": null
          },
          "y2": { "field": "targetHeight" },
          "color": {
            "field": "cluster",
            "type": "nominal",
            "scale": {
              "domain": ["C1", "C2", "C3", "C4", "C5", "C6", "Mixed"],
              "range": [
                "#7c3aed",
                "#0ea5e9",
                "#f59e0b",
                "#e11d48",
                "#10b981",
                "#64748b",
                "#334155"
              ]
            },
            "legend": null
          }
        }
      }
    ],

    "left": [
      {
        "name": "rowDendrogram",

        "width": { "grow": 0.2 },

        "data": {
          "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/cell-line-tree.json"
        },

        "transform": [
          {
            "type": "lookup",
            "from": {
              "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/cell-lines.csv",
              "format": {
                "parse": {
                  "row": "number"
                }
              }
            },
            "key": "sample",
            "fields": "id",
            "values": ["row"],
            "description": "Join stable leaf IDs to the heatmap's precomputed row positions."
          },
          {
            "type": "dendrogram",
            "key": "id",
            "parentKey": "parent",
            "height": "height",
            "leafIndex": "row",
            "description": "Convert the supplied hierarchy into link endpoints while preserving merge heights and leaf order."
          }
        ],
        "mark": {
          "type": "link",
          "linkShape": "orthogonal",
          "orient": "horizontal",
          "size": 0.9,
          "minPickingSize": 5,
          "clip": true
        },
        "encoding": {
          "x": {
            "field": "sourceHeight",
            "type": "quantitative",
            "scale": {
              "domain": { "source": "viewport", "mode": "contain" },
              "padding": 0.02,
              "reverse": true,
              "zero": true,
              "nice": false,
              "zoom": false
            },
            "axis": null
          },
          "x2": { "field": "targetHeight" },
          "y": {
            "field": "sourceIndex",
            "type": "index",
            "band": 0.5,
            "axis": null
          },
          "y2": { "field": "targetIndex", "band": 0.5 },
          "yOffset": {
            "field": "sourceOffset",
            "type": "quantitative",
            "scale": {
              "domain": [0, 1],
              "range": [0, { "expr": "-height * bandstep('y', height)" }]
            }
          },
          "y2Offset": { "field": "targetOffset" },
          "color": {
            "field": "cluster",
            "type": "nominal",
            "scale": {
              "domain": ["R1", "R2", "R3", "R4", "Mixed"],
              "range": ["#3b82f6", "#ef4444", "#22a16d", "#a855f7", "#334155"]
            },
            "legend": null
          }
        }
      }
    ],

    "right": {
      "name": "cellLineAnnotations",
      "spacing": 4,
      "data": {
        "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/cell-lines.csv",
        "format": { "parse": { "row": "number" } }
      },
      "encoding": {
        "y": { "field": "row", "type": "index", "axis": null },
        "tooltip": [
          {
            "field": "cellLine",
            "title": "Cell line"
          },
          {
            "field": "sample",
            "title": "DepMap ModelID"
          },
          {
            "field": "lineage",
            "title": "Cancer lineage"
          },
          {
            "field": "primaryDisease",
            "title": "Primary disease"
          },
          {
            "field": "subtype",
            "title": "Cancer subtype"
          },
          {
            "field": "growthPattern",
            "title": "Growth pattern"
          },
          {
            "field": "sampleOrigin",
            "title": "Primary/metastatic origin"
          }
        ]
      },
      "views": [
        {
          "name": "cancerAnnotation",
          "width": 16,
          "mark": { "type": "rect", "clip": "y" },
          "encoding": {
            "color": {
              "field": "lineage",
              "type": "nominal",
              "scale": {
                "domain": [
                  "Bowel",
                  "Breast",
                  "CNS/Brain",
                  "Kidney",
                  "Lung",
                  "Lymphoid",
                  "Myeloid",
                  "Ovary/Fallopian Tube",
                  "Pancreas",
                  "Skin"
                ],
                "range": [
                  "#bcbd22",
                  "#e377c2",
                  "#8c564b",
                  "#2ca02c",
                  "#1f77b4",
                  "#17becf",
                  "#d62728",
                  "#ff7f0e",
                  "#7f7f7f",
                  "#9467bd"
                ]
              },
              "legend": {
                "title": "Cancer lineage",
                "columns": 3
              }
            }
          },
          "title": {
            "text": "Lineage",
            "style": "metadata-column-title"
          }
        },

        {
          "name": "growthAnnotation",
          "width": 16,
          "mark": { "type": "rect", "clip": "y" },
          "encoding": {
            "color": {
              "field": "growthPattern",
              "type": "nominal",
              "scale": {
                "domain": ["Adherent", "Suspension", "Mixed"],
                "range": ["#33936b", "#8064a2", "#d78239"]
              },
              "legend": {
                "title": "Growth pattern",
                "direction": "vertical",
                "columns": 1
              }
            }
          },
          "title": {
            "text": "Growth pattern",
            "style": "metadata-column-title"
          }
        },

        {
          "name": "originAnnotation",
          "width": 16,
          "mark": { "type": "rect", "clip": "y" },
          "encoding": {
            "color": {
              "field": "sampleOrigin",
              "type": "nominal",
              "scale": {
                "domain": ["Primary", "Metastatic", "Unknown"],
                "range": ["#3274a1", "#d1495b", "#cbd5e1"]
              },
              "legend": {
                "title": "Sample origin",
                "direction": "vertical",
                "columns": 1
              }
            }
          },
          "title": {
            "text": "Sample origin",
            "style": "metadata-column-title"
          }
        },

        {
          "name": "sampleLabels",
          "width": 70,
          "mark": {
            "type": "text",
            "align": "left",
            "baseline": "middle",
            "fitToBand": true,
            "flushY": false,
            "dx": 4,
            "size": 9,
            "color": "#334155",
            "clip": "y",
            "tooltip": null
          },
          "encoding": {
            "x": { "value": 0 },
            "y": {
              "field": "row",
              "type": "index",
              "axis": null
            },
            "text": { "field": "cellLine" }
          },
          "title": {
            "text": "Cell line",
            "orient": "bottom",
            "anchor": "start",
            "align": "left",
            "baseline": "top",
            "fontSize": 8,
            "fontWeight": "normal",
            "color": "#334155",
            "offset": 8
          }
        }
      ],
      "overhang": { "bottom": true }
    }
  },

  "spec": {
    "name": "heatmap",
    "layer": [
      {
        "name": "cells",
        "data": {
          "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/expression-zscores.csv"
        },
        "transform": [
          {
            "type": "regexFold",
            "columnRegex": "^(gene\\d+)$",
            "asKey": "feature",
            "asValue": "value",
            "description": "Unfold gene columns into one expression value per cell line and gene."
          },
          {
            "type": "lookup",
            "from": {
              "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/cell-lines.csv",
              "format": { "parse": { "row": "number" } }
            },
            "key": "sample",
            "values": ["row", "cellLine", "lineage", "subtype"],
            "description": "Attach clustered row positions and cell-line annotations to each matrix cell."
          },
          {
            "type": "lookup",
            "from": {
              "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/genes.csv",
              "format": {
                "parse": {
                  "column": "number",
                  "entrezId": "number",
                  "meanLog2Tpm": "number",
                  "sdLog2Tpm": "number"
                }
              }
            },
            "key": "feature",
            "values": [
              "column",
              "symbol",
              "entrezId",
              "cluster",
              "meanLog2Tpm",
              "sdLog2Tpm"
            ],
            "description": "Attach clustered column positions, gene metadata, and normalization statistics."
          },
          {
            "type": "formula",
            "expr": "datum.value * datum.sdLog2Tpm + datum.meanLog2Tpm",
            "as": "log2Tpm",
            "description": "Reconstruct log2(TPM + 1) expression from the z score and the gene's mean and standard deviation."
          }
        ],
        "params": [
          {
            "name": "hovered",
            "select": {
              "type": "point",
              "on": "mousemove",
              "clear": "mouseleave",
              "toggle": false
            },
            "persist": false
          }
        ],
        "mark": { "type": "rect", "stroke": "#0f172a" },
        "encoding": {
          "x": {
            "field": "column",
            "type": "index",
            "axis": null
          },
          "y": {
            "field": "row",
            "type": "index",
            "axis": null
          },
          "color": {
            "field": "value",
            "type": "quantitative",
            "scale": {
              "domain": [-2.5, 0, 2.5],
              "range": ["#2166ac", "#f7f7f7", "#b2182b"],
              "clamp": true
            },
            "legend": {
              "title": "Gene z score"
            }
          },
          "strokeWidth": {
            "condition": {
              "param": "hovered",
              "empty": false,
              "value": 1.5
            },
            "value": 0
          },
          "order": {
            "condition": {
              "param": "hovered",
              "empty": false,
              "value": 1
            },
            "value": 0
          },
          "tooltip": [
            { "field": "cellLine", "title": "Cell line" },
            { "field": "sample", "title": "DepMap ModelID" },
            { "field": "lineage", "title": "Cancer lineage" },
            { "field": "subtype", "title": "Cancer subtype" },
            { "field": "symbol", "title": "Gene" },
            { "field": "entrezId", "title": "Entrez ID" },
            { "field": "cluster", "title": "Gene cluster" },
            {
              "field": "value",
              "type": "quantitative",
              "title": "Gene z score",
              "format": ".2f"
            },
            {
              "field": "log2Tpm",
              "type": "quantitative",
              "title": "log2(TPM + 1)",
              "format": ".3f"
            }
          ]
        }
      },

      {
        "name": "featureLabels",
        "data": {
          "url": "https://data.genomespy.app/datasets/depmap-cell-line-rna-seq/v1/genes.csv",
          "format": { "parse": { "column": "number" } }
        },
        "mark": {
          "type": "text",
          "align": "right",
          "baseline": "middle",
          "fitToBand": true,
          "flushX": false,
          "angle": -90,
          "dx": -8,
          "size": 8,
          "color": "#334155",
          "clip": "x",
          "tooltip": null
        },
        "encoding": {
          "x": { "field": "column", "type": "index", "axis": null },
          "y": { "value": 0 },
          "text": { "field": "symbol" }
        }
      }
    ]
  },
  "config": {
    "style": {
      "metadata-column-title": {
        "orient": "bottom",
        "anchor": "middle",
        "angle": -90,
        "align": "right",
        "baseline": "middle",
        "fontSize": 8,
        "fontWeight": "normal",
        "color": "#334155",
        "offset": 8
      }
    },
    "legend": {
      "orient": "bottom",
      "direction": "horizontal",
      "gradientLength": 110,
      "titleOrient": "top",
      "offset": 8,
      "labelFontSize": 10,
      "titleFontSize": 11,
      "titleFontWeight": "bold",
      "symbolSize": 49,
      "spacing": 28,
      "rowPadding": 4,
      "columnPadding": 12,
      "layout": {
        "bottom": {
          "anchor": "middle",
          "direction": "horizontal",
          "wrap": true
        }
      }
    }
  }
}

Data: Broad DepMap (2024), DepMap 24Q4 Public, licensed under CC BY 4.0. GenomeSpy hosts a processed demonstration subset of 60 cell lines and 72 protein-coding genes, with gene-wise standardization, computed Ward trees, and extracted cell-line annotations. The reproducible recipe records selection and processing; the data notice accompanies the mirrored extract.

Reading the heatmap

Rows are cell lines and columns are genes. Red indicates expression above a gene's mean across the selected cell lines; blue indicates expression below that mean. The values are z scores of the supplied log2(TPM + 1) expression, so the colors emphasize each gene's variation across cell lines. The color scale saturates at ±2.5, while tooltips retain the full numeric range.

The trees group similar expression profiles. Branch colors and gaps identify four row groups and six gene groups cut from the hierarchies. These groups are derived from expression and can span multiple cancer lineages. The three annotation strips show cancer lineage, growth pattern, and primary/metastatic sample origin. Gray sample-origin cells indicate unknown values.

Zooming and panning fit each tree's height scale to links whose two index anchors are inside the visible range. Local branches can then use the available panel space. Branches extending beyond the fitted height domain are clipped.

Preparing the data

GenomeSpy does not perform clustering. This visualization loads a wide expression matrix, cell-line and gene metadata tables, and a precomputed tree for each axis. The metadata links stable identifiers to labels, annotations, and zero-based row or column positions; tree records contain node IDs, parent IDs, and merge heights.

These files can be prepared with tools such as SciPy or R. Filter and normalize the matrix as appropriate, cluster rows and columns, and choose a leaf order. Export each hierarchy with identifiers that match the matrix and metadata, then assign leaf positions in the chosen order. Optional hierarchy cuts can supply branch groups and gap positions.

The reproducible recipe shows how these inputs were prepared for this example, using gene-wise z scores, Euclidean distance, Ward linkage, and optimal leaf ordering.

How the views stay aligned

The wide expression matrix is unfolded with regexFold. lookup joins the cell-line and gene metadata to the matrix and assigns the same leaf positions to each tree. The dendrogram transform converts the precomputed hierarchy into edges for orthogonal link marks.

The surround operator places the gene tree above the heatmap, the cell-line tree to its left, and the annotations and labels to its right. The matrix and gene tree share an x index scale; the matrix, cell-line tree, labels, and annotation strips share the y scale. Each tree retains an independent quantitative merge-height scale. Its height domain uses viewport autoscaling with mode: "contain". The dendrogram's fractional offsets are converted to pixels with bandstep(), keeping branches connected as the view changes size or zoom level. Explicit gapPositions on the index scales separate the chosen hierarchy groups.

The annotation strips use native bottom view titles with a shared named style. Their reserved bottom overhang leaves room for the rotated titles and gene labels. Collected legends place the expression ramp and categorical keys below the complete composition and wrap on narrow canvases.