Some visualizations animate, pulse, and flash. If you're sensitive to motion or flashing, turn on Reduce motion in your system settings.

poopdeck.gl
deck.gl Layers

FlowCorridorLayer

The FlowCorridorLayer renders a static-geometry overview whose per-vertex color is a time series. It is the pre-aggregated OD / flow renderer behind the nyc-taxi-flows demo: a corridor network (e.g. street segments carrying taxi volume) is stored once, and every corridor "pulses" as the playhead moves — width-constant lines whose color tracks the active flow value.

It extends AnimatedTripsLayer, keeping all of its tile caching and sublayer plumbing, and overrides just the gradient seams so the per-vertex scalar comes from a time bucket instead of a single static channel.

How it works#

A flow-corridor tile stores its geometry once and carries a per-vertex × per-time-bucket value matrix (BinaryFeatures.vertexValueMatrix, vertexValueBuckets columns). The geometry never re-uploads as time advances — only the per-vertex color changes:

  • For the current playhead the layer finds the continuous bucket position, selects the two adjacent bucket columns, and linearly blends them into a per-vertex scalar (or, with persistenceMs, takes a trailing-window max). The base class maps that scalar through the gradient ramp into per-vertex RGBA.
  • The cross-fade is quantized to a sub-step grid (STEP = 0.5, i.e. 2 sub-steps per bucket). That quantized sub-step is the prepared tile's dynamicKey, kept separate from the structural styleKey: between sub-steps the cache hits and nothing happens at all, and on a crossing the layer re-expands only the per-vertex colour (and chevron-direction) buffers and swaps those attribute wrappers in place. The tile's data object stays reference-identical, so deck sees no data change and skips both path re-tessellation and the fp64 hi/lo re-upload of the position buffer — only the swapped attributes are invalidated, via updateTriggers. AnimatedTripsLayer additionally memoizes the purely geometry-derived arrays (synthesizeVertexTimes, the static-width expansion) on the tile's binary identity, so they are computed once per tile load rather than once per sub-step. The fine (1-minute) buckets chevronPerTripLight targets are why the step is this coarse: a smaller one would re-expand the two per-vertex signals ~24× per bucket.
  • Corridors are timeless: each feature's [start, end] spans the whole range, so the window-mode time filter never hides the network.

trailLength is pinned to 0#

FlowCorridorLayer overrides AnimatedTripsLayer's trailLength: 180000 default with 0. A corridor is static geometry animated by colour: it runs the time filter in window mode and repurposes the instanceVertexTime slot to carry the chevron direction sign. Any trailLength > 0 switches the shader into trail mode (precedence: cumulative > wake > trail > window), where that slot is read as a relative vertex time — blanking the entire corridor network, silently, once the relative playhead passes it (and, under signedFlow, misreading the direction signs as timestamps). Overriding it warns once; leave it at 0.

Installation#

import { FlowCorridorLayer } from '@poopdeck.gl/layers';

Usage#

const layer = new FlowCorridorLayer({
id: 'taxi-flows',
data: '/data/nyc-taxi-flows/manifest.json',
currentTime,
gradientDomain: [0, 400], // flow-count range mapped onto the ramp
gradientColorRamp: [
[40, 40, 80, 180],
[80, 180, 255, 220],
[255, 220, 120, 255],
],
tripWidth: 2,
});

The active-bucket scalar is colored through the inherited per-vertex gradient props — gradientDomain and gradientColorRamp (the gradientProperty channel is selected automatically from the value matrix). See AnimatedTripsLayer for the full gradient and width prop tables.

Properties#

Inherits all properties from AnimatedTripsLayer (and through it SpatioTemporalLayer). The relevant inherited props are:

PropertyTypeDefaultDescription
gradientDomain[number, number][0, 1]Flow-value range mapped onto the color ramp.
gradientColorRampColor[][]Low→high color stops (piecewise-lerped) for the pulsing color.
tripWidthnumber | string3Corridor line width (constant; widths do not animate here).
widthUnits / widthScale / widthMinPixels / widthMaxPixelsPathLayer width controls (see AnimatedTripsLayer).
trailLengthnumber0Overridden to 0 here, unlike AnimatedTripsLayer's 180000. See above — any non-zero value blanks the network.

FlowCorridorLayer also adds its own props, all optional, that control how the active-bucket scalar is derived and how a paired ChevronFlowExtension reads direction/intensity off it:

PropertyTypeDefaultDescription
signedFlowbooleanfalseRead the value matrix as signed, so the sign carries travel direction.
chevronPerTripLightbooleanfalsePack a rolling aggregate and an instantaneous flash into one buffer.
chevronAggregateWindowMsnumber240000Half-span (ms of data time) of the chevronPerTripLight RGB aggregate.
chevronInstantDomainnumber1.5Trailing-sum value that reads as a full-brightness flash.
chevronInstantDecayMsnumber120000Decay time constant (ms of data time) of the instant flash.
chevronDirectionWindowMsnumber0 (inherits chevronAggregateWindowMs)Half-span of the signedFlow directional-coherence window.
persistenceMsnumber0 (off)TRAILING persistence window (ms of data time) for the default matrix path: each vertex holds the max of the bucket signal over [t − persistenceMs, t] instead of the instantaneous two-bucket blend. See below.

signedFlowabs(value) drives the color (volume) while the sign carries per-bucket travel direction. Set it for tiles built with bixi --streets --per-bucket-direction. When on, chevronDirectionsFor emits a continuous [-1, 1] directional-coherence signal — a rolling Σsigned / Σ abs(value) ratio — instead of the default plain-magnitude blend, so a paired ChevronFlowExtension can morph arrow shape, hue, and march smoothly between forward and reverse.

chevronPerTripLight — switches the gradient source to the two-signal mode ChevronFlowExtension({ perTripLight: true }) expects. The RGB channel becomes a rolling-window aggregate mean of abs(value) (the colour over a granular period), and the colour buffer's alpha byte carries a separate instantaneous per-trip flash — a short trailing decay of the nearest fine bucket. No extra GPU attribute is needed.

persistenceMs — generators bin a trip's whole route into its START bucket, so without persistence a corridor's highlight fades one bucket after departure while the ride (a moving-heads overlay) is still traversing it. Set it to the typical trip DURATION and the highlight rises over one bucket, holds for the window, then fades over one bucket. Time-CHUNKED archives clamp the window at each chunk's first column (a brief reset at chunk boundaries); whole-range corridor archives are unaffected. Only applies on the default matrix path, not under chevronPerTripLight.

chevronAggregateWindowMs defaults to ±4 min; wider windows smooth the ramp colour further. A non-positive chevronInstantDomain falls back to the default. chevronDirectionWindowMs defaults to 0, which inherits the aggregate window so colour and direction resolve at the same temporal granularity — set it non-zero to decouple them.

These props drive the directional chevrons in the bixi-streets-flow demo, where signedFlow and chevronPerTripLight pair with a ChevronFlowExtension added through the inherited extensions prop.

FlowStrokeLayer extends FlowCorridorLayer for merged, directed corridor networks — it keeps the same per-vertex time-bucket coloring and adds breathing width plus twin directional offset ribbons. ChevronFlowExtension is the standalone LayerExtension form of the marching-arrow effect this layer's signedFlow / chevronPerTripLight props are designed to feed — it can also be applied to any other PathLayer-based layer.

Difference from FlowmapLayer / FlowLinesLayer#

FlowmapLayer and FlowLinesLayer animate weighted arrows/arcs between an origin and destination — the width tracks volume at the playhead. FlowCorridorLayer instead renders the static corridor geometry and animates color: the network shape never changes, and each corridor's value pulses through the gradient ramp over time. Use it when you have a fixed network (streets, rails) with a per-segment volume time series, rather than discrete OD pairs.

Source#

packages/layers/src/layers/trips/flow-corridor-layer.ts