Lattice Grid Buy a licence

data router starter kit

A desk that keeps up with the market.

A trading screen is the same market feed seen several ways at once: every live quote, the names that are actually moving, and a picture of where prices sit. This template drives all of it from one feed, so the views never fall out of step with each other.

Open in the sandbox See the code

Building…
Loading a live grid…

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.

One feed of quotes fans out to a full quotes grid and a movers grid at once. The movers grid is the same feed narrowed to the names that are really moving and sorted by the size of the move, and the grid itself stays plain: the route does the narrowing and sorting before a row ever reaches it.

Every reprice lands on exactly the row it changes and flashes as it moves, so the screen stays readable at speed and each grid keeps its own scroll and selection through it.

It runs with no backend, so you can build the desk against a feed that behaves like the real one and swap in your market data with a single line.

what the one feed fills

Everything on the screen, off one source.

Quotes

Every live quote, flashing as it reprices.

Biggest moves

The same feed, narrowed to the names moving most.

Prices

A live chart of the last price by symbol.

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: #0e1420; color: #d8dee9; }
  .desk { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
  .panel { background: #131b2a; border: 1px solid #232f45; border-radius: 10px; overflow: hidden; min-width: 0; }
  .panel h2 { font-size: 12px; margin: 0; padding: 8px 12px; border-bottom: 1px solid #1d2740; color: #9fb0cc; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; }
  .grid { height: 300px; }
  #chart { height: 220px; margin-top: 12px; background: #131b2a; border: 1px solid #232f45; border-radius: 10px; }
  @media (max-width: 900px) { .desk { grid-template-columns: 1fr; } }
</style>

<div class="desk">
  <div class="panel"><h2>Quotes</h2><div id="quotes" class="grid"></div></div>
  <div class="panel"><h2>Biggest moves</h2><div id="movers" class="grid"></div></div>
</div>
<div id="chart"></div>

<script>
  var g = LatticeGrid;
  var money = { style: 'currency', currency: 'USD', decimals: 2 };
  var upDown = [
    { id: 'up', when: { op: 'gt', value: 0 }, style: { color: '#3ddc97' } },
    { id: 'down', when: { op: 'lt', value: 0 }, style: { color: '#ff6b6b' } },
  ];
  var cols = [
    { field: 'symbol', title: 'Symbol', layout: { width: 90, pin: 'start' } },
    { field: 'last', title: 'Last', type: 'number', format: money },
    { field: 'chg', title: 'Chg', type: 'number', format: { decimals: 2 } },
    { field: 'bid', title: 'Bid', type: 'number', format: money },
    { field: 'ask', title: 'Ask', type: 'number', format: money },
  ];

  // Every live quote, flashing as it reprices.
  var quotes = g.createGrid(document.getElementById('quotes'), {
    rowKey: 'symbol', theme: 'dark', highlightOnChange: { duration: 350 },
    formatting: { chg: upDown }, columns: cols,
  });

  // The same feed, narrowed to the names that are actually moving and sorted by
  // the size of the move.
  var movers = g.createGrid(document.getElementById('movers'), {
    rowKey: 'symbol', theme: 'dark', highlightOnChange: { duration: 350 },
    formatting: { chg: upDown }, columns: cols,
  });

  // One router keyed on the kind of record, fanning each quote to both grids.
  var router = LatticeGridDataRouter.createDataRouter({ key: 'type', rowKey: 'symbol', overlap: true });
  router.attach(quotes, 'price');
  // A route can reshape and narrow its slice before it reaches the grid: this one
  // shows only the real movers, biggest first, and the grid stays plain.
  router.attach(movers, 'price', {
    filter: function (row) { return Math.abs(row.chg) >= 0.4; },
    sort: { key: 'chg', dir: 'desc' },
  });

  // The chart reads the quotes grid, so it follows the same feed.
  g.createChart({ grid: quotes, container: '#chart', type: 'bar', x: 'symbol', y: 'last', title: 'Last price by symbol' });

  // The feed, with no backend. Going live is one line: swap the mock socket for
  //   var socket = new WebSocket('wss://feed.example.com/marketdata');
  var socket = new LatticeGridMockSocket.MockWebSocket({ feed: LatticeGridMockSocket.priceFeed({ seed: 7 }), rate: 260 });
  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 →