data router starter kit
The whole fleet on one control-room wall.
Telemetry from a whole fleet comes up one pipe, but a control room wants a lane per kind of device with its own readings, plus a trend beside it. This template partitions the single feed by device class, so every lane fills itself and adding a class is adding a grid, not adding a connection.
Live above, running in your browser with no backend. The feed is the mock socket that ships with the grid; going live is the one line that builds it.
why it fits the desk
One feed, the whole screen.
Trucks, drones and sensors report genuinely different readings, so each gets its own grid with its own columns rather than sharing one crowded table. The single feed is split by device class and each lane shows only its own units, live.
Every reading lands on the one device it belongs to and flashes as it changes, so a lane stays readable while it updates and keeps its own scroll and filters through the stream.
It runs with no backend, so you can lay out the wall for your fleet and point it at your real telemetry when you are ready. Going live is a single line.
what the one feed fills
Everything on the screen, off one source.
Trucks
Speed, fuel and status for every vehicle.
Drones
Altitude and battery for everything in the air.
Sensors
Temperature and battery across the estate.
Units online
A live chart of how many units are reporting.
the whole thing, to paste
The code that runs it.
This is the entire template: two script tags for the modules, plain grids, one router in front, and the mock socket driving them. It is what the sandbox opens and what runs above. Paste it into an empty page and it works. On localhost the grid is free to use; a deployed site is licensed per domain.
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/lattice-grid.min.css">
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/lattice-grid.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/charts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/data-router.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.33.0/modules/mock-socket.min.js"></script>
<style>
body { font-family: system-ui, sans-serif; margin: 0; padding: 16px; background: #f6f7f9; color: #1b2430; }
.fleet { display: grid; grid-template-columns: 1fr 1fr 1fr; gap: 12px; }
.panel { background: #fff; border: 1px solid #e3e6ea; border-radius: 10px; overflow: hidden; min-width: 0; }
.panel h2 { font-size: 13px; margin: 0; padding: 8px 12px; border-bottom: 1px solid #eef0f3; color: #3a4250; font-weight: 600; }
.grid { height: 300px; }
#chart { height: 200px; margin-top: 12px; background: #fff; border: 1px solid #e3e6ea; border-radius: 10px; }
@media (max-width: 900px) { .fleet { grid-template-columns: 1fr; } }
</style>
<div class="fleet">
<div class="panel"><h2>Trucks</h2><div id="trucks" class="grid"></div></div>
<div class="panel"><h2>Drones</h2><div id="drones" class="grid"></div></div>
<div class="panel"><h2>Sensors</h2><div id="sensors" class="grid"></div></div>
</div>
<div id="chart"></div>
<script>
var g = LatticeGrid;
var rng = LatticeGridMockSocket.rng;
// One feed for a whole fleet: each device class carries its own readings, so
// the lanes on the screen are genuinely different shapes rather than one table
// filtered three ways. Every record carries a 'kind', which the router splits
// on, and an 'id', its row key. To go live, swap the feed for a real socket.
function* fleetFeed(seed) {
var rand = rng(seed || 11);
var REGIONS = ['North', 'South', 'East', 'West'];
var pick = function (list) { return list[Math.floor(rand() * list.length)]; };
var round = function (n, p) { var f = Math.pow(10, p || 0); return Math.round(n * f) / f; };
var trucks = [], drones = [], sensors = [];
for (var i = 0; i < 14; i++) trucks.push({ id: 'TRK-' + (100 + i), kind: 'truck', region: pick(REGIONS), speed: Math.round(rand() * 70), fuel: 20 + Math.round(rand() * 80), status: 'en route' });
for (var j = 0; j < 10; j++) drones.push({ id: 'DRN-' + (200 + j), kind: 'drone', region: pick(REGIONS), altitude: 40 + Math.round(rand() * 260), battery: 30 + Math.round(rand() * 70), status: 'in flight' });
for (var k = 0; k < 18; k++) sensors.push({ id: 'SNS-' + (300 + k), kind: 'sensor', region: pick(REGIONS), tempC: round(4 + rand() * 30, 1), battery: 30 + Math.round(rand() * 70), status: 'ok' });
var all = trucks.concat(drones, sensors);
var t = 0;
var online = function () { return { id: 'M-' + t, kind: 'metric', t: t, online: all.filter(function (d) { return d.status !== 'offline'; }).length }; };
yield { kind: 'snapshot', rows: all.concat([online()]) };
for (;;) {
var changes = [];
var n = 2 + Math.floor(rand() * 4);
for (var m = 0; m < n; m++) {
var d = all[Math.floor(rand() * all.length)];
if (d.kind === 'truck') { d.speed = Math.round(rand() * 70); d.fuel = Math.max(0, d.fuel - Math.round(rand() * 3)); d.status = d.fuel < 10 ? 'low fuel' : 'en route'; }
else if (d.kind === 'drone') { d.altitude = 40 + Math.round(rand() * 260); d.battery = Math.max(0, d.battery - Math.round(rand() * 4)); d.status = d.battery < 15 ? 'returning' : 'in flight'; }
else { d.tempC = round(4 + rand() * 30, 1); d.battery = Math.max(0, d.battery - Math.round(rand() * 2)); d.status = d.tempC > 30 ? 'alert' : 'ok'; }
changes.push({ op: 'upsert', row: Object.assign({}, d) });
}
t++;
changes.push({ op: 'upsert', row: online() });
yield { kind: 'delta', changes: changes };
}
}
var trucks = g.createGrid(document.getElementById('trucks'), {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 100 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'speed', title: 'Speed', type: 'number' },
{ field: 'fuel', title: 'Fuel %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
var drones = g.createGrid(document.getElementById('drones'), {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 100 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'altitude', title: 'Alt (m)', type: 'number' },
{ field: 'battery', title: 'Battery %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
var sensors = g.createGrid(document.getElementById('sensors'), {
rowKey: 'id', theme: 'light', highlightOnChange: { duration: 400 },
columns: [
{ field: 'id', title: 'Unit', layout: { width: 100 } },
{ field: 'region', title: 'Region', filter: { type: 'set' } },
{ field: 'tempC', title: 'Temp C', type: 'number' },
{ field: 'battery', title: 'Battery %', type: 'number' },
{ field: 'status', title: 'Status' },
],
});
var metrics = g.createHeadlessGrid({
rowKey: 't',
columns: [{ field: 't', type: 'number' }, { field: 'online', type: 'number' }],
});
// One router, keyed on the device class. Adding a class is adding a grid, not
// adding a connection.
var router = LatticeGridDataRouter.createDataRouter({ key: 'kind', rowKey: 'id' });
router.attach(trucks, 'truck');
router.attach(drones, 'drone');
router.attach(sensors, 'sensor');
router.attach(metrics, 'metric', { rowKey: 't' });
g.createChart({ grid: metrics, container: '#chart', type: 'line', x: 't', y: 'online', title: 'Units online per tick' });
var socket = new LatticeGridMockSocket.MockWebSocket({ feed: fleetFeed(11), rate: 800, jitter: 200 });
socket.onmessage = function (event) {
var message = JSON.parse(event.data);
if (message.kind === 'snapshot') router.load(message.rows);
else router.apply(message.changes);
};
</script>
Open it in the sandbox and edit it live → How the Data Router works →
more starter kits