Skip to content
quartiledocsv0.1
GitHub

Formatting and notation

IntlField metadata

Give every value the right precision, notation, locale and context—from a compact axis to an exact table.

Try the same data in different formats

Change the locale, time zone and notation. Inspect a chart point with a pointer or keyboard, switch to exact values, then export raw or formatted CSV. The four fictional observations stay unchanged. For more formats and inspectable parts, open the full formatting lab.

Total batch costEUR
€514,523.24

Concentration from Oct 10, 2026, 12:30:00 AM UTC to Oct 10, 2026, 3:30:00 AM UTC: up 90.5%, from 1.234E-5 mol/L to 2.351E-5 mol/L. Low 1.234E-5 mol/L on Oct 10, 2026, 12:30:00 AM UTC; high 2.351E-5 mol/L on Oct 10, 2026, 3:30:00 AM UTC. Four fictional observations. The missing third measurement leaves a gap.

Concentration by observation time
Observed atConcentration
Oct 10, 2026, 12:30:00 AM UTC1.234E-5 mol/L
Oct 10, 2026, 1:30:00 AM UTC1.985E-5 mol/L
Oct 10, 2026, 2:30:00 AM UTCNot measured
Oct 10, 2026, 3:30:00 AM UTC2.351E-5 mol/L
Exact observations
Header explanations work on hover or focus. CSV exports the matching records; raw values are the default.
4 of 4 source rows
Q-01Oct 10, 2026, 12:30:00 AM UTC1.234E-5 mol/L€123,456.78
Q-02Oct 10, 2026, 1:30:00 AM UTC1.985E-5 mol/L€128,530.45
Q-03Oct 10, 2026, 2:30:00 AM UTCNot measured€119,824.39
Q-04Oct 10, 2026, 3:30:00 AM UTC2.351E-5 mol/L€142,711.62
1–4 of 4 rows

Define meaning once, choose each display

import { dataset, QuartileProvider, LineChart, DataTable } from '@quartile/react';

const observations = dataset([
  { id: 'a', time: '2026-10-10T00:30:00Z', value: 0.00001234 },
  { id: 'b', time: '2026-10-10T01:30:00Z', value: 0.00001985 },
], {
  time: { type: 'temporal', format: 'datetime', timeZone: 'UTC' },
  value: {
    label: 'Concentration', type: 'quantitative',
    format: { type: 'number', notation: 'scientific', maximumSignificantDigits: 5, suffix: ' mol/L' },
    axisFormat: { type: 'number', notation: 'engineering', maximumSignificantDigits: 3 },
    tooltipFormat: { type: 'number', notation: 'scientific', maximumSignificantDigits: 6, suffix: ' mol/L' },
    description: 'Measured concentration; a missing value is not zero.',
  },
});

export function Measurements() {
  return <QuartileProvider locale="en-GB" timeZone="UTC">
    <LineChart data={observations} x="time" y="value"
      tooltipNote="One row per observation. No interpolation over missing data." />
    <DataTable data={observations} rowKey="id"
      columns={[{ field: 'time' }, { field: 'value' }]} />
  </QuartileProvider>;
}

A component formatter overrides the relevant field axisFormat or tooltipFormat, then the base format. Explicit options retain their chosen precision. Descriptor locale overrides provider locale. Timestamp time zones resolve from descriptor, then field, then provider; omitting them uses the runtime's zone.

SurfaceContract
AxisaxisFormat, or automatic shortening for supported named formats.
TooltiptooltipFormat, or the base value format; supported tooltips include field explanations.
Exact chart tableBase field format or explicit component format; tooltip abbreviations do not shorten this view.
Table cell / KPIExplicit column/component format, then field format. Currency is full by default.
Header / chart contextField description; DataTableColumn.description can override it. Chart tooltipNote also becomes accessible context.
CSVRaw by default. Formatted mode is an explicit presentation export.

Table header explanations work on focus and hover. tooltipNote appears in supported chart tooltips and accessible descriptions. BarList and DonutChart expose it as chart context rather than a floating tooltip; Sparkline has no tooltipNote prop. All explanation text is plain text.

Precision, currencies and units

NameMeaning
number / integerNumber preserves integer digits; fractional Numbers use up to 15 significant digits. Integer rounds the label.
compactLocale-aware compact notation.
scientific / engineeringExponent notation; engineering uses multiples of three. Six significant digits by default.
currency / currency-compactFull minor-unit-aware currency, or explicit abbreviation. Field currency defaults to USD.
percent / ptRatio × 100 with percent sign, or already-computed percentage points.
bytes / bytes-binaryBase 1000 kB/MB, or base 1024 KiB/MiB.
durationElapsed milliseconds in clock notation; hours can exceed 24.
import { makeFormatter, formatDelta } from '@quartile/react';

const money = makeFormatter({
  type: 'number', style: 'currency', currency: 'EUR',
  currencySign: 'accounting', locale: 'fr-FR',
});
const measured = makeFormatter({
  type: 'number', style: 'unit', unit: 'celsius',
  maximumFractionDigits: 1, missing: 'Not measured',
});
const percentAsStored = makeFormatter({
  type: 'number', style: 'unit', unit: 'percent',
}); // 72 means 72 percent. style: 'percent' expects a ratio.
const change = formatDelta(0.125, 'percent', 1, { locale: 'fr-FR' });

Bare Intl.NumberFormatOptions remain supported. A { type: 'number' } descriptor adds locale, missing text, literal prefix/suffix and finite display scale. Use fractional places for fixed decimals and significant digits for measurement precision; roundingPriority resolves combinations. Newer rounding options depend on runtime support or application-installed polyfills.

Formatting does not convert currency or physical units. Percent style already multiplies ratios by 100. A unit badge is only a label. Display scale uses Number multiplication; it is not an exact financial conversion and does not alter source rows.

Numeric/date missing values use an em dash, or descriptor missing; zero remains zero. Invalid configuration can throw from a direct formatter. Validate user-authored input before rendering. Delta values use the provider locale; percent-formatted KPI comparisons use percentage points. KPI.deltaDigits and DataTableColumn.deltaDigits set 0–20 fractional places for visual change pills independently of the value formatter; they do not change formatted CSV.

Calendar dates, timestamps and elapsed time

import { makeFormatter } from '@quartile/react';

const timestamp = makeFormatter({
  type: 'date', dateStyle: 'medium', timeStyle: 'short',
  timeZone: 'America/New_York', locale: 'en-GB',
});
const elapsed = makeFormatter({
  type: 'duration', unit: 'second', maximumFractionDigits: 2,
});
const memory = makeFormatter({
  type: 'bytes', base: 1024, maximumFractionDigits: 1,
});

Date-only strings such as 2026-10-10 retain calendar identity in every display zone. A timestamp such as 2026-10-10T00:30:00Z is an instant and may display on the prior day in New York. Display time zones do not change chart bucketing or selection semantics. Normalize ambiguous input timestamps upstream.

Choose date/time styles or individual components; Intl rejects mixing them. Native formats respect locale ordering, calendar and hour cycle. Use explicit locale/time zone on server and client. Native date/time literal nonbreaking spaces normalize to ordinary spaces to avoid a common hydration mismatch; other wording can still vary with runtime locale-data versions. Duration is numeric elapsed time, independent of time zones; the default input unit is milliseconds. Bytes and durations allow 0–20 fractional places and use Number arithmetic.

Exact display has a calculation boundary

Modern Intl can preserve bigint and decimal-string digits. Quartile requires a built-in numeric string to represent a finite JavaScript Number, then passes the original string to Intl: "9007199254740993.125" can retain its digits, while "1e400" uses missing text. Named number allows up to 20 fractional places for strings/bigints; use explicit precision when needed. Already-rounded Number values cannot recover lost digits.

Charts, aggregates, byte/duration formatting and display scaling still use Number arithmetic. JSON cannot contain bigint directly. Keep exact decimal/identifier handoffs as strings, or use an application-owned math.js callback for a wider display range. This is not an arbitrary-precision calculation engine.

Helpers, parts and validation

import { makeFormatter, makeRangeFormatter, formatParts, validateFormat } from '@quartile/react';

const format = { type: 'number', notation: 'engineering', maximumSignificantDigits: 4 } as const;
const result = validateFormat(format); // { valid, errors }
if (result.valid) {
  const label = makeFormatter(format)(0.00001234);
  const parts = formatParts(format, 0.00001234);
  const range = makeRangeFormatter(format)(0.00001, 0.00002);
}

makeFieldFormatter(field, options?, override?) resolves a field's surface format. formatField(field, value, options?) formats one value. formatDelta(value, kind?, digits?, options?) accepts locale options. Parts preserve number grouping and bidirectional marks. Native date/time literal U+00A0 and U+202F spaces normalize to ordinary spaces in values, parts and ranges; application text and callbacks stay unchanged. Callbacks and missing values may be literal parts. Number/date ranges use native Intl where available. String/bigint endpoints use separately formatted labels and a separator to preserve their precision; callbacks also use a separator. Distinct values that round alike keep both endpoint labels, without implying measurement uncertainty.

validateFormat returns errors relative to the format root. validateSpec performs the same checks inside dashboard JSON, including native Intl constructor conflicts. External JSON Schema validation alone cannot verify all locale, zone or precision combinations. JSON supports descriptors and named formats; callback functions remain React-only.

PropTypeDefaultDescription
localestring——
currencystring——
timeZonestring——
shortboolean—Short automatic labels for axes. Explicit format objects retain their precision.
surface'value' | 'axis' | 'tooltip'——
PropTypeDefaultDescription
typerequired'number'——
scalenumber—Display-only multiplier. Percent style already multiplies ratios by 100.
prefixstring——
suffixstring——
localestring—Overrides the provider locale for this format.
missingstring—Display text for missing or invalid observations. Defaults to an em dash.

Also accepts the props of Intl.NumberFormatOptions, spread onto the root element.

PropTypeDefaultDescription
typerequired'date'——
localestring—Overrides the provider locale for this format.
missingstring—Display text for missing or invalid observations. Defaults to an em dash.

Also accepts the props of Intl.DateTimeFormatOptions, spread onto the root element.

PropTypeDefaultDescription
typerequired'bytes'——
base1000 | 1024—Decimal kB/MB (1000) or binary KiB/MiB (1024).
maximumFractionDigitsnumber——
localestring—Overrides the provider locale for this format.
missingstring—Display text for missing or invalid observations. Defaults to an em dash.
PropTypeDefaultDescription
typerequired'duration'——
unit'millisecond' | 'second'—Unit of the input observation. A duration is elapsed time, never a timestamp.
maximumFractionDigitsnumber——
localestring—Overrides the provider locale for this format.
missingstring—Display text for missing or invalid observations. Defaults to an em dash.

Number/date descriptors inherit Intl options in addition to the Quartile options listed here. Read the NumberFormat and DateTimeFormat contracts for their full option sets.

Use FormatJS or math.js in your application

Quartile installs none of these libraries. When your application already uses React Intl, reuse its cached imperative formatter. The application owns messages, locale switching and the IntlProvider.

import { useIntl } from 'react-intl'; // Optional application dependency.
import { DataTable } from '@quartile/react';

export function LocalizedTable({ rows }: { rows: { id: string; value: number }[] }) {
  const intl = useIntl(); // Inside your application's IntlProvider.
  return <DataTable data={rows} rowKey="id" columns={[{
    field: 'value',
    format: value => typeof value === 'number' && Number.isFinite(value)
      ? intl.formatNumber(value, { style: 'currency', currency: 'EUR' })
      : '—',
  }]} />;
}
import { format as mathFormat } from 'mathjs'; // Optional application dependency.
import type { Formatter } from '@quartile/react';

const engineering: Formatter = value =>
  typeof value === 'number' && Number.isFinite(value)
    ? mathFormat(value, { notation: 'engineering', precision: 5 })
    : '—';

Callbacks accept an unknown value and return a string; they own missing-value handling. math.js precision means significant digits for engineering/auto/exponential notation and fractional places for fixed notation. Compute with BigNumber or dimensional units upstream, then choose a scalar or text boundary deliberately. An existing d3-format callback can provide SI notation or a fixed shared prefix. See the primary-source research for tradeoffs.

Raw versus formatted CSV

DataExplorer defaults to raw CSV. Choose csvFormat="formatted" to apply column/field formatters with the provider locale/time zone. The lower-level helper accepts tableToCSV(rows, columns, { mode: 'formatted', schema, locale, timeZone }). Export includes all matching sorted result rows before paging; groups export groups.

Formatted CSV contains formatted scalar result values, not cell JSX, sparkline/bar-share visuals or delta-pill rendering. Formula-like strings receive prefix protection. Keep raw mode for machine-readable handoffs. Formatting does not alter filtering, sorting, editing or onExport.rows. See Analytical tables.

PropTypeDefaultDescription
mode'raw' | 'formatted'—Raw values by default. Formatted mode applies column/field formats, not custom cell JSX.
schemaSchema——
localestring——
timeZonestring——

Update an existing preview integration

  • Named currency now keeps the full value and currency-specific minor units. Choose currency-compact or explicit digits to retain a previous display policy.
  • Named number preserves more significant digits; choose maximumFractionDigits: 2 when two decimals are intentional.
  • Month/date-time labels use native locale ordering, and date-time labels include year and seconds. Review snapshots and compact layouts.

The formatting decision records these preview compatibility changes. Continue with field modeling, JSON specs or Studio.

Report an issueBuilt Oct 21, 2026