Lattice Grid Buy a licence

blog

Top 3 Products Per Region: a Derived Grid with limitPer

Building…
Loading a live grid…

A regional merchandising lead asks for the same slide every week: the best three products in each region, so the next order and the next promotion go on what is actually selling there. Built by hand, that slide comes from sorting the full product list by revenue, then going region by region, eyeballing the sorted rows and cutting the list to three. It works until a region is added or a quarter filter is applied to the orders first, and a global top ten is no shortcut: it can fill six of its rows from one region and leave another with none. What the lead needs is top N per group, in JavaScript, recalculated with the filter.

A derived grid can rank within a group, which a plain top N cannot express. Group the orders by region and product, sum the revenue for each pair, sort by revenue and limit to three, with limitPer set to region so the limit resets for every region instead of applying once across all of them. Point it at the orders grid rather than at a dataset of its own, and it reads whatever the filters leave: narrow the orders to a quarter or a single customer and every region’s top three re-derives on the next frame.

That turns a weekly cut-and-paste job into a few lines of configuration that never goes stale between filters. The regional lead applies a filter instead of sending a spreadsheet request, and the top three for every region are right there, recomputed, not re-typed.

Plain JavaScript

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

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

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

// A second grid whose rows come from the first, ranked within each region.
const top3 = createGrid(document.querySelector('#top3'), {
  source: {
    mode: 'derived',
    from: book,            // the grid to read
    follow: 'filtered',    // read whatever the filters leave
    groupBy: ['region', 'product'],
    select: {
      revenue: { of: 'amount', fn: 'sum' },
    },
    sort: [{ col: 'region', dir: 'asc' }, { col: 'revenue', dir: 'desc' }],
    limit: 3,
    limitPer: 'region',
  },
  columns: [
    { field: 'region',  title: 'Region' },
    { field: 'product', title: 'Product' },
    { field: 'revenue', title: 'Revenue', type: 'number' },
  ],
});

Filter the orders grid to Americas and EMEA only, and top3 drops to six rows, three for each of those two regions, APAC’s three gone, with no second query 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 TopProductsByRegion({ orders }) {
  const [book, setBook] = React.useState(null);

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

  const top3Columns = [
    { field: 'region',  title: 'Region' },
    { field: 'product', title: 'Product' },
    { field: 'revenue', title: 'Revenue', type: 'number' },
  ];

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

onGridReady hands back the live orders grid as soon as it mounts, which is what the top three 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, re-ranks the 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 here.
sort Order the derived rows before limit is applied: by region, then by revenue within it, so each region’s three read together.
limit Keep at most this many rows.
limitPer Apply limit within each distinct value of this column rather than overall, the best three products in each region, which a global limit cannot express.

Top N per group: what the example produced

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

Region Product Revenue
APAC Beacon Switch 695
APAC Atlas Router 670
APAC Cirrus Gateway 275
Americas Cirrus Gateway 1,300
Americas Delta Modem 970
Americas Atlas Router 930
EMEA Cirrus Gateway 1,340
EMEA Atlas Router 1,200
EMEA Beacon Switch 310

Nine rows, exactly three for each of the three regions, each sorted by revenue within its own region rather than across all of them. Delta Modem never cracks the top three in EMEA or APAC, but ranks second in Americas, which is the case a single global top ten would have hidden.

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