Displace 1D¶
The "displace1d" transform separates overlapping items along a positional
axis while keeping them as close as possible to their original positions. It
preserves every input row and writes the signed displacement to a new field.
Input rows must be ordered by ascending scaled position. Use an upstream
collect transform to establish that order and provide the replay buffer needed
for reactive updates.
Parameters¶
as- Type: string
The output field for signed displacement.
Default value:
"displacement" description- Type: string
A description of the transform step. Can be used for documentation and agent context.
extent- Type: [number, number] | ExprRef
Preferred outer bounds for the placed collision intervals, expressed in the original
poscoordinate system. The bounds are multiplied bypositionFactortogether with the item positions. When all items cannot fit, they remain non-overlapping and extend beyond the bounds by the minimum necessary amount. An expression can update the bounds reactively. lengthRequired- Type: number | string (field name) | ExprRef
The full collision length, including any desired spacing, or a field containing that length. The value uses the same units as the scaled positions and output displacement. An expression provides a reactive scalar length shared by all rows.
posRequired- Type: string (field name)
The field containing the original position. Input rows must be ordered by ascending
pos * positionFactor. positionFactor- Type: number | ExprRef
A multiplier applied to
posbefore placement. An expression can convert position units to logical pixels and react to zoom or layout changes. Use an ascendingpossort for a positive factor and a descending sort for a negative factor. Place acollecttransform before this transform to buffer input for expression-driven updates.Default value:
1
Example¶
When using the output with an offset channel, make positionFactor produce
logical pixels and disable the offset scale:
{
"transform": [
{
"type": "collect",
"sort": { "field": "position", "order": "ascending" }
},
{
"type": "displace1d",
"pos": "position",
"length": 18,
"positionFactor": {
"expr": "width * (scale('x', 1) - scale('x', 0))"
},
"extent": [0.5, 1068.5],
"as": "xDisplacement"
}
],
"encoding": {
"x": { "field": "position", "type": "quantitative" },
"xOffset": {
"field": "xDisplacement",
"type": "quantitative",
"scale": null
}
}
}
Pixel-space placement¶
GenomeSpy positional scales use a unit range. In the expression above, the
difference between two scaled values gives the signed unit-range distance for
one position unit. Multiplication by width converts that distance to logical
pixels. Thus, positionFactor converts the positions to the pixel units used by
length and the resulting displacement.
The expression reacts to zoom and layout changes. As zoom spreads the positions
apart, the displacements decrease naturally to zero; items at the same position
remain separated. Setting scale to null on xOffset applies the resulting
pixel offsets directly.
This conversion assumes an affine mapping, such as a linear quantitative, index, or locus scale. Nonlinear mappings are not supported directly.
Bounds¶
Use extent to keep collision intervals inside preferred outer bounds. The
bounds use the original pos coordinate system and are multiplied by
positionFactor together with the item positions. For a one-based index scale
covering protein residues 1 through 1068, [0.5, 1068.5] represents the outer
edges of the first and last residue bands.
extent can also be a reactive expression. For example, the following keeps
the preferred bounds at the edges of the currently visible x domain:
"extent": { "expr": "[invert('x', 0), invert('x', 1)]" }
When all collision intervals fit, the extent acts as a hard bound. If their
combined length exceeds the available extent, items remain non-overlapping and
extend outside it by the minimum necessary amount. Among placements with the
same minimum overflow, the transform minimizes squared displacement. Placement
changes continuously as positionFactor changes; active constraints may
change the rate of movement but do not introduce jumps.
Placement method¶
The transform minimizes the total squared displacement while preserving the incoming item order and preventing overlaps. For ordered input, this is solved in linear time using equal-weight least-squares isotonic regression and the pool-adjacent-violators algorithm (PAVA). See Busing, Monotone Regression: A Simple and Fast O(n) PAVA Implementation.