Lattice Grid Buy a licence

demo D47

Colour scales in htmx

A heat map across a numeric column, between bounds you declare

formatting scale rules

This grid shades each cell in a numeric column by where its value sits between bounds you declare, turning a column of figures into a heat map. It makes the highs and lows in a column of margins or temperatures visible without reading every number.

Building…
Loading a live grid…

This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.

The configuration

<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">

<!-- A real, server-rendered table: readable with JavaScript off. data-field on
     each header names the column that cell becomes, and data-id on each row is
     the key the grid carries it under. -->
<table id="colour-scales-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="service">Service</th>
      <th data-field="team">Team</th>
      <th data-field="region">Region</th>
      <th data-field="p99Ms">p99 ms</th>
      <th data-field="monthlyCost">Monthly cost</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="1"><td>svc-00001</td><td>Search</td><td>Malmö</td><td>3906</td><td>37920.55</td></tr>
    <tr data-id="2"><td>svc-00002</td><td>Mobile</td><td>Leeds</td><td>1281</td><td>30070.04</td></tr>
    <tr data-id="3"><td>svc-00003</td><td>Identity</td><td>Zürich</td><td>2637</td><td>36278.48</td></tr>
    <!-- the rest of the rows, rendered by your server -->
  </tbody>
</table>

<script type="module">
  import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';

  // The markup carries the rows; a server-rendered table's column types are
  // declared in the config hydrateTable takes. The grid replaces the table
  // where it stood.
  const grid = hydrateTable(document.getElementById('colour-scales-table'), {
    formatting: {
      // A scale states its own bounds. They are not derived from the rows, so
      // filtering the column does not rescale the colours. The two ink rules
      // run after the scale and carry stopIfTrue: false so the scale's colour is
      // still reached: this is the layering the ordered list is for.
      monthlyCost: [
        { id: 'cost-scale', scale: { min: 0, max: 42000, colours: ['#f5f7fa', '#1a6bc7'] } },
        { id: 'cost-ink-high', when: { op: 'gte', value: 21000 }, style: { color: '#ffffff' }, stopIfTrue: false },
        { id: 'cost-ink-low', when: { op: 'lt', value: 21000 }, style: { color: '#16202b' }, stopIfTrue: false },
      ],
      // Three stops rather than two: the middle colour is reached at the middle
      // of the range, which is what makes a latency column read as good,
      // borderline, bad.
      p99Ms: [
        { id: 'p99-scale', scale: { min: 90, max: 4100, colours: ['#e8f5ee', '#fdf3e0', '#fbeceb'] } },
        { id: 'p99-ink', when: { op: 'notBlank' }, style: { color: '#16202b' }, stopIfTrue: false },
      ],
    },
    columns: [
      { field: 'service', title: 'Service', layout: { pin: 'start', width: 140 } },
      { field: 'team', title: 'Team', filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'p99Ms', title: 'p99 ms', type: 'number', filter: { type: 'number' } },
      { field: 'monthlyCost', title: 'Monthly cost', type: 'number', filter: { type: 'number' }, format: { style: 'currency', currency: 'USD' }, total: 'sum' },
    ],
  });
</script>

Heat-mapping a numeric column with colour scales

A colour scale shades each cell in a numeric column by where its value sits between a declared minimum and maximum, so a column of margins or temperatures reads as a heat map rather than a list of digits. A developer reaches for it when the pattern across a column matters more than any single figure: spotting the worst-performing row in a sales sheet, or the outlier reading in a sensor log, without scanning every number by eye. Lattice Grid configures this through formatting scale rules, a column option taking the bounds and a colour range and interpolating the background for every cell, so the shading updates as values change rather than needing a fixed lookup table. The interpolation runs once per cell against the declared bounds, so a scale over several thousand rows costs the same per cell as a plain formatter, and the JavaScript data grid does not recompute the range on every scroll or resize. Colour alone never carries the only signal: the underlying value stays visible in the cell text, so a viewer relying on a screen reader or unable to distinguish the colour range still gets the figure the shading describes. Bounds can be fixed values or derived from the data, tracking a known target range or the spread present in the current rows.

How do I add a colour scale or heat map to a data grid column?

Apply a formatting scale rule to the column, giving it a minimum, a maximum and the colours to interpolate between. Lattice Grid shades each cell’s background by where its value falls in that range, recalculating per cell without a separate lookup pass. Bounds can be fixed numbers or derived from the visible data, and the cell text stays legible so the colour is a supplement to the value, not a replacement for it.