Skip to content

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.

end Required
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 preferredOrder property.

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.

start Required
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" }
      }
    }
  ]
}