Lattice Grid Buy a licence

blog

Revenue by Region: a Derived Grid, Not a Second Query

Building…
Loading a live grid…

Every orders screen grows the same panel next to it: revenue by region, with its own query against the database or its own loop over the same array the table already holds. It agrees with the table on the day it ships. Then someone adds a region filter, or a date range, and the table narrows while the panel keeps showing the total for everyone, because nothing told it to run again. Now there are two numbers for “this quarter’s revenue” on one screen, and whichever one the reader trusts is the wrong half of the time. A group by inside the JavaScript data grid itself closes that gap.

A derived grid removes the second query instead of fixing it. Build the summary as a second grid whose rows come from the first: group the orders by region, reduce each group to a revenue sum, an order count and an average order size, and point it at the orders grid rather than at a dataset of its own. It reads whatever the orders grid’s filters leave, so filtering the orders table to a region, a quarter or a single customer re-derives the summary on the next frame, with nothing wired between them by hand.

That is one query to write instead of two, and one state that can be wrong instead of two that can disagree. The panel cannot drift from the table, because it is computed from the table, every time the table changes. Revenue by region becomes a few lines of configuration, not a query you write, test and keep in step.

Group by in a JavaScript data grid

import { createGrid } from '@toclocoinc/lattice-grid';

const orders = [
  { id: 1,  region: 'EMEA',     amount: 420 },
  { id: 2,  region: 'APAC',     amount: 180 },
  { id: 3,  region: 'Americas', amount: 960 },
  { id: 4,  region: 'EMEA',     amount: 310 },
  { id: 5,  region: 'Americas', amount: 540 },
  { id: 6,  region: 'APAC',     amount: 275 },
  { id: 7,  region: 'EMEA',     amount: 890 },
  { id: 8,  region: 'Americas', amount: 150 },
  { id: 9,  region: 'APAC',     amount: 410 },
  { id: 10, region: 'EMEA',     amount: 220 },
  { id: 11, region: 'Americas', amount: 730 },
  { id: 12, region: 'APAC',     amount: 95 },
  { id: 13, region: 'EMEA',     amount: 605 },
  { id: 14, region: 'Americas', amount: 340 },
  { id: 15, region: 'APAC',     amount: 515 },
  { id: 16, region: 'EMEA',     amount: 175 },
  { id: 17, region: 'Americas', amount: 820 },
  { id: 18, region: 'APAC',     amount: 260 },
  { id: 19, region: 'EMEA',     amount: 450 },
  { id: 20, region: 'Americas', amount: 390 },
];

const book = createGrid(document.querySelector('#orders'), {
  rowKey: 'id',
  columns: [
    { field: 'id',     title: 'Order' },
    { field: 'region', title: 'Region', filter: { type: 'set' } },
    { field: 'amount', title: 'Amount', type: 'number' },
  ],
  rows: orders,
});

// A second grid whose rows come from the first.
const byRegion = createGrid(document.querySelector('#by-region'), {
  source: {
    mode: 'derived',
    from: book,            // the grid to read
    follow: 'filtered',    // read whatever the filters leave
    groupBy: 'region',
    select: {
      revenue:  { of: 'amount', fn: 'sum' },
      orders:   { fn: 'count' },
      avgOrder: { of: 'amount', fn: 'avg' },
    },
    sort: [{ col: 'revenue', dir: 'desc' }],
  },
  columns: [
    { field: 'region',   title: 'Region' },
    { field: 'revenue',  title: 'Revenue',   type: 'number' },
    { field: 'orders',   title: 'Orders',    type: 'number' },
    { field: 'avgOrder', title: 'Avg order', type: 'number' },
  ],
});

Filter the orders grid to Americas and EMEA only, and byRegion drops from three rows to two, Americas and EMEA unchanged, APAC gone, with no second call anywhere in the example above.

React

import React from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';

// Build the component once, at module scope, not on every render.
const LatticeGrid = createLatticeGrid({ React, createGrid });

function RevenueByRegion({ orders }) {
  const [book, setBook] = React.useState(null);

  const orderColumns = [
    { field: 'id',     title: 'Order' },
    { field: 'region', title: 'Region', filter: { type: 'set' } },
    { field: 'amount', title: 'Amount', type: 'number' },
  ];

  const regionColumns = [
    { field: 'region',   title: 'Region' },
    { field: 'revenue',  title: 'Revenue',   type: 'number' },
    { field: 'orders',   title: 'Orders',    type: 'number' },
    { field: 'avgOrder', title: 'Avg order', type: 'number' },
  ];

  return (
    <>
      <LatticeGrid
        rowKey="id"
        columns={orderColumns}
        rows={orders}
        onGridReady={setBook}
        style={{ height: 320 }}
      />
      {book && (
        <LatticeGrid
          columns={regionColumns}
          source={{
            mode: 'derived',
            from: book,
            follow: 'filtered',
            groupBy: 'region',
            select: {
              revenue:  { of: 'amount', fn: 'sum' },
              orders:   { fn: 'count' },
              avgOrder: { of: 'amount', fn: 'avg' },
            },
            sort: [{ col: 'revenue', dir: 'desc' }],
          }}
          style={{ height: 160 }}
        />
      )}
    </>
  );
}

onGridReady hands back the live orders grid as soon as it mounts, which is what the region panel’s from needs; the panel itself only renders once that instance exists. Filtering the orders grid, from its own header or a quick filter, moves the region panel the same way it does in the plain example, because both read the grid’s public API, not a copy of its rows.

The options used

Option What it does
mode: 'derived' This grid’s rows are computed from another grid’s, not loaded from an array or a server.
from The grid to read.
follow: 'filtered' Read the rows the filters leave. The default.
groupBy The dimension, or dimensions, to group by.
select The reduced columns, by output id: of names the field to reduce, fn is any totals function: sum, count, avg, median, p95 and more.
sort How to order the derived rows before any limit is applied.

What the example actually produced

Running the plain JavaScript example above with createHeadlessGrid from the published package, and printing byRegion’s rows, gives:

Region Revenue Orders Avg order
Americas 3,930 7 561.43
EMEA 3,070 7 438.57
APAC 1,735 6 289.17

Filtering the orders grid to Americas and EMEA and re-reading byRegion leaves exactly those two rows, with the same revenue, order count and average for each, which is the whole point: the summary never has its own copy of the numbers to get out of step.

The derived and chained grids guide covers joins, column profiles, chaining a derived grid from a derived grid, and cross-filtering back into the source in full. For a worked dashboard built the same way, with top sellers and a company-wide tile alongside the per-rep totals, see the sales dashboard demo.

Read next

All posts RSS