Sensor Telemetry
The widest grid on this site, and the one that proves column virtualization: 80 instrument channels plus five identity columns, heatmap-coloured against each channel’s own range. Scroll sideways — only the columns on screen exist in the DOM.
Loading live grid…
What you are looking at
- Column virtualization
- Column menu
- Heatmap cells
- Hide + pin columns
- Pinned columns
Every one of those is a documented option, not demo-only code. The manual has the config for each, and the playground lets you edit a grid like this one live.
The code behind it
The whole demo, exactly as it runs above. It is plain JavaScript against the published packages — createGrid, AllModules and the same options the manual documents. Nothing here is private API.
Sensor Telemetry — script129 linesshowhide
const h = React.createElement
const ROWS = 20000
const CHANNELS = 80
const RIGS = ['Thermal Loop A','Thermal Loop B','Vibration Rig','Vacuum Chamber',
'Cryo Stage','Pressure Cell','Spin Table','Acoustic Bay']
const STATES = ['Nominal','Nominal','Nominal','Nominal','Watch','Fault']
let seed = 31337
const rnd = () => (seed = (seed * 1103515245 + 12345) & 0x7fffffff) / 0x7fffffff
// Channel metadata, built once. Each channel gets its own unit and its own
// plausible range, so the heatmap is per-channel rather than one global
// scale across quantities that share no dimension.
const UNITS = ['°C','kPa','mm/s','mA','V','rpm','ppm','µm']
const channels = []
for (let c = 0; c < CHANNELS; c++) {
const unit = UNITS[c % UNITS.length]
const base = 10 + (c * 37) % 400
channels.push({
key: 'ch' + c,
label: 'CH' + String(c + 1).padStart(2, '0'),
unit,
min: base,
max: base + 40 + (c % 7) * 25,
})
}
// 20,000 x 80 = 1.6M readings. Built in one typed pass; the per-row object
// is created once with all its channel keys rather than grown field by
// field, which keeps it on a single hidden class.
const readings = new Array(ROWS)
for (let i = 0; i < ROWS; i++) {
const row = {
id: 'R' + String(i).padStart(6, '0'),
ts: new Date(Date.UTC(2026, 8, 1, 0, 0, 0) + i * 30000)
.toISOString().replace('T', ' ').slice(0, 19),
rig: RIGS[i % RIGS.length],
state: STATES[(i * 13) % STATES.length],
run: 'RUN-' + String(1 + Math.floor(i / 500)).padStart(3, '0'),
}
for (let c = 0; c < CHANNELS; c++) {
const ch = channels[c]
row[ch.key] = Math.round((ch.min + rnd() * (ch.max - ch.min)) * 10) / 10
}
readings[i] = row
}
const stateCell = (v) =>
h('span', { className: 'state state-' + String(v).toLowerCase() },
String(v).toUpperCase())
// ── Columns ───────────────────────────────────────────────────
// Four identity columns, then 80 channels. The identity ones are pinned
// left, so a reader 60 columns deep still knows which run they are in.
const columns = [
{ field: 'ts', header: 'Timestamp',
size: { width: 168 },
display: { pinned: 'left', cellClass: 'ts' },
filter: { type: 'text' } },
{ field: 'id', header: 'Reading',
size: { width: 104 },
display: { pinned: 'left', cellClass: 'unit-id' } },
{ field: 'rig', header: 'Rig',
size: { width: 140 },
filter: { type: 'set' } },
{ field: 'run', header: 'Run',
size: { width: 104 },
filter: { type: 'set' } },
{ field: 'state', header: 'State',
size: { width: 104 },
display: { cellRenderer: stateCell },
filter: { type: 'set' } },
]
for (const ch of channels) {
const span = ch.max - ch.min || 1
columns.push({
field: ch.key,
header: ch.label + ' (' + ch.unit + ')',
size: { width: 112 },
display: {
formatter: (v) => Number(v).toFixed(1),
// Bucketed against THIS channel's own range — a global scale would
// paint every millivolt column blue and every rpm column red.
cellClass: (v) => {
const t = (Number(v) - ch.min) / span
const b = Math.max(0, Math.min(7, Math.floor(t * 8)))
return 'hm-' + b
},
tooltip: (v) => ch.label + ': ' + Number(v).toFixed(1) + ' ' + ch.unit,
},
filter: { type: 'number' },
})
}
let api = null
const grid = createGrid({
modules: [AllModules],
columns,
data: readings,
rowId: 'id',
sorting: { enabled: true, multiSort: true },
filtering: { enabled: true, searchBar: true, searchPlaceholder: 'Search readings…' },
appearance: { rowHeight: 34, headerHeight: 38, rowStriping: false },
export: { filename: 'elitegrid-telemetry', scope: 'filtered' },
events: {
onReady: (a) => { api = a; ready({ rowCount: ROWS, columnCount: columns.length }) },
},
})
createRoot(mountEl).render(
h('div', { className: 'shell' },
h('div', { className: 'hint' },
h('b', null, columns.length + ' columns × ' + ROWS.toLocaleString('en-US') + ' readings'),
' — scroll sideways. Only the columns on screen exist in the DOM. ',
'Use a column\'s ⋮ menu to pin or hide one.'),
h('div', { className: 'grid-wrap' }, h(Grid, { grid })),
),
)
onMessage('EXPORT_CSV', () =>
api && api.exportCSV({ filename: 'elitegrid-telemetry', scope: 'filtered' }))Sensor Telemetry — stylescell renderers and layoutshowhide
.ts {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem; color: var(--eg-muted-text);
}
.unit-id { font-weight: 600; font-size: 0.8125rem; }
/* ── Heatmap ──────────────────────────────────────────────────
Eight buckets rather than a continuous scale: a reader comparing two
cells across a wide table is matching bands, not interpolating, and a
fixed palette keeps every channel on the same scale. */
.yg-cell[class*="hm-"] span {
font-family: 'JetBrains Mono', monospace;
font-size: 0.75rem;
font-variant-numeric: tabular-nums;
}
.yg-cell[class*="hm-"] { justify-content: flex-end; }
.hm-0 { background: rgba(37,99,235,0.16); }
.hm-1 { background: rgba(14,165,233,0.14); }
.hm-2 { background: rgba(20,184,166,0.13); }
.hm-3 { background: rgba(34,197,94,0.12); }
.hm-4 { background: rgba(163,230,53,0.14); }
.hm-5 { background: rgba(250,204,21,0.18); }
.hm-6 { background: rgba(249,115,22,0.20); }
.hm-7 { background: rgba(239,68,68,0.24); }
[data-theme='dark'] .hm-0 { background: rgba(59,130,246,0.22); }
[data-theme='dark'] .hm-1 { background: rgba(14,165,233,0.20); }
[data-theme='dark'] .hm-2 { background: rgba(20,184,166,0.19); }
[data-theme='dark'] .hm-3 { background: rgba(34,197,94,0.18); }
[data-theme='dark'] .hm-4 { background: rgba(163,230,53,0.18); }
[data-theme='dark'] .hm-5 { background: rgba(250,204,21,0.22); }
[data-theme='dark'] .hm-6 { background: rgba(249,115,22,0.26); }
[data-theme='dark'] .hm-7 { background: rgba(239,68,68,0.30); }
.state {
font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.03em;
padding: 2px 7px; border-radius: 4px;
}
.state-nominal { color: #15803d; background: rgba(21,128,61,0.11); }
.state-watch { color: #b45309; background: rgba(180,83,9,0.12); }
.state-fault { color: #be123c; background: rgba(190,18,60,0.12); }
[data-theme='dark'] .state-nominal { color: #4ade80; background: rgba(74,222,128,0.14); }
[data-theme='dark'] .state-watch { color: #fbbf24; background: rgba(251,191,36,0.14); }
[data-theme='dark'] .state-fault { color: #fb7185; background: rgba(251,113,133,0.14); }
.hint {
flex-shrink: 0; padding: 9px 14px;
border-bottom: 1px solid var(--eg-border);
background: var(--eg-surface);
font-size: 0.75rem; color: var(--eg-muted-text);
}
.hint b { color: var(--eg-text); font-weight: 600; }
.shell { display: flex; flex-direction: column; height: 100%; min-height: 0; }
.grid-wrap { flex: 1; min-height: 0; display: flex; flex-direction: column; }