Lattice Grid Buy a licence

demo D58

Header histograms in htmx

A distribution in every heading that is also the filter control

facets: { enabled: true }

This grid draws each column's distribution as a small bar chart inside its header, and that chart is also the filter control. It shows the shape of a numeric or date column at a glance and lets a click or drag on it set the filter for that column.

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="header-histograms-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="account">Account</th>
      <th data-field="service">Service</th>
      <th data-field="region">Region</th>
      <th data-field="change">Change</th>
      <th data-field="cost">Monthly cost</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="1"><td>Legacy (Do Not Decommission)</td><td>Relational Database</td><td>us-east-1</td><td>-0.0468</td><td>31612.55</td></tr>
    <tr data-id="2"><td>Cost Optimisation Programme</td><td>Elastic Compute</td><td>us-east-1</td><td>0.1143</td><td>6109.09</td></tr>
    <tr data-id="3"><td>Data &amp; Insights</td><td>Block Storage</td><td>us-east-1</td><td>0.1082</td><td>42.69</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('header-histograms-table'), {
    toolPanel: { side: 'left', panels: ['filters', 'columns'], actions: ['restore'], exportName: 'lattice-demo' },
    // Open rather than the default one-line strip: the band is the subject here.
    facets: { enabled: true, collapsed: false, height: 46 },
    columns: [
      { field: 'account', title: 'Account', filter: { type: 'set' } },
      { field: 'service', title: 'Service', filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'environment', title: 'Env', filter: { type: 'set' },
        cell: { decoration: 'pill', variant: { map: {
          prod: 'danger', 'prod-2': 'danger', 'definitely-prod': 'danger',
          staging: 'warning', untagged: 'warning', test: 'info',
          dev: 'success', 'not-prod': 'neutral',
        } } } },
      // Quantile buckets on both money columns: cloud spend has a long tail,
      // and equal-width buckets would put nearly every row in the first bar.
      { field: 'change', title: 'Change', type: 'number',
        layout: { width: 140, min: 140 }, format: { style: 'percent', decimals: 1 },
        facet: { strategy: 'quantile' } },
      { field: 'cost', title: 'Monthly cost', type: 'number', layout: { width: 170 },
        format: { style: 'currency', currency: 'USD', decimals: 2 }, total: 'sum',
        facet: { strategy: 'quantile' } },
    ],
  });
</script>

Filtering a numeric column from a histogram in the header

A header histogram draws the distribution of a column’s values as a small bar chart inside the header, then lets a click or drag on that chart set the filter for the column below it. A developer reaches for it when a numeric or date column has enough spread that a plain min/max input leaves the shape of the data invisible: a price column with most rows clustered low and a handful of outliers, or a duration column where the useful cutoff is not obvious until the distribution is visible. Lattice Grid turns this on with facets: { enabled: true }, a grid-level option that builds bucketed counts for each eligible column and keeps them current as rows are added, removed or edited, so the bars never lag an applied filter elsewhere in the grid. Bucketing runs once per column on data change, not per render, so in a JavaScript data grid holding a hundred thousand rows a histogram costs a single pass rather than one per scroll frame. Each bar carries its count and range in an accessible label, so a screen reader announces the bucket a keyboard user has focused rather than requiring the chart’s shape to be seen. The same facet data backs the filter menu, so a histogram selection and a typed range stay in sync.

How do I let users filter a column by clicking its value distribution?

Set facets: { enabled: true } on the grid. Lattice Grid computes bucketed counts for each eligible numeric or date column and renders them as a small histogram in the column header; clicking or dragging across the bars sets the column filter to that range. The buckets recompute as the underlying data changes, so the chart and the active filter stay consistent with each other.