Dendrogram¶
The "dendrogram" transform turns a precomputed clustering tree into
parent–child edges for a link mark. It preserves merge heights
and computes node positions along a discrete index scale.
{
"description": [
"Unbalanced dendrogram",
"A precomputed tree rendered with one orthogonal link mark. Pan and zoom vertically to explore the discrete leaf positions."
],
"data": {
"values": [
{ "id": "A", "parent": "AB", "height": 0, "row": 0 },
{ "id": "B", "parent": "AB", "height": 0, "row": 1 },
{ "id": "C", "parent": "ABC", "height": 0, "row": 2 },
{ "id": "D", "parent": "root", "height": 0, "row": 3 },
{ "id": "AB", "parent": "ABC", "height": 0.8 },
{ "id": "ABC", "parent": "root", "height": 1.6 },
{ "id": "root", "parent": null, "height": 3 }
]
},
"transform": [
{
"type": "dendrogram",
"key": "id",
"parentKey": "parent",
"height": "height",
"leafIndex": "row"
}
],
"scales": {
"y": {
"type": "index",
"domain": [0, 3],
"paddingInner": 0.08,
"paddingOuter": 0.15
}
},
"mark": {
"type": "link",
"linkShape": "orthogonal",
"orient": "horizontal",
"clip": "y",
"size": 1.5
},
"encoding": {
"x": {
"field": "sourceHeight",
"type": "quantitative",
"scale": { "domain": [0, 3], "reverse": true, "zoom": false },
"axis": { "title": "Merge height" }
},
"x2": { "field": "targetHeight" },
"y": {
"field": "sourceIndex",
"type": "index",
"band": 0.5,
"axis": { "orient": "right", "title": "Leaf index" }
},
"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": { "value": "#34495e" }
}
}
The example uses supplied merge heights and a single link mark. The offset scale converts each derived fraction to part of the current row step. Clustering and leaf ordering happen before the specification is rendered.
Input tree¶
Each input row describes a node. The root's parent is null; every other parent
identifies another node. Leaves have explicit integer positions along the index
scale. Internal nodes need no row position.
[
{ "id": "A", "parent": "AB", "height": 0, "row": 0 },
{ "id": "B", "parent": "AB", "height": 0, "row": 1 },
{ "id": "C", "parent": "root", "height": 0, "row": 2 },
{ "id": "AB", "parent": "root", "height": 1 },
{ "id": "root", "parent": null, "height": 3 }
]
{
"type": "dendrogram",
"key": "id",
"parentKey": "parent",
"height": "height",
"leafIndex": "row"
}
Node IDs may be strings or finite numbers. IDs and leaf indices must be unique. Each subtree's leaves must form a contiguous run in the ordered leaf sequence; the indices themselves may have holes. Input row order is independent of leaf order. Missing parents, cycles, and incompatible leaf orders are rejected. The transform supports binary, unary, and multifurcating trees.
Clustering is performed before supplying the tree. A
SciPy linkage matrix
can be converted by numbering leaves 0…n−1 and merge nodes n+i, assigning
each merge's two children their parent ID, and copying the third matrix column
to the merge height. Give leaves height zero and use a chosen leaf permutation
to assign their row positions. In an
R hclust object,
negative merge references identify leaves and positive references identify
earlier merges; copy height and use order to assign zero-based row positions.
Keep labels as separate metadata, so node identity remains independent of order.
Output and placement¶
One output row represents each non-root node's incoming edge. Child metadata,
such as labels or colors, is copied to that row. Generated fields overwrite
same-named metadata. The optional as array changes the output field names.
| Fields | Meaning |
|---|---|
sourceId, targetId |
Parent and child node IDs |
sourceHeight, targetHeight |
Their supplied merge heights |
sourceIndex, targetIndex |
Integer row anchors |
sourceOffset, targetOffset |
Relative displacements from the row anchors |
An internal node is placed midway between its extreme direct children. For an unbalanced tree, positions may include quarters or eighths of a row. The transform separates each position into an integer anchor and a fraction, so the index scale continues to receive integers.
For a horizontal dendrogram, encode heights on x/x2, anchors on y/y2
with band: 0.5, and fractions on yOffset/y2Offset. Give the primary offset
scale an explicit domain [0, 1] and range
[0, { "expr": "-height * bandstep('y', height)" }]. Both offset channels then use
the full padded row step. See offset channels
and the bandstep() helper.
Zoom, pan, and resize update scale mappings without recomputing the tree. Grouping gaps can move parents away from their children's visual midpoints; branches remain connected and leaves remain aligned. With multifurcations, separate edges may overlap on a shared trunk, so transparency and picking follow ordinary paint order. Point selections and tooltips identify the child edge; interval selections retain the link mark's endpoint semantics. Heatmap selections continue to use integer rows.
A facet contains one complete tree and can span several physical files. Outside facets, each input file contains one tree. Empty input or a single leaf produces no edges. Heights are preserved, including linkage inversions. Very small derived fractions near large row indices retain JavaScript's floating-point precision limits.
Parameters¶
as-
Type: array
Output field names for source/target IDs, heights, integer anchors, and fractional offsets. Generated values overwrite copied child metadata. Names must be distinct and cannot use the reserved internal __uniqueId.
Default value: ["sourceId", "targetId", "sourceHeight", "targetHeight", "sourceIndex", "targetIndex", "sourceOffset", "targetOffset"]
description-
Type: string
A description of the transform step. Can be used for documentation and agent context.
heightRequired-
Type: string (field name)
Finite, nonnegative merge height, including leaf heights.
keyRequired-
Type: string (field name)
Unique string or finite numeric node identifier.
leafIndexRequired-
Type: string (field name)
Integer row position for each leaf, matching the heatmap order. Indices must be distinct and in [0, 2^44); each subtree's leaves must be contiguous in the ordered leaf sequence. Internal nodes need no value in this field.
parentKeyRequired-
Type: string (field name)
Parent node identifier. Exactly one root must contain null.