Lattice Grid

blog

Scatter Chart for Price Against Conversion

Building…
Loading a live grid…

You are a pricing analyst, and you have a list price and a conversion rate for every SKU in the catalogue. The question is not which SKU converts best. It is where, as the price climbs, buyers stop buying: is there a band where conversion collapses, and where does it start? A sorted table gives you thirty numbers to compare by eye. A scatter chart gives you the shape, and the cliff is the first thing you see.

The example below plots one point per SKU, list price along the bottom and conversion up the side. The chart is bound to the grid, so it draws exactly the rows the grid is showing. Put a SKU in the box, or open the filter on the Price column and keep only the products above fifty, and the points you do not want leave the chart on the next frame. Clear the filter and they come back.

Why a scatter and not a line

A line chart joins its points in order, and that implies a trend from one price to the next that you have not measured. Your SKUs are separate products, not consecutive readings of one thing. A scatter draws each SKU as itself and leaves the space between them empty, so a gap in the prices stays a gap and a single odd product stays visible as a single odd point. The collapse shows up as the place where the cloud drops away.

The chart is a view of the grid

There is no second data set to keep in step. createChart takes the grid itself, reads its filtered rows, and redraws whenever they change. x names the column along the bottom and y the one up the side. Because every SKU has its own price, each row keeps its own point. Sort, filter or edit the grid and the chart follows, because a chart of rows you cannot see would be describing a different data set.

The code, in plain JavaScript

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

const prices = [9, 12, 15, 18, 21, 24, 27, 30, 33, 36, 39, 42, 45, 48, 51,
  54, 57, 60, 63, 66, 69, 72, 75, 78, 81, 84, 87, 90, 93, 96];
const conversion = [5.8, 5.9, 5.6, 5.7, 5.4, 5.5, 5.2, 5.3, 5.0, 5.1, 4.8, 4.9,
  4.6, 4.4, 4.5, 3.1, 2.6, 2.2, 1.8, 1.9, 1.5, 1.2, 1.4, 1.0, 0.9, 1.1, 0.8,
  0.7, 0.8, 0.6];
const rows = prices.map((price, i) => ({
  id: i + 1, sku: `SKU-${101 + i}`, price, conversion: conversion[i],
}));

const grid = createGrid(document.querySelector('#grid'), {
  rowKey: 'id',
  columns: [
    { field: 'sku', title: 'SKU' },
    { field: 'price', title: 'List price', type: 'number', filter: { type: 'number' } },
    { field: 'conversion', title: 'Conversion (%)', type: 'number', filter: { type: 'number' } },
  ],
  rows,
});

createChart({
  grid, container: '#chart', type: 'scatter',
  x: 'price', y: 'conversion',
  title: 'Conversion rate against list price',
});

Give #chart and #grid a height in your page and that is the whole screen.

The same thing in React

The React adapter is a factory: you pass it React and createGrid, and it returns the component. The component forwards a ref whose .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 { createChart } from '@toclocoinc/lattice-grid/modules/charts';
import { createLatticeGrid } from '@toclocoinc/lattice-grid/modules/react';
import '@toclocoinc/lattice-grid/css';

const LatticeGrid = createLatticeGrid({ React, createGrid });
const columns = [
  { field: 'sku', title: 'SKU' },
  { field: 'price', title: 'List price', type: 'number', filter: { type: 'number' } },
  { field: 'conversion', title: 'Conversion (%)', type: 'number', filter: { type: 'number' } },
];

export function PriceBands({ rows }) {
  const gridRef = useRef(null);
  const chartEl = useRef(null);

  useEffect(() => {
    const chart = createChart({
      grid: gridRef.current.grid, container: chartEl.current, type: 'scatter',
      x: 'price', y: 'conversion',
      title: 'Conversion rate against list price',
    });
    return () => chart.destroy();
  }, []);

  return (
    <>
      <div ref={chartEl} style={{ height: 300 }} />
      <LatticeGrid ref={gridRef} rowKey="id" columns={columns} rows={rows} style={{ height: 300 }} />
    </>
  );
}

Pass it the same thirty rows as the first listing, with sku, price and conversion on each.

The options used

Option Type What it does
grid Grid Required. The grid to read.
container Element or string Required. Where to draw.
type string 'scatter' here. 'bubble' adds a third column as the size of each point.
x / y string The two columns to plot against each other.
title string Drawn above the plot.

Go further

The charts page lists every chart type the same grid can feed, and the bar, line and pie family demo draws the scatter and its bubble neighbour beside the rest of the Cartesian types. To see how a chart keeps following the grid when you filter by clicking, read how to draw a chart that follows the grid.

Read next

  • Waterfall Chart for a Gross to Net Bridge

    Bind a waterfall chart to a grid of gross-to-net steps by region. Filter the rows and the bridge redraws. Paste-ready JavaScript and React.

  • 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.

All posts RSS