Lattice Grid

blog

Waterfall Chart for a Gross to Net Bridge

Building…
Loading a live grid…

If you are a controller, month end ends with the same question from the finance director: we sold this much, we kept this much, so where did the difference go? A table of steps holds the answer and hides it. Returns, discounts, freight and tax are all in there, but you have to add them up in your head to see which one took the most.

A waterfall chart answers it at a glance. Gross sales is the first bar, each deduction is a step down from the running total, and the last bar is net revenue. The chart above is bound to the grid beneath it, so filtering the rows redraws the bridge. Filter the Region column to Online and the steps shrink to what the online channel gave up between gross and net.

Why a waterfall and not a bar chart

A bar chart puts every amount on the same baseline, so the returns bar and the tax bar sit side by side and the reader has to subtract them from the gross bar in their head. A waterfall starts each step where the last one ended, so the gap between the first bar and the last is the sum of the steps, drawn. Use bars to compare amounts with each other, and a waterfall when the amounts add up to something and you want to see how.

The chart over a grid

The chart reads the grid’s rows and adds up the rows that share a step, so there is nothing to keep in step. Here is the whole example in plain JavaScript. It builds the grid from thirty inline rows, one per step and region, then draws the bridge from it.

import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';

const regions = ['North', 'South', 'East', 'West', 'Online', 'Wholesale'];
const steps = {
  'Gross sales': [412000, 358000, 391000, 337000, 276000, 189000],
  Returns: [-18400, -21500, -14200, -12900, -15800, -9100],
  Discounts: [-24600, -19800, -22100, -18400, -16200, -21700],
  Freight: [-9200, -11400, -8600, -10100, -7300, -5200],
  Tax: [-31500, -27200, -29800, -25600, -21100, -14400],
};
const rows = Object.entries(steps).flatMap(([step, amounts], s) =>
  amounts.map((amount, r) => ({ id: s * 6 + r, step, region: regions[r], amount })));

const gridEl = document.createElement('div');
const chartEl = document.createElement('div');
gridEl.style.height = chartEl.style.height = '300px';
document.body.append(gridEl, chartEl);

const grid = createGrid(gridEl, {
  rowKey: 'id',
  rows,
  columns: [
    { field: 'step', title: 'Step' },
    { field: 'region', title: 'Region', filter: { type: 'set' } },
    { field: 'amount', title: 'Amount', type: 'number' },
  ],
});

createChart({ grid, container: chartEl, type: 'waterfall', x: 'step', y: 'amount',
  waterfall: { total: 'Net revenue' }, title: 'Gross sales to net revenue' });

The same thing in React

The React adapter is a factory, so you build the component once at module scope. The ref’s grid is the real grid, which is what the chart binds to.

import React, { useEffect, useRef } from 'react';
import { createGrid } from '@toclocoinc/lattice-grid';
import '@toclocoinc/lattice-grid/css';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import { createChart } from '@toclocoinc/lattice-grid/modules/charts';

const LatticeGrid = createLatticeGrid({ React, createGrid });
const regions = ['North', 'South', 'East'];
const steps = {
  'Gross sales': [412000, 358000, 391000],
  Returns: [-18400, -21500, -14200],
  Discounts: [-24600, -19800, -22100],
  Freight: [-9200, -11400, -8600],
  Tax: [-31500, -27200, -29800],
};
const rows = Object.entries(steps).flatMap(([step, amounts], s) =>
  amounts.map((amount, r) => ({ id: s * 3 + r, step, region: regions[r], amount })));
const columns = [
  { field: 'step', title: 'Step' },
  { field: 'region', title: 'Region', filter: { type: 'set' } },
  { field: 'amount', title: 'Amount', type: 'number' },
];

export function Bridge() {
  const gridRef = useRef(null);
  const chartRef = useRef(null);
  useEffect(() => {
    const chart = createChart({ grid: gridRef.current.grid, container: chartRef.current,
      type: 'waterfall', x: 'step', y: 'amount', waterfall: { total: 'Net revenue' } });
    return () => chart.destroy();
  }, []);
  return (<>
    <LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 300 }} />
    <div ref={chartRef} style={{ height: 300 }} />
  </>);
}

The options used

These are the rows of the charts reference for the options in the listings above.

Option Type What it does
grid Grid Required. The grid to read.
container Element or string Required. Where to draw.
type string 'waterfall' here.
x / y string The category and measure columns.
waterfall { total?: boolean or string } total adds a closing bar after the last step, drawn from the baseline to the running total. A string labels it, so a label such as Net revenue names the bridge’s end.
title string Drawn above the plot.

Keep the order of the steps in the rows

Each category is a step from the running total, in the order the rows list them. Put gross sales first and the deductions after it, as the sample rows do, and the closing bar is the net.

Next

See every Cartesian type on one dataset in the Cartesian chart demo, read the full list of chart types on the charts page, or follow the how-to for a chart that follows the grid.

Read next

  • Bar Chart for Tickets by Support Queue

    A JavaScript bar chart bound to a data grid, with open tickets per support queue split by priority and a filter that redraws the bars. Live, with code.

  • Area Chart for Stacked Cloud Spend

    Bind a stacked area chart to a grid of weekly cloud spend per service. Filter the rows and it redraws. Paste-ready JavaScript and React listings.

  • Line Chart for a Monthly Revenue Trend

    A JavaScript line chart bound to a data grid, with twelve months of revenue per product line and a filter that redraws the lines. Live, with code.

All posts RSS