Lattice Grid Buy a licence

api reference

deck.gl API Reference

bindDeck: a deck.gl layer as one more viewer of a grid, and deck's view as the grid's viewport filter. deck.gl stays the page's; nothing of it is imported.

API reference › The deck.gl adapter

All 18 pages Everything on one page → Developer guide →

The deck.gl adapter

modules/deckgl makes a deck.gl layer one more viewer of a grid, the way a chart or a KPI tile is one: the grid decides which rows, your code decides how they look, and deck's own view can filter the grid back. deck.gl and MapLibre stay yours - the module imports neither, ships no dependency, and is handed your Deck (or a React DeckGL's deck) and a function that builds layers with your own deck.gl classes.

import { bindDeck } from '@toclocoinc/lattice-grid/modules/deckgl';

const map = new deck.Deck({ parent, initialViewState: { longitude: 0, latitude: 20, zoom: 1 }, controller: true });
const binding = bindDeck(grid, {
  deck: map,
  position: { lon: 'lon', lat: 'lat' },                 // or { geometry: 'place' }
  layers: (rows, ctx) => [new deck.ScatterplotLayer({
    id: 'quakes', data: rows, getPosition: (r) => [r.lon, r.lat], getRadius: (r) => r.mag * 20000,
  })],
  viewportFilter: true,                                  // deck's view filters the grid
});

Grid → deck. layers(rows, ctx) is called with the rows the grid currently shows - every leaf row after its filter and sort for a grid that holds them all - and its result goes to deck.setProps({ layers }). model:changed and rows:changed queue one rebuild for the next frame however many fire; binding.update() rebuilds now and binding.layers() returns what deck was last given. A geometry position also arrives as GeoJSON Features in ctx.features (the row is each feature's properties), so a GeoJsonLayer needs no conversion; a lon/lat pair gives Point features the same way.

A paged pushdown grid holds a page of its relation, and a map of a page is a map of the page. So, exactly as a map chart over a pushdown source does, the binding asks the engine for the rows inside deck's view - the same question, through the same code - up to viewportCap (default 20,000). Past the cap your function receives the engine's square density cells ({ west, south, east, north, count }) with ctx.binned true, so it can draw a grid or hexagon layer instead of 35,000 overlapping dots. ctx.provenance says what was handed over, in the shape of chart.provenance().viewport: rows drawn, rows matched, the cap, whether it was binned, the box, and whether the engine or the browser counted.

Deck → grid. With viewportFilter: true (or { debounce }, default 150 ms) the binding wraps deck's onViewStateChange - your own handler still runs first and its return value is kept - and, once a pan or zoom by the reader settles (deck's own fitting of its initial view writes nothing), reads the box off deck's own WebMercatorViewport, and writes one condition onto the grid: a withinBbox on a geometry position, or a between pair on lon/lat. It is the map viewport filter's condition, built and merged by the same code, so the grid's other conditions are kept and a second pan replaces the box rather than adding one. A matching condition set from elsewhere - a saved view, a sibling map - moves deck's view to its box instead of being overwritten. destroy() removes the condition, the grid listeners and the wrapper.

Refused by name. A deck without setProps (deckgl:deck), a layers that is not a function (deckgl:layers), or a position naming no column of the grid (deckgl:position) warns once and binds nothing. A view over the cap that the engine cannot bin warns deckgl:over and hands over no rows rather than a random subset.

Rows to deck, a filter followed, a pan written back as the grid's filter, and destroy

const { createHeadlessGrid } = await import('../packages/core/src/index.js');
const { bindDeck } = await import('../packages/modules/deckgl/index.js');

const grid = createHeadlessGrid({
  rowKey: 'id',
  columns: [{ field: 'id' }, { field: 'mag', type: 'number' },
    { field: 'lon', type: 'number' }, { field: 'lat', type: 'number' }],
  rows: Array.from({ length: 40 }, (_, i) => ({ id: i, mag: i % 4, lon: -100 + i * 5, lat: 10 })),
});

// A stand-in for deck.gl's Deck: setProps recorded, one viewport whose box we set.
// On a page this is `new deck.Deck({ ... })`, and the layer is a ScatterplotLayer.
let box = [-180, -85, 180, 85];
const deck = {
  props: {},
  setProps(p) { Object.assign(deck.props, p); },
  getViewports: () => [{
    getBounds: () => box,
    fitBounds: ([[w, s], [e, n]]) => ({ longitude: (w + e) / 2, latitude: (s + n) / 2, zoom: 4 }),
  }],
};

const binding = bindDeck(grid, {
  deck,
  position: { lon: 'lon', lat: 'lat' },
  layers: (rows, ctx) => [{ id: 'quakes', data: rows, binned: ctx.binned }],
  viewportFilter: { debounce: 0 },
  viewportCap: 20000,
});
const drawn = [deck.props.layers[0].data.length];

grid.filters.set({ col: 'mag', op: 'gte', value: 2 });
binding.update();                                  // now, rather than on the next frame
drawn.push(binding.layers()[0].data.length);

box = [-100, 0, -50, 20];                          // the reader pans deck
deck.props.onViewStateChange({ viewState: {}, interactionState: { isDragging: true } });
await new Promise((r) => setTimeout(r, 30));
drawn.push(grid.rows.matchCount());

binding.destroy();                                 // the box condition goes with it
drawn.push(grid.rows.matchCount());
return drawn.join(' | ');                          // 40 | 20 | 5 | 20

Type reference

Generated from the type declarations, so it always matches the release. Each surface lists its properties, its methods and the events it raises as three tables; an option or value type lists its members once.

The deck.gl adapter

DeckBindingOptions

What {@link bindDeck} takes. deck.gl is the page's: the module imports none of it, and is handed the page's `Deck` and a function that builds layers with the page's own deck.gl classes.

PropertyTypeDescription
deck{ setProps(props: Record<string, unknown>): void; props?: Record<string, unknown>; getViewports?(): unknown[]; }The page's deck.gl `Deck` instance (or a React `DeckGL`'s `deck`). The binding calls its `setProps({ layers })`, wraps its `onViewStateChange`, and reads its view from `getViewports()[0]` (`getBounds()`, `fitBounds()`). Without `setProps` nothing is bound and `deckgl:deck` warns.
position{ lon: string; lat: string } | { geometry: string }The column(s) rows are placed by: a `lon`/`lat` pair, or a `geometry` column. A name that is not a column of the grid: nothing is bound and `deckgl:position` warns.
viewportFilterboolean | { debounce?: number }deck's view as a filter on the grid, exactly as a map chart's {@link ChartSpec.viewportFilter}: after a pan or zoom settles (debounced, default 150 ms) the binding writes ONE condition - a `withinBbox` on a geometry column, or a `between` pair on `lon`/`lat` - and a matching condition set from elsewhere moves deck's view to its box. Off by default. (optional)
viewportCapnumberThe most rows fetched for one view of a paged pushdown grid before the engine's density cells are handed over instead; as {@link ChartSpec.viewportCap}, default 20,000. (optional)
MethodSignatureParametersReturnsDescription
layers(rows: Array<Record<string, unknown>>, ctx: DeckLayerContext) => unknown[]rows: Array<Record<string, unknown>>
ctx: DeckLayerContext
=> unknown[]Builds the layers from the rows the grid shows: every leaf row after its filter and sort for a grid that holds them all; for a paged pushdown grid, the rows inside deck's view fetched from the engine, or - past {@link DeckBindingOptions.viewportCap} - the engine's density cells, with `ctx.binned` true. Not a function: nothing is bound and `deckgl:layers` warns.

DeckLayerContext

The second argument of {@link DeckBindingOptions.layers}.

PropertyTypeDescription
gridGridThe bound grid.
position{ lon?: string; lat?: string; geometry?: string }The position, as given.
binnedbooleanTrue when `rows` are density cells (`{ west, south, east, north, count }`), not rows.
pendingbooleanTrue while an engine request for the view is in flight; `rows` is the last answer.
featuresArray<{ type: 'Feature'; geometry: Record<string, unknown>; properties: Record<string, unknown> }>One GeoJSON `Feature` per placed row, the row as its `properties`; empty when binned.
provenanceChartViewportProvenance & { skipped: number }What was handed over - the shape of `chart.provenance().viewport`, where `rows` is the rows drawn and always equals `features.length`, plus `skipped`: the rows handed over whose position is blank or could not be read by the geometry type's own reader (WKT, WKB or GeoJSON, as an object or as text), counted here and never as drawn; 0 when binned.

DeckBinding

A live binding between a grid and a deck.

Properties

No properties.

Methods
MethodSignatureParametersReturnsDescription
update(): void - voidRebuild the layers and hand them to deck now, rather than on the next frame.
layers(): unknown[] - unknown[]The layers last handed to deck.
destroy(): void - voidRemove the binding's viewport condition, its grid listeners and its `onViewStateChange` wrapper.
Events

No events.