Infinite row model
For a huge flat source (millions to billions of rows) there is no tree to ship — the browser
holds only a window of rows and requests the next block as you scroll. Sorting and filtering run
in the engine over the whole dataset (get_rows_window) and only the requested slice comes back,
so sort/filter stay correct and cheap no matter how big the source is. The frontend never orders or
filters rows itself.
from dash import Input, Output, callback, no_updatefrom dash_tensor_grid import GridDataEngine, TensorGrid
engine = GridDataEngine(orders) # 1,000,000+ flat rowsN = orders.height
grid = TensorGrid( id="grid", column_defs=column_defs, # accessorKey / header / type / filterType per column row_model="infinite", # window + block requests, not the full frame row_count=N, # sizes the scrollbar row_height=34,)
# The grid emits `rows_request` as you scroll; the engine filters + sorts the WHOLE# frame and returns just the requested [start_row, end_row) window.@callback( Output("grid", "rows_response"), Output("grid", "row_count"), Input("grid", "rows_request"), prevent_initial_call=True,)def serve_block(req): if not req: return no_update, no_update out = engine.get_rows_window( req["start_row"], req["end_row"], sort=req.get("sort"), filters=req.get("filters"), ) resp = {"start_row": req["start_row"], "rows": out["rows"], "row_count": out["row_count"]} return resp, out["row_count"]// Run the same window function server-side (an API route / edge function); the client's// infinite scroller fetches blocks from it. `getRowsWindow` is the port of the engine.import { getRowsWindow } from '@tensorgrid/core';
export function serveBlock(req: { startRow: number; endRow: number; sort?; filters? }) { const { rows, row_count } = getRowsWindow(source, req.startRow, req.endRow, { sort: req.sort, // [{ id, desc }] filters: req.filters, // { [col]: { type: 'range' | 'select' | 'set' | 'text', value | min | max } } }); return { startRow: req.startRow, rows, rowCount: row_count };}// Same core window function behind a Nitro / Express route; the client requests blocks.import { getRowsWindow } from '@tensorgrid/core';
export function serveBlock(req: { startRow: number; endRow: number; sort?; filters? }) { const { rows, row_count } = getRowsWindow(source, req.startRow, req.endRow, { sort: req.sort, filters: req.filters, }); return { startRow: req.startRow, rows, rowCount: row_count };}// The zero-dependency path: call the core window function in your fetch handler or a Web Worker// and feed the returned block to your virtual scroller.import { getRowsWindow } from '@tensorgrid/core';
function serveBlock(req) { const { rows, row_count } = getRowsWindow(source, req.startRow, req.endRow, { sort: req.sort, filters: req.filters, }); return { startRow: req.startRow, rows, rowCount: row_count };}- The frontend does zero math. The engine filters and sorts the entire frame, then returns only
the
[start, end)window plus the filteredrow_count(which sizes the scrollbar). Each returned row carries an absolute_indexsogetRowIdstays stable across re-windows. - Server-mode facets. Because the client caches only a window, set/select value lists and range
bounds can’t be derived client-side — supply them from the engine via
column_facets(engine.facet_counts(col, filters=...)), faceted by the other active filters and recomputed only when the filter model changes, not on scroll. Seeexamples/infinite_app.py. - Byte-identical.
@tensorgrid/core’sgetRowsWindowis a parity-verified port of the Polarsget_rows_window(samenulls_lastsort, same per-column filter semantics). - Reach for this only for a flat source. A large aggregated tree that fits in one payload uses virtualization; a tree too large to ship uses server / lazy mode.