Data model
Every data component takes rows (or a dataset) and field names as strings; types, labels and formats come from a schema that is inferred or declared once.
Rows and fields
data accepts an array of plain objects or a dataset made with dataset(). Fields are named by key: x="date", y="revenue", category="region". When rows share an x value (or a category), charts combine them: line, area and bar charts sum them, and BarList, DonutChart, Heatmap and CalendarHeatmap take an aggregate prop. You can pass raw order rows when summation matches the question. Exact duplicate coordinates are summed; timestamps are not automatically bucketed by day. For means, distinct counts or other statistics, aggregate explicitly. Studio imposes a stricter unique-coordinate rule on lines to protect non-additive API measures.
Missing values and explicit zeros
Numeric charts and aggregates accept finite numbers and nonblank numeric strings. Nulls, blank strings, booleans, objects and nonfinite values are not numeric observations. An explicit zero remains zero. Missing line measures appear as gaps; invalid x coordinates are omitted.
A nonempty group with no valid measures is unavailable, not zero. Table/KPI sums over an empty population are zero; counts still count records even when their measures are missing. Formatting changes labels, not stored values: percent formats expect zero-to-one ratios, while humidity already expressed as 0–100 needs number format with a percent unit.
Schema inference
Without a dataset, the schema is inferred from up to 200 rows. ISO date strings and Date objects become temporal; finite numbers become quantitative; names ending in revenue, amount, price, cost or similar get a currency format; rates between −1 and 1 named rate, share, conversion and similar become percentages; labels are humanized from the key. This table is computed live by inferSchema from two sample rows:
| Field | Type | Label | Format | Unit |
|---|---|---|---|---|
| date | temporal | Date | date-short | — |
| region | nominal | Region | text | — |
| amount | quantitative | Amount | currency | USD |
| items | quantitative | Items | integer | COUNT |
| conversion_rate | quantitative | Conversion rate | percent | RATE |
| gift | boolean | Gift | text | — |
Declaring fields with dataset()
When inference is wrong or a label needs wording, attach a schema once with dataset(rows, fields). An override is a format name or an object with type, label, format, currency and unit. Add axisFormat, tooltipFormat, description or timeZone to choose display roles and explanatory context. Axes, tooltips, tables and KPIs read this metadata.
import { dataset } from '@quartile/react';
// Describe only what inference gets wrong; every other field is inferred.
export const orders = dataset(rows, {
amount: { label: 'Revenue', format: 'currency', currency: 'EUR' },
conversion_rate: 'percent',
hour: { type: 'nominal', label: 'Hour of day' },
});Formats
A format is a name, an Intl.NumberFormatOptions object, a typed number/date/bytes/duration descriptor or a function. Named formats use the provider's locale. Examples below assume English conventions; actual punctuation and ordering are locale-aware. Read Formatting and notation for precision, time zones and adapters.
| Name | Example |
|---|---|
| integer | 12,480 |
| number | 1,284.56 |
| compact | 12.5K |
| currency | $1,284.56; full value with currency-specific minor units |
| currency-compact | $187.4K |
| percent | 3.1% |
| pt | 0.21 pt |
| date | Oct 6, 2026 |
| date-short | Oct 6 |
| month | Oct 26 (localized short month and two-digit year) |
| weekday | Tue, Oct 6 |
| datetime | Date with year, hour, minute and second |
| time | Hour, minute and second |
| scientific / engineering | Scientific exponents or exponents in multiples of three |
| bytes / bytes-binary | Decimal kB/MB or binary KiB/MiB |
| duration | Elapsed milliseconds, hours may exceed 24 |
| text | as is |
Row types in TypeScript
Row generics extend Row, which is Record<string, unknown>. Describe row shapes with type aliases. A TypeScript interface without an index signature is not assignable to Row; an interface explicitly extending Row is another option.
// ✓ A type alias is assignable to Row (Record<string, unknown>)
type Order = { date: string; region: string; amount: number };
// ✗ An interface has no index signature, so data={orders} is rejected
interface OrderShape { date: string; region: string; amount: number }Field props are typed as keyof R & string, so a misspelled field name is a type error.