Pileup¶
The "pileup" transform computes a piled up layout for overlapping segments.
The computed lane can be used to position the segments in a visualization. The
segments must be sorted by their start coordinates before passing them to the
pileup transform.
Parameters¶
as- Type: string
The output field name for the computed lane.
Default:
"lane". description- Type: string
A description of the transform step. Can be used for documentation and agent context.
endRequired- Type: string (field name)
The field representing the end coordinate of the segment (exclusive).
preference- Type: string (field name)
An optional field indicating the preferred lane. Use together with the
preferredOrderproperty. preferredOrder- Type: string[] | number[] | boolean[]
The order of the lane preferences. The first element contains the value that should place the segment on the first lane and so forth. If the preferred lane is occupied, the first available lane is taken.
spacing- Type: number
The spacing between adjacent segments on the same lane in coordinate units.
Default:
1. startRequired- Type: string (field name)
The field representing the start coordinate of the segment (inclusive).
Example¶
Given the following data:
| start | end |
|---|---|
| 0 | 4 |
| 1 | 3 |
| 2 | 6 |
| 4 | 8 |
... and configuration:
{
"type": "pileup",
"start": "start",
"end": "end",
"as": "lane"
}
A new field is added:
| start | end | lane |
|---|---|---|
| 0 | 4 | 0 |
| 1 | 3 | 1 |
| 2 | 6 | 2 |
| 4 | 8 | 1 |
Interactive examples¶
Coverage and pileup¶
The following example demonstrates both "coverage" and "pileup" transforms.
{
"description": "Pileup transform example.",
"data": {
"sequence": { "start": 1, "stop": 100, "as": "start" }
},
"transform": [
{
"type": "formula",
"expr": "datum.start + ceil(random() * 20)",
"as": "end"
}
],
"resolve": {
"scale": { "x": "shared" }
},
"vconcat": [
{
"transform": [
{ "type": "coverage", "start": "start", "end": "end", "as": "coverage" }
],
"mark": "rect",
"encoding": {
"x": { "field": "start", "type": "index" },
"x2": { "field": "end" },
"y": { "field": "coverage", "type": "quantitative" }
}
},
{
"transform": [
{ "type": "pileup", "start": "start", "end": "end", "as": "lane" }
],
"mark": "rect",
"encoding": {
"x": { "field": "start", "type": "index" },
"x2": { "field": "end" },
"y": {
"field": "lane",
"type": "index",
"scale": { "padding": 0.2, "reverse": true, "zoom": false }
}
}
}
]
}
Labels beside segments¶
Labels drawn beside segments also occupy horizontal space. The following
example measures each label in pixels, projects its width into index coordinates,
and uses the resulting occupied endpoint when assigning lanes. The intermediate
"collect" transform caches the measured, start-sorted rows for
scale-dependent replay. Debouncing the formula transform avoids continuously
recomputing the pileup during zoom gestures while allowing new data to use the
current pixelsPerBase value immediately.
{
"description": [
"Pileup with projected label extents",
"Projects measured label widths into coordinate units before assigning lanes."
],
"params": [
{ "name": "labelGap", "value": 5 },
{
"name": "pixelsPerBase",
"expr": "width * abs(scale('x', 1) - scale('x', 0))"
}
],
"data": {
"values": [
{ "start": 20, "end": 105, "label": "kirre" },
{ "start": 20, "end": 105, "label": "kirre" },
{ "start": 20, "end": 105, "label": "kirre" },
{ "start": 110, "end": 225, "label": "N" },
{ "start": 230, "end": 234, "label": "CG18508" },
{ "start": 238, "end": 242, "label": "Fcp3C" },
{ "start": 246, "end": 250, "label": "CG3939" },
{ "start": 430, "end": 434, "label": "CG14265" },
{ "start": 446, "end": 450, "label": "ng2" },
{ "start": 455, "end": 459, "label": "ng3" },
{ "start": 464, "end": 468, "label": "ng1" },
{ "start": 475, "end": 479, "label": "ng4" },
{ "start": 490, "end": 494, "label": "Pig1" },
{ "start": 505, "end": 510, "label": "Sgs4" },
{ "start": 525, "end": 790, "label": "dnc" },
{ "start": 690, "end": 805, "label": "CR44886" },
{ "start": 695, "end": 775, "label": "dnc" },
{ "start": 805, "end": 850, "label": "dm" },
{ "start": 860, "end": 880, "label": "CG10793" }
]
},
"transform": [
{
"type": "measureText",
"field": "label",
"fontSize": 11,
"as": "_labelWidth"
},
{
"type": "collect",
"sort": { "field": "start", "order": "ascending" },
"description": "Caches label measurements before scale-dependent replay."
},
{
"type": "formula",
"expr": "datum.end + (datum._labelWidth + labelGap * 2) / max(pixelsPerBase, 1e-6)",
"as": "_occupiedEnd",
"debounce": 50
},
{
"type": "pileup",
"start": "start",
"end": "_occupiedEnd",
"as": "lane",
"spacing": 0
}
],
"height": { "step": 24 },
"encoding": {
"x": {
"field": "start",
"type": "index",
"scale": { "domain": [0, 1000] }
},
"y": {
"field": "lane",
"type": "ordinal",
"scale": { "padding": 0.3 }
}
},
"layer": [
{
"mark": {
"type": "rect",
"fill": "#2c7fb8",
"stroke": "black",
"strokeWidth": 1
},
"encoding": { "x2": { "field": "end" } }
},
{
"mark": {
"type": "text",
"align": "left",
"baseline": "middle",
"dx": 5
},
"encoding": {
"x": { "field": "end", "type": "index", "band": 0 },
"text": { "field": "label" },
"color": { "value": "black" }
}
}
]
}