@toclocoinc/lattice-grid 1.9.0 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  dependencies, no build step required. Optional adapters for React, Vue, Svelte
5
5
  and Web Components ship alongside it.
6
6
 
7
- Version 1.9.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
7
+ Version 1.10.0 · [latticegrid.dev](https://www.latticegrid.dev) · TOCLOCO Inc
8
8
 
9
9
  ---
10
10
 
@@ -61,7 +61,7 @@ enough to know whether the grid covers what you need.
61
61
 
62
62
  ### Working with the data
63
63
 
64
- - **Editing.** Cell, row and form editing, with over twenty editors — text,
64
+ - **Editing.** Cell, row and form editing, with twenty-three editors — text,
65
65
  number, date, time, select, multi-select, colour, rating, slider, segmented,
66
66
  code, password, icon picker and more. Validation, async commits, optimistic
67
67
  updates with rollback, and a full undo history.
@@ -78,14 +78,14 @@ enough to know whether the grid covers what you need.
78
78
 
79
79
  ### Seeing the data
80
80
 
81
- - **Charts.** `modules/charts` draws thirty chart types from the grid's own
81
+ - **Charts.** `modules/charts` draws thirty-five chart types from the grid's own
82
82
  data — line, bar, area, scatter, pie, donut, sunburst, treemap, radar, gauge,
83
83
  funnel, heatmap, histogram, box plot, candlestick, combo, geomap, sankey,
84
84
  chord, network, stream, violin, gantt and more. They follow the grid's
85
85
  filters, and clicking a mark can filter it in turn.
86
86
  - **Statistics.** `grid.statistics` profiles a column in one pass — count,
87
87
  missing, distinct, five-number summary, standard deviation, outliers and a
88
- histogram — and answers correlations and weighted averages. Twenty-eight
88
+ histogram — and answers correlations and weighted averages. Thirty-eight
89
89
  reduction kernels are available to the totals row, and you can register your
90
90
  own.
91
91
  - **Shadow columns.** Values the grid maintains about itself: how many times a
@@ -119,7 +119,8 @@ enough to know whether the grid covers what you need.
119
119
  - **Accessible.** Keyboard operable throughout, ARIA grid semantics, a live
120
120
  region for announcements, honours reduced motion, forced colours and large
121
121
  target sizes. The devtools module runs the accessibility checks in place.
122
- - **Internationalised.** Eighteen complete locales, right-to-left layout, and
122
+ - **Internationalised.** Twenty-two complete locale catalogues across nineteen
123
+ languages, right-to-left layout, and
123
124
  locale-aware number, date and currency formatting throughout.
124
125
  - **Themeable** through CSS custom properties, with light and dark built in and
125
126
  a reset that keeps a host page's stylesheet out.
package/docs/API.html CHANGED
@@ -768,6 +768,8 @@ autoInit(document); <span class="cmt">// builds every [data-lattice-grid] under
768
768
  <tr><td class="name">density</td><td class="type">'compact' | 'standard' | 'comfortable' | 'spacious' | number</td><td class="dflt">'compact'</td><td class="desc">One scale that every geometry token derives from — row height, spacing, decoration sizes, and type at a damped rate. Row heights are 23.8 / 28 / 42 / 56px. A number scales 28px, so <code>1.4</code> gives 39.2px for anything between the presets. Virtualisation follows it; an explicit <code>rowHeight</code> overrides it.</td></tr>
769
769
  <tr><td class="name">rowHeight</td><td class="type">number | (row) =&gt; number</td><td class="dflt">28</td><td class="desc">A function enables variable-height rows.</td></tr>
770
770
  <tr><td class="name">headerHeight</td><td class="type">number</td><td class="dflt">32</td><td class="desc">Per header row.</td></tr>
771
+ <tr><td class="name">title</td><td class="type">string</td><td class="dflt">&mdash;</td><td class="desc">A caption drawn above the column headings. Inside the grid rather than an element placed above it, so it scrolls with the grid, sits in the region a screen reader announces, and is kept by image capture and print.</td></tr>
772
+ <tr><td class="name">showHeader</td><td class="type">boolean</td><td class="dflt">true</td><td class="desc">Draw the column headings at all. <code>false</code> removes the row, and removes it from the accessibility tree rather than only from view. Distinct from <code>showColumnFunctions</code>, which keeps the headings and drops only their sort, filter and menu controls.</td></tr>
771
773
  <tr><td class="name">overscan</td><td class="type">number</td><td class="dflt">4</td><td class="desc">Rows rendered beyond the viewport.</td></tr>
772
774
  <tr><td class="name">autoHeight</td><td class="type">boolean | 'visible'</td><td class="dflt">—</td><td class="desc">Size rows to their content: cells wrap instead of ellipsising, and each row takes the height its tallest cell needs. Only rendered rows are measured either way — the difference is that <code>true</code> gives up above 10,000 rows and returns to fixed heights, while <code>'visible'</code> keeps measuring at any size and accepts a scrollbar that shifts as rows are measured on the way past.</td></tr>
773
775
  <tr><td class="name">columnVirtualisationAbove</td><td class="type">number</td><td class="dflt">30</td><td class="desc">Column count above which columns virtualise too.</td></tr>
@@ -1680,6 +1682,62 @@ stats.destroy(); // yours to call: the element belongs to your page</code></pr
1680
1682
  </table>
1681
1683
  </div>
1682
1684
 
1685
+ <h3 id="derived-values">Values the grid maintains for you</h3>
1686
+ <p>
1687
+ Three of the ideas in this section are not standard grid vocabulary, so it is worth saying what
1688
+ they have in common before the detail. <strong>Each of them is a value the grid keeps up to date
1689
+ from data you already have</strong>, declared once rather than maintained by hand.
1690
+ </p>
1691
+ <p>
1692
+ The alternative, in every application that needs one of these, is a parallel structure in the
1693
+ host: a copy of what each row looked like a moment ago, a rank recomputed on every tick, a
1694
+ cumulative total that has to be redone whenever the sort changes, and a dashboard panel running
1695
+ its own query beside the table. That code works for a while and then produces a number nobody
1696
+ can account for, usually because one part of it noticed a filter and another did not.
1697
+ </p>
1698
+ <div class="table-wrap">
1699
+ <table>
1700
+ <thead><tr><th>Concept</th><th>What it is</th><th>Reach for it when</th></tr></thead>
1701
+ <tbody>
1702
+ <tr>
1703
+ <td class="name">Shadow column</td>
1704
+ <td class="desc">An extra column, declared against a real one, holding something the grid works out about it: how it has changed, or where it sits among the others.</td>
1705
+ <td class="desc">You want &ldquo;what was this an hour ago&rdquo;, &ldquo;how many places has it moved&rdquo;, or &ldquo;which decile is it in&rdquo; as a column you can sort and filter on.</td>
1706
+ </tr>
1707
+ <tr>
1708
+ <td class="name">Running column</td>
1709
+ <td class="desc">A cumulative value: the total, or the share of the total, <em>by the time you reach this row</em>.</td>
1710
+ <td class="desc">You want a running balance, a cumulative percentage, or a Pareto curve down the page.</td>
1711
+ </tr>
1712
+ <tr>
1713
+ <td class="name">Derived grid</td>
1714
+ <td class="desc">A whole second grid whose rows are built from the first: grouped, unnested, filtered, ranked or profiled.</td>
1715
+ <td class="desc">You want a top-five panel, a breakdown by region, an exceptions list or a statistics summary <em>beside</em> the table, and it must never disagree with it.</td>
1716
+ </tr>
1717
+ </tbody>
1718
+ </table>
1719
+ </div>
1720
+ <p>
1721
+ <strong>The line between a shadow and a running column is the sort order.</strong> A shadow is a
1722
+ function of the column: a row&rsquo;s own history, or where its value sits among the others.
1723
+ Sort the grid differently and a rank is still the same rank. A running total is the opposite
1724
+ &mdash; it answers &ldquo;how much by the time we reach this row&rdquo;, and <em>by the time</em>
1725
+ is the order the rows are in, so re-sorting changes every value in the column. That is why they
1726
+ are declared separately rather than as two kinds of one thing.
1727
+ </p>
1728
+ <p>
1729
+ <strong>A derived grid is a different scale of the same idea.</strong> A shadow adds a column to
1730
+ the rows you have; a derivation produces different rows altogether &mdash; one per sales person
1731
+ rather than one per sale. Because it is a <em>source</em> rather than a special kind of grid,
1732
+ the result sorts, filters, totals, themes and exports like any other, and can itself be the
1733
+ source of another.
1734
+ </p>
1735
+ <p class="section-note">
1736
+ All three read the rows the grid is currently showing, so a filter applied to the table moves
1737
+ the ranks, the running totals and every derived panel together. That is the property worth
1738
+ having: not that any one of them is clever, but that they cannot disagree.
1739
+ </p>
1740
+
1683
1741
  <h3>Shadow columns</h3>
1684
1742
  <p>A shadow column is declared against another column and maintained by the grid. It has no field in the data and it is not a pure computed column either, because its value depends on what happened <em>before</em>. It is a real column throughout: sortable, filterable, totalled, grouped, exported, saved into a view — which is what makes "show me every circuit repriced more than twice this session, most-changed first" one gesture rather than a report.</p>
1685
1743
  <pre><code>columns: [
@@ -1720,13 +1778,18 @@ stats.destroy(); // yours to call: the element belongs to your page</code></pr
1720
1778
  </table>
1721
1779
  </div>
1722
1780
  <h3>Running totals</h3>
1723
- <p>A running total is <strong>not</strong> a shadow kind, and the distinction is the design. Every shadow is a function of the column — of a row's own history, or of where its value sits among the others — so it reads the same however the rows are arranged. A running total does not: sort the grid differently and every value changes, because the question is "how much by the time we reach this row", and <em>by the time</em> is the sort order.</p>
1781
+ <p>
1782
+ A running column answers &ldquo;how much by the time we reach this row&rdquo;: a balance
1783
+ that accumulates down the page, or the share of the total accounted for so far. It is the
1784
+ column a Pareto chart is made of, and the one a finance report opens with.
1785
+ </p>
1786
+ <p>It is declared separately from a shadow column, and the sort order is the reason. Every shadow is a function of the column — of a row's own history, or of where its value sits among the others — so it reads the same however the rows are arranged. A running total does not: sort the grid differently and every value changes, because the question is "how much by the time we reach this row", and <em>by the time</em> is the sort order.</p>
1724
1787
  <pre><code>columns: [
1725
1788
  { field: 'amount', type: 'number' },
1726
1789
  { id: 'cum', title: 'Running', running: { of: 'amount', kind: 'total' } },
1727
1790
  { id: 'share', title: 'Cumulative %', running: { of: 'amount', kind: 'percent' } },
1728
1791
  ]</code></pre>
1729
- <p>Computed in one pass over the display rows and cached against that ordering, so a hundred thousand rows are walked once per sort rather than once per cell. Group headings and totals rows are skipped — a running total that counted a subtotal would double everything below it — and a row with no value carries the figure forward unchanged rather than resetting it.</p>
1792
+ <p>Computed in one pass over the display rows and cached against that ordering, so a hundred thousand rows are walked once per sort rather than once per cell. A running column is not sortable. Sorting on one asks the sort to depend on its own output &mdash; the value is defined by the display order &mdash; so the column does not offer a sort unless its definition asks for one, and the query layer refuses such a sort with a warning rather than computing it. Sort by the column it runs over instead. Group headings and totals rows are skipped — a running total that counted a subtotal would double everything below it — and a row with no value carries the figure forward unchanged rather than resetting it.</p>
1730
1793
 
1731
1794
  <div class="note"><p>Positional kinds rank over every <em>tracked</em> row, not over the filtered set: a rank that changed as you filtered would make "the top ten movers" depend on what happened to be on screen, and the column would disagree with itself between two views of the same data. Pass <code>scope: 'filtered'</code> on the shadow spec to rank within what the filters left instead — both answers are legitimate, which is why it is a choice rather than a default.</p></div>
1732
1795
  <div class="note"><p>Shadow state is keyed by row key, never by index — after any sort an index-keyed history would report one row's past against another row's present, and the wrong number would be <em>sortable</em>. Memory is capped at 200,000 tracked rows per column; past that the oldest are dropped and <code>tracking().forgotten</code> says how many, rather than a smaller number being reported as though it were the truth.</p></div>
@@ -2028,10 +2091,91 @@ grid.licence.state(); <span class="cmt">// 'licensed' | 'localhost' | 'tria
2028
2091
  <tr><td class="sig">paged</td><td class="type">fetch</td><td class="desc">A page at a time from a server that paginates.</td></tr>
2029
2092
  <tr><td class="sig">remote</td><td class="type">fetch</td><td class="desc">Blocks fetched as the viewport reaches them, with sort, filter and grouping pushed to the server.</td></tr>
2030
2093
  <tr><td class="sig">stream</td><td class="type">connect</td><td class="desc">Rows arriving over time. Promotes to memory once complete.</td></tr>
2094
+ <tr><td class="sig">derived</td><td class="type">from</td><td class="desc">Rows built from another grid: grouped, unnested, filtered, ranked or profiled. Read-only, and follows the source.</td></tr>
2031
2095
  </tbody>
2032
2096
  </table>
2033
2097
  </div>
2034
2098
 
2099
+ <h3 id="derived-source">Derived sources: a grid built from another grid</h3>
2100
+ <p class="section-note">
2101
+ Most dashboards put a summary panel beside a table &mdash; the top five sales people, the
2102
+ breakdown by region, the exceptions list. Built by hand, that panel runs its own query, and
2103
+ sooner or later somebody filters the table and the panel does not follow. Everyone who has
2104
+ shipped a dashboard has been in the meeting where two numbers on one screen disagree.
2105
+ </p>
2106
+ <p>
2107
+ A derived grid removes the possibility. It is a second grid whose rows are <em>built from the
2108
+ first</em> &mdash; grouped, unnested, filtered, ranked or profiled &mdash; so the panel is the
2109
+ table, one derivation later, and one filter moves both. It answers the questions a summary
2110
+ panel exists for: the top five sales people, the most-sold SKUs, a statistical profile of
2111
+ whatever the user has filtered to.
2112
+ </p>
2113
+ <p>
2114
+ It is a <em>source</em> rather than a new kind of grid, so everything downstream &mdash; its own sorting and filters,
2115
+ totals, shadow columns, formatting, export, themes &mdash; works on the result and knows
2116
+ nothing about where the rows came from. Charts bind to one as readily as to any grid.
2117
+ </p>
2118
+ <pre><code>source: {
2119
+ mode: 'derived',
2120
+ from: salesGrid,
2121
+ follow: 'filtered',
2122
+
2123
+ groupBy: 'rep',
2124
+ select: { revenue: { of: 'amount', fn: 'sum' }, deals: { fn: 'count' } },
2125
+ sort: [{ col: 'revenue', dir: 'desc' }],
2126
+ limit: 5,
2127
+ }</code></pre>
2128
+ <p class="section-note">
2129
+ The pipeline runs in one order, and the order is the contract:
2130
+ <code>unnest &rarr; where &rarr; bucket &rarr; group &rarr; reduce &rarr; sort &rarr; limit</code>.
2131
+ <code>where</code> sits before grouping deliberately: filtering afterwards is a different
2132
+ question &mdash; which <em>groups</em>, not which <em>rows</em> &mdash; and one key cannot mean both.
2133
+ </p>
2134
+ <div class="table-wrap">
2135
+ <table>
2136
+ <thead><tr><th>Key</th><th>Type</th><th>Description</th></tr></thead>
2137
+ <tbody>
2138
+ <tr><td class="name">from</td><td class="type">Grid</td><td class="desc">Required. The grid to read.</td></tr>
2139
+ <tr><td class="name">follow</td><td class="type">'filtered' | 'all' | 'selected' | 'grouped'</td><td class="desc">Which of its rows to read. <code>filtered</code> by default. <code>grouped</code> re-aggregates by whatever dimension the user has grouped the source by, so a panel tracks the reader rather than a dimension fixed when the page was built; with the source ungrouped it falls back to <code>groupBy</code>.</td></tr>
2140
+ <tr><td class="name">unnest</td><td class="type">string</td><td class="desc">Expand an array property, one row per element, keeping the parent's fields. Address the element with a dotted path afterwards &mdash; <code>lines.sku</code> is the element, <code>region</code> is still the parent. A row whose property is absent or empty contributes nothing.</td></tr>
2141
+ <tr><td class="name">where</td><td class="type">(row) =&gt; boolean</td><td class="desc">A row predicate, applied before grouping. With no <code>groupBy</code> the rows pass through as themselves, which is how an exceptions list is built.</td></tr>
2142
+ <tr><td class="name">bucket</td><td class="type">{ of, by }</td><td class="desc">Round a date column down to the start of its period and group on that. <code>by</code> is <code>day</code>, <code>week</code>, <code>month</code>, <code>quarter</code> or <code>year</code>; weeks start on the ISO Monday.</td></tr>
2143
+ <tr><td class="name">groupBy</td><td class="type">string | string[]</td><td class="desc">The dimension, or dimensions, to group by. Omit to pass rows through.</td></tr>
2144
+ <tr><td class="name">select</td><td class="type">Record&lt;string, {of, fn}&gt;</td><td class="desc">The reduced columns, by output id. <code>fn</code> is any key of the totals-row kernels, so <code>median</code>, <code>p95</code>, <code>stddev</code> and <code>gini</code> are available as readily as <code>sum</code>. <code>count</code> needs no <code>of</code>.</td></tr>
2145
+ <tr><td class="name">sort</td><td class="type">{ col, dir }[]</td><td class="desc">Order the derived rows before limiting them. The grid's own user-facing sort is separate and unaffected.</td></tr>
2146
+ <tr><td class="name">limit</td><td class="type">number</td><td class="desc">Keep at most this many rows.</td></tr>
2147
+ <tr><td class="name">limitPer</td><td class="type">string</td><td class="desc">Apply <code>limit</code> within each distinct value of this column rather than overall &mdash; the best three SKUs in <em>each</em> region, which a global limit cannot express.</td></tr>
2148
+ <tr><td class="name">cumulative</td><td class="type">{ of, upTo }</td><td class="desc">Keep rows until their running share of the total reaches <code>upTo</code>, 0 to 1. The Pareto question. The row that crosses the cutoff is kept, because the set has to reach the share.</td></tr>
2149
+ <tr><td class="name">profile</td><td class="type">string | string[]</td><td class="desc">Replaces the pipeline with a transpose: one row per column, with count, present, missing, distinct, min, max, mean, median, quartiles, deviation and outlier count as its columns.</td></tr>
2150
+ <tr><td class="name">orient</td><td class="type">'columns' | 'metrics'</td><td class="desc">With <code>profile</code>, emit one row per statistic instead of one per column &mdash; the shape a dashboard tile wants.</td></tr>
2151
+ <tr><td class="name">refresh</td><td class="type">'live' | 'idle' | 'manual' | number</td><td class="desc">When to re-derive. <code>idle</code> by default, coalescing to a frame, because a hundred cell updates in one frame are one derivation. A number debounces by that many milliseconds.</td></tr>
2152
+ </tbody>
2153
+ </table>
2154
+ </div>
2155
+ <p class="section-note">
2156
+ <strong>Read-only.</strong> A derived row is an answer, not a record: there is no write-back for
2157
+ the sum of four hundred rows, so writes are refused with a reason rather than accepted and
2158
+ discarded on the next refresh.
2159
+ </p>
2160
+ <p class="section-note">
2161
+ <strong>They chain.</strong> A derived grid can be the source of another to any depth &mdash; a
2162
+ profile <em>of</em> the top five &mdash; and a change at the root travels the whole chain. A
2163
+ cycle is refused rather than recursed.
2164
+ </p>
2165
+ <p class="section-note">
2166
+ <strong>The key.</strong> <code>rowKey</code> defaults to the derived key and need not be set.
2167
+ It is the group value, which is what makes a live ranking readable: the row moves rather than
2168
+ the values under it changing.
2169
+ </p>
2170
+ <p class="section-note">
2171
+ <strong>Cost.</strong> Each refresh re-derives from the source in full, so the work is linear in
2172
+ the number of rows read and largely independent of what is reduced &mdash; roughly 650&nbsp;ms
2173
+ per 200,000 rows, whether the selection is one sum or four statistics. For a large source under
2174
+ a live feed, prefer the default <code>idle</code> or a debounce, and narrow with
2175
+ <code>follow: 'filtered'</code> so the derivation reads what the user is looking at rather than
2176
+ the whole table.
2177
+ </p>
2178
+
2035
2179
  <h3>The remote request</h3>
2036
2180
  <p class="section-note">Your <code>fetch</code> receives one object and returns <code>{ rows, total }</code>.</p>
2037
2181
  <div class="table-wrap">
@@ -2274,8 +2418,61 @@ createGrid(el, {
2274
2418
  <p>A unit given <code>{ auto: false }</code> stays off the <code>display: 'auto'</code> ladder while remaining accepted on input and available as an explicit <code>display</code>. That is how imperial units sit beside metric ones without an auto readout jumping between the two.</p>
2275
2419
  <div class="note"><p><strong>The stored value is always a plain number in the column's own unit.</strong> Sorting, filtering, grouping, totals and the pivot all read that number and never the text, which is why <code>250mm</code> sorts below <code>1.5 cm</code> correctly rather than <code>1</code> sorting before <code>9</code>. <code>registerUnitSystem</code> is global and throws on a duplicate name, so register each system once at startup rather than inside a component that may mount twice.</p></div>
2276
2420
 
2421
+ <h2 id="stat">The statistic block</h2>
2422
+ <p>
2423
+ <code>createStat</code> draws the tile a dashboard opens with: a label, a value, its change
2424
+ against a baseline, and a line saying what the comparison was. Two things make it worth using
2425
+ rather than writing. <strong>It reads the grid</strong>, so it cannot disagree with the table
2426
+ beneath it &mdash; a tile saying &pound;4.2M above a table filtered to &pound;1.8M is worse
2427
+ than no tile, and that is what a hand-built tile does the first time somebody adds a filter.
2428
+ And <strong>it formats through the column's own type</strong>: a stat over a
2429
+ <code>seconds</code> column reads <code>42.1 ms</code>, over a money column with an auto ladder
2430
+ <code>&pound;1.2M</code>, with nothing declared.
2431
+ </p>
2432
+ <pre><code>import { createStat } from '@toclocoinc/lattice-grid';
2433
+
2434
+ createStat({
2435
+ grid, container: '#mrr',
2436
+ title: 'Monthly recurring revenue',
2437
+ of: 'mrr', fn: 'sum',
2438
+ baseline: lastMonth,
2439
+ footer: 'vs. last month',
2440
+ });</code></pre>
2441
+ <div class="table-wrap">
2442
+ <table>
2443
+ <thead><tr><th>Key</th><th>Type</th><th>Description</th></tr></thead>
2444
+ <tbody>
2445
+ <tr><td class="name">grid</td><td class="type">Grid</td><td class="desc">The grid to read.</td></tr>
2446
+ <tr><td class="name">container</td><td class="type">Element | string</td><td class="desc">Required. An element, or a selector resolved against the grid's document.</td></tr>
2447
+ <tr><td class="name">title</td><td class="type">string</td><td class="desc">The label above the value. Hidden when absent rather than left blank.</td></tr>
2448
+ <tr><td class="name">of</td><td class="type">string</td><td class="desc">The column to reduce. Omit for <code>count</code>.</td></tr>
2449
+ <tr><td class="name">fn</td><td class="type">TotalName</td><td class="desc">Any of the totals-row kernels: <code>sum</code>, <code>avg</code>, <code>median</code>, <code>p95</code>, <code>distinct</code>, <code>gini</code> and the rest. <code>sum</code> by default.</td></tr>
2450
+ <tr><td class="name">show</td><td class="type">string</td><td class="desc">Report this column from the row holding the extreme, rather than the extreme itself &mdash; <code>{ of: 'sales', fn: 'max', show: 'rep' }</code> is the <em>name</em> of the best rep. Needs <code>min</code> or <code>max</code>; no single row holds an average, so any other reduction is refused with a warning.</td></tr>
2451
+ <tr><td class="name">value</td><td class="type">unknown | fn</td><td class="desc">A literal value &mdash; numeric or otherwise &mdash; or a function of the grid, instead of a reduction.</td></tr>
2452
+ <tr><td class="name">footer</td><td class="type">string | fn</td><td class="desc">Text under the value, or a function of it.</td></tr>
2453
+ <tr><td class="name">baseline</td><td class="type">number | fn</td><td class="desc">What the value is compared against. A zero baseline reports the absolute change and no percentage, because &ldquo;up infinity per cent&rdquo; is not a reading anyone can act on.</td></tr>
2454
+ <tr><td class="name">goodWhen</td><td class="type">'up' | 'down' | 'neither'</td><td class="desc">Whether a rise is good news, which decides the colour. <code>up</code> by default. Revenue up is green and error rate up is red; a tile that paints every rise green is misleading on half a dashboard.</td></tr>
2455
+ <tr><td class="name">scope</td><td class="type">'filtered' | 'all' | 'selected'</td><td class="desc">Which rows feed the value. <code>filtered</code> by default; <code>all</code> for a tile that is deliberately a constant, such as the denominator a filtered number is a share of.</td></tr>
2456
+ <tr><td class="name">live</td><td class="type">boolean</td><td class="desc"><code>false</code> stops the tile following the grid. <code>refresh()</code> still works, so a caller can drive it.</td></tr>
2457
+ <tr><td class="name">format</td><td class="type">(value, grid) =&gt; string</td><td class="desc">Override the formatting the column's type would apply.</td></tr>
2458
+ <tr><td class="name">empty</td><td class="type">string</td><td class="desc">Shown when there is no value. An em dash by default.</td></tr>
2459
+ <tr><td class="name">decimals</td><td class="type">number</td><td class="desc">Fraction digits for a value whose reduction changed the unit. 2 by default.</td></tr>
2460
+ </tbody>
2461
+ </table>
2462
+ </div>
2463
+ <p class="section-note">
2464
+ The column's formatter is borrowed only where the reduction leaves the unit alone. A Gini
2465
+ coefficient over a money column is a ratio between 0 and 1, and rendering it as
2466
+ <code>$0.34</code> says it is thirty-four cents; counts, ratios and variances &mdash; which are
2467
+ in units <em>squared</em> &mdash; fall back to a plain number.
2468
+ </p>
2469
+ <p class="section-note">
2470
+ Returns <code>{ element, value, refresh, destroy }</code>. A misconfigured tile returns an inert
2471
+ handle rather than throwing, so a dashboard with one bad tile still renders the other eleven.
2472
+ </p>
2473
+
2277
2474
  <h2 id="chartsmodule">The charts module</h2>
2278
- <p><code>modules/charts</code> draws thirty chart types from the grid's own data. It is optional and imports nothing from the grid &mdash; the grid is handed in &mdash; so the bundle carries the drawing and none of the grid, and a page that never charts never loads it.</p>
2475
+ <p><code>modules/charts</code> draws thirty-five chart types from the grid's own data. It is optional and imports nothing from the grid &mdash; the grid is handed in &mdash; so the bundle carries the drawing and none of the grid, and a page that never charts never loads it.</p>
2279
2476
  <pre><code>import { createChart } from '@toclocoinc/lattice-grid/modules/charts';
2280
2477
 
2281
2478
  const chart = createChart({
@@ -2566,7 +2763,7 @@ createGrid(el, {
2566
2763
  <span class="chip chip--new">ipv4</span><span class="chip chip--new">cidr</span><span class="chip chip--new">ipv6</span>
2567
2764
  <span class="chip chip--new">json</span><span class="chip chip--new">secret</span>
2568
2765
  <span class="chip chip--new">hex</span><span class="chip chip--new">hex8</span><span class="chip chip--new">hex16</span><span class="chip chip--new">hex32</span>
2569
- <span class="chip chip--new">binary</span><span class="chip chip--new">binary8</span><span class="chip chip--new">octal</span>
2766
+ <span class="chip chip--new">binary</span><span class="chip chip--new">binary8</span><span class="chip chip--new">octal</span><span class="chip chip--new">decibel</span><span class="chip chip--new">decibelAmplitude</span>
2570
2767
  <span class="chip chip--new">bytes</span><span class="chip chip--new">megabytes</span><span class="chip chip--new">gigabytes</span>
2571
2768
  <span class="chip chip--new">bitrate</span><span class="chip chip--new">gigabits</span>
2572
2769
  <span class="chip chip--new">metres</span><span class="chip chip--new">millimetres</span><span class="chip chip--new">kilometres</span>
@@ -2587,7 +2784,7 @@ createGrid(el, {
2587
2784
  <span class="chip chip--new">luminousFlux</span><span class="chip chip--new">illuminance</span><span class="chip chip--new">substance</span>
2588
2785
  <span class="chip chip--new">absorbedDose</span><span class="chip chip--new">equivalentDose</span><span class="chip chip--new">radioactivity</span>
2589
2786
  <span class="chip chip--new">frequency</span>
2590
- <span class="chip chip--new">celsius</span><span class="chip chip--new">fahrenheit</span><span class="chip chip--new">kelvin</span>
2787
+ <span class="chip chip--new">luminousIntensity</span><span class="chip chip--new">doseRate</span><span class="chip chip--new">rpm</span><span class="chip chip--new">angularVelocity</span><span class="chip chip--new">ppm</span><span class="chip chip--new">ppb</span><span class="chip chip--new">basisPoints</span><span class="chip chip--new">molarity</span><span class="chip chip--new">massFlow</span><span class="chip chip--new">tonnesPerHour</span><span class="chip chip--new">viscosity</span><span class="chip chip--new">kinematicViscosity</span><span class="chip chip--new">thermalConductivity</span><span class="chip chip--new">specificHeat</span><span class="chip chip--new">celsius</span><span class="chip chip--new">fahrenheit</span><span class="chip chip--new">kelvin</span>
2591
2788
  </div>
2592
2789
 
2593
2790
  <h3>Editors</h3>
@@ -1281,7 +1281,7 @@ LatticeGrid.getVersion(); <span class="cmt">// the same, when you have no grid
1281
1281
  <tr><td class="name">Units — computing</td><td class="desc"><code>bytes</code>, <code>megabytes</code>, <code>gigabytes</code>, <code>bitrate</code>, <code>gigabits</code></td></tr>
1282
1282
  <tr><td class="name">Units — physical</td><td class="desc"><code>metres</code>, <code>millimetres</code>, <code>kilometres</code>, <code>grams</code>, <code>kilograms</code>, <code>tonnes</code>, <code>seconds</code>, <code>milliseconds</code>, <code>hours</code></td></tr>
1283
1283
  <tr><td class="name">Units — engineering</td><td class="desc"><code>speed</code>, <code>kph</code>, <code>mph</code>, <code>knots</code>, <code>acceleration</code>, <code>area</code>, <code>hectares</code>, <code>volume</code>, <code>cubicMetres</code>, <code>energy</code>, <code>kilowattHours</code>, <code>power</code>, <code>kilowatts</code>, <code>force</code>, <code>pressure</code>, <code>bar</code>, <code>psi</code>, <code>torque</code>, <code>density</code>, <code>flow</code>, <code>litresPerMinute</code>, <code>radians</code>, <code>degrees</code></td></tr>
1284
- <tr><td class="name">Units — electrical and scientific</td><td class="desc"><code>voltage</code>, <code>current</code>, <code>resistance</code>, <code>capacitance</code>, <code>inductance</code>, <code>charge</code>, <code>conductance</code>, <code>fluxDensity</code>, <code>luminousFlux</code>, <code>illuminance</code>, <code>substance</code>, <code>absorbedDose</code>, <code>equivalentDose</code>, <code>radioactivity</code>, <code>frequency</code></td></tr>
1284
+ <tr><td class="name">Units — electrical and scientific</td><td class="desc"><code>voltage</code>, <code>current</code>, <code>resistance</code>, <code>capacitance</code>, <code>inductance</code>, <code>charge</code>, <code>conductance</code>, <code>fluxDensity</code>, <code>luminousFlux</code>, <code>illuminance</code>, <code>substance</code>, <code>absorbedDose</code>, <code>equivalentDose</code>, <code>radioactivity</code>, <code>luminousIntensity</code>, <code>doseRate</code>, <code>rpm</code>, <code>angularVelocity</code>, <code>ppm</code>, <code>ppb</code>, <code>basisPoints</code>, <code>molarity</code>, <code>massFlow</code>, <code>tonnesPerHour</code>, <code>viscosity</code>, <code>kinematicViscosity</code>, <code>thermalConductivity</code>, <code>specificHeat</code>, <code>frequency</code></td></tr>
1285
1285
  <tr><td class="name">Temperature</td><td class="desc"><code>celsius</code>, <code>fahrenheit</code>, <code>kelvin</code></td></tr>
1286
1286
  <tr><td class="name">Structured</td><td class="desc"><code>json</code>, <code>colour</code>, <code>rating</code>, <code>percent</code></td></tr>
1287
1287
  </tbody>