Lattice Grid Buy a licence

api reference

Renderers, formatting and charts

The built-in renderers and editors, grid.formatting and rules, in-cell charts, highlighting, and styling with CSS custom properties.

API reference › Renderers, formatting and charts

Built-in renderers and editors

Both are addressable by name. Anything you register through components is addressable the same way, and a name you register wins over a built-in one.

Cell renderers, for cell.render:

NameDraws
areaA filled sparkline over a series.
bulletA value against a target and qualitative bands.
checkboxA boolean, optionally as a switch.
colourA colour swatch with its value.
columnA column sparkline.
deltaMovement since the last value, with direction.
detailExpanderThe master-detail chevron. Generated; not usually named directly.
donutA donut chart from a series.
gaugeA value on an arc against a range.
groupThe group and tree label, with its expander and indent.
iconAn icon chosen from the value.
imageA picture from a URL. Selected automatically for type: 'image'.
lineA line sparkline.
linkAn anchor, with the text and href drawn from the row.
pieA pie chart from a series.
pillA status chip carrying a semantic variant.
progressA progress bar with an optional label.
qrcodeA QR code of the value.
rangeA span between a low and a high value.
ratingA star rating.
skeletonA loading placeholder for a row not yet arrived.
stackedA stacked proportion bar.
twolineA primary value with a secondary line beneath it.
winlossA win/loss sparkline of signed values.

Editors, for edit.editor. Each column type already selects a sensible one, so naming an editor is for overriding that choice:

NameEdits
checkboxA boolean.
codeSource text, in a monospace field.
colourA colour.
dateA calendar date.
datetimeA date and a time together.
durationA length of time.
iconPickerOne icon from a set.
ipaddressAn IPv4 or IPv6 address.
multiSelectSeveral options, as chips.
numberA number, with the column's constraints.
objectPickerA record chosen from a list.
passwordA masked secret.
radixA value in its own base.
ratingA star rating.
segmentedOne of a few options, as a segmented control.
selectOne option from a list.
sliderA number on a track.
textA single line. The default.
textareaSeveral lines.
timeA time of day.
treeSelectA value from a hierarchy.
unitA quantity with a unit.

grid.formatting

Conditional formatting rules the grid holds as runtime state, so an end user can change them. Rules travel in a saved view and undo like any other change. A scope is a column id, or '*' for every column; grid-wide rules are evaluated first, then the column's own, as one ordered list in which the first match wins.

This is distinct from compileRules() feeding cell.style, which compiles at configuration time and is what you want for rules a user should not be able to change. Both work at once: a runtime rule layers over whatever cell.style produced, winning only for the properties it names.

createGrid(el, {
  formatting: {                                   // optional seed
    margin: [{ when: { op: 'lt', value: 0 }, style: { background: '#fbeceb' } }],
  },
});

grid.formatting.add('margin', { when: { op: 'lt', value: 0 }, style: { background: 'red' } });
grid.formatting.add('*', { when: { op: 'blank' }, style: { background: '#f1f3f5' } });
grid.formatting.move('margin', ruleId, 0);        // order is meaning
grid.formatting.update('margin', ruleId, { enabled: false });
grid.formatting.remove('margin', ruleId);
grid.formatting.clear('margin');                  // or clear() for everything
MethodReturnsDescription
list(scope?)Rule[]The rules for one scope, in evaluation order.
all()objectEvery rule keyed by scope, the shape a saved view carries.
scopes()string[]Every scope holding at least one rule.
add(scope, rule, opts?)Rule | nullAppends, or inserts at opts.at. Returns the rule with its generated id.
remove(scope, idOrIndex)booleanBy id or position.
update(scope, idOrIndex, patch)Rule | nullMerges fields. The id is identity and cannot be reassigned.
move(scope, idOrIndex, to)booleanReorder, which can change which rule wins.
set(scope, rules)Rule[]Replace one scope.
replaceAll(rules)voidReplace every scope at once.
clear(scope?)voidOne scope, or all of them.
styleFor(colId, value)object | nullWhat the rules alone would paint, for an export or a preview.

A rule held here must be JSON: style may not be a function, because the rules are serialised into views and undo slices. Config-time cell.style still accepts one. Group rows are not formatted, matching the way decoration is dropped for them.

Rules that describe the data, not a threshold

gt: 100 needs somebody to know that 100 is the interesting number. Often nobody does, the interesting cells are the top decile, or the outliers, and where those fall is a property of the data rather than of the rule. These operators say that directly, and the grid works out the threshold from the column itself, over the filtered rows.

grid.formatting.add('margin', { when: { op: 'outlier' }, style: { background: '#fbeceb' } });
grid.formatting.add('qty',    { when: { op: 'topPercent', value: 10 }, style: { bold: true } });
grid.formatting.add('score',  { scale: { from: 'quantile', colours: ['#f8f9fa', '#1a6bc7'] } });

grid.formatting.distribution('margin');   // { n, min, max, mean, stddev, median, q1, q3, iqr }
grid.formatting.restat();                 // re-derive every threshold from the data as it stands
OperatorvalueMarks
topPercent10 or 0.1The top tenth of the column. Written either way; both mean the same thing.
bottomPercent10 or 0.1The bottom tenth.
topN5The five largest, ties included: three rows sharing second place in a top three all take the colour.
bottomN5The five smallest.
aboveMean / belowMean, Either side of the mean.
aboveMedian / belowMedian, Either side of the median, which is the one to reach for on a skewed column.
zAbove / zBelow2That many standard deviations from the mean. A column with no spread marks nothing rather than everything.
outlier1.5Outside Tukey's fences at that many IQRs, the same definition a box plot draws, so the marked cells are the ones its whiskers exclude.

A colour scale can take its bounds the same way, with from in place of min and max: 'minmax' spans the data, 'quantile' spans low to high (5th to 95th percentile by default), 'stddev' spans deviations either side of the mean. The quantile form is the better default on real data, one mistyped order of magnitude otherwise compresses every real value into the first swatch.

Thresholds are pinned when the rules compile and do not move on their own. That is deliberate: a boundary that re-derived itself as rows were filtered would repaint cells whose values had not changed, and nobody comparing two screenshots could tell which of the two things had moved. grid.formatting.restat() is how you move it, and a "recalculate" control is the natural place to put it.

grid.highlight

One mechanism for two jobs: the flash a changed cell makes, and a marker you paint deliberately. A target is a cell ({key, colId}), a row ({key}, or a bare row key) or a column ({colId}). Cell beats row beats column, so a specific highlight is never hidden by a broad one laid over it.

createGrid(el, {
  highlightOnChange: { colour: '#ffe08a', duration: 1200 },   // or just true
});

grid.highlight({ key: 'r1', colId: 'cap' }, { colour: 'green', duration: 800 });
grid.highlight({ key: 'r3' }, { colour: '#fdeaea', duration: 0 });   // 0 = until cleared
grid.highlight({ colId: 'margin' }, { colour: '#e7f1fd', duration: 0 });
grid.highlight.clear({ key: 'r3' });
grid.highlight.clear();                                             // everything
MethodReturnsDescription
highlight(target, opts?)booleancolour (or color) and duration in milliseconds. duration: 0 stays until cleared.
clear(target?)booleanOne target, or every highlight when called with nothing.
list()object[]Every active highlight and its remaining duration.
colourFor(key, colId)string | nullWhat a given cell is painted, after precedence.

A highlight belongs to the row, not the element. Rows are recycled as you scroll, so highlights are reapplied after every paint, they survive scrolling, sorting, filtering and paging without any of them knowing highlights exist.

Conditional formatting

Rules compile into the function cell.style already takes, so a compiled rule set installs exactly like a hand-written style function.

import { compileRules } from '@toclocoinc/lattice-grid';

{ field: 'margin', cell: { style: compileRules([
  { when: { op: 'lt', value: 0 },              style: { background: '#fdecea', colour: '#b91c1c' } },
  { when: { op: 'between', value: 0, value2: 5 }, style: { background: '#fdf3e0' } },
  { scale: { min: 0, max: 100, colours: ['#f8f9fa', '#1a6bc7'] } },
]) } }
KeyDescription
when{ op, value, value2 }, using the same operators as filters: eq, ne, gt, gte, lt, lte, between, outside, contains, notContains, startsWith, endsWith, blank, notBlank, true, false.
styleA style object, or a function of the cell params.
scale{ min, max, colours }, a colour scale. Two or more stops, reached evenly.
stopIfTrueDefault true. false lets a later rule add to this one.
enabledfalse skips the rule without removing it.

Rules are evaluated in order and the first match wins, as in a spreadsheet: "red if overdue, amber if due this week" reads top to bottom and stops. A blank cell satisfies no comparison, so an empty cell is not swept into "less than 100".

A scale's min and max are required rather than derived from the data. A scale that rescaled as rows were filtered would change a cell's colour without its value changing.

In-cell charts

Eleven chart renderers for a cell. Each is a single SVG whose path data is the only thing a repaint writes, so they cost the same as any other cell as rows recycle.

NameShowsReads
lineTrend across a series.An array
areaTrend, with the area beneath filled.An array
columnA bar per point, drawn from zero.An array
winlossOne equal mark per point, up or down.An array
pieHow a set of numbers divides.An array
donutThe same, with a hole.An array
bulletOne measure against a target, over bands.A number
stackedHow one row's total divides, across the cell.An array
rangeThe span a set of values covers, middle marked.An array
gaugeOne value as a dial.A number
deltaDirection and movement over a sampling interval.A number
{ id: 'trend',  field: 'readings', cell: 'line' }
{ id: 'spend',  field: 'monthly',  cell: { render: 'column', props: { min: 0, max: 100 } } }
{ id: 'mix',    field: 'split',    cell: { render: 'donut', props: { hole: 0.55 } } }
{ id: 'sla',    field: 'uptime',
  cell: { render: 'bullet', props: { target: 80, bands: [60, 85], max: 120 } } }

// When the series lives on another property than the cell's value.
{ id: 'trend', field: 'latest', cell: { render: 'line', props: { series: 'readings' } } }
PropApplies toDescription
seriessparklinesProperty name holding the array, when it is not the cell's value.
min / maxallPin the scale so several columns compare like for like.
labelallfalse hides the number beside the chart.
markerline, areafalse hides the dot on the last point.
holedonutInner radius as a fraction, default 0.55.
targetbulletDraws the target marker.
bandsbulletEdges of the qualitative bands, e.g. [60, 85].
intervaldeltaMilliseconds between samples. Default 1000.
modedelta'change' (default) or 'against'.
againstdeltaProperty to compare with in against mode.
showdelta'both', 'arrow' or 'delta'.

Entries that are not numbers are gaps rather than zeroes: a line breaks across them and a bar is omitted. Pin min and max when comparing columns, a sparkline scaled to its own data fills its cell whatever the magnitude.

The chart is aria-hidden and the cell carries a text summary, so a screen reader is told "12 points, 9 to 20, ending 18" rather than each value in turn.

Built-in names

Every registry accepts a custom entry under the same name, which then wins over ours.

Data types

Seven built-in, and seventy-four in the extended catalogue. Inference only ever reaches the built-in names: the candidates are tried in registration order, and every string settles on text and every number on number before an extended type is reached. So a column asks for an extended type by name.

textnumberbooleandatedateStringlookupobject
timedatetimeduration ipv4cidripv6 jsonsecret hexhex8hex16hex32 binarybinary8octaldecibeldecibelAmplitude bytesmegabytesgigabytes bitrategigabits metresmillimetreskilometres gramskilogramstonnes secondsmillisecondshours speedkphmphknots accelerationareahectares volumecubicMetres energykilowattHours powerkilowattsforce pressurebarpsi torquedensity flowlitresPerMinute radiansdegrees voltagecurrentresistance capacitanceinductancecharge conductancefluxDensity luminousFluxilluminancesubstance absorbedDoseequivalentDoseradioactivity frequency luminousIntensitydoseRaterpmangularVelocityppmppbbasisPointsmolaritymassFlowtonnesPerHourviscositykinematicViscositythermalConductivityspecificHeatcelsiusfahrenheitkelvin

Editors

texttextareanumberdatecheckboxselectmultiSelect timedatetimedurationipaddresspasswordcode unittemperatureradixsliderratingsegmented treeSelectobjectPickericonPickercolour

Cell renderers

groupcheckboxprogresslinkpilliconskeletonratingcolourqrcode

Filters and aggregations

textnumberdatesetmultiadvanced
summinmaxavgcountcountValuesfirstlast

Icons

Inline SVG sprites, overridable by name through registerIcon(name, def).

chevronRightchevronDownchevronUpchevronLeft checkdashcloseplusminus infosuccesswarningdanger clocklocklinkexternalfilter sortAscsortDescmenudrag starheartcircleFilledsquareboltflagthumbUp eyeeyeOffcopyblank

Styling and your page's CSS

Forced colours. In Windows High Contrast Mode the grid translates state that is normally a background tint into borders and system colours: selection takes the system's own selection colours, pinned regions swap their shadow for a rule, and diff states are told apart by border style rather than by hue. A colour swatch and a collaborator's presence colour keep their own colour, because there the colour is the information.

Every selector is namespaced under .lattice, so the grid cannot restyle your page. Since 1.4.0 the reverse holds too: the elements the grid builds are given a floor for the properties a host page commonly sets on a bare tag: margin, padding, border, radius, background, shadow, text transform, letter spacing, and type and colour on form controls. A rule such as section { padding: 5.5rem 0 } no longer reaches inside the grid.

No !important is involved. The reset is specificity (0,1,1); every rule that dresses a grid element is (0,2,0) or higher, and so is any rule of yours aimed at a Lattice class. Deliberate overrides work exactly as before: only bare-tag rules are shut out. The reset covers box model and decoration only, never display, position or any dimension.