# Charts (/islands/charts)





A family of islands for shape, comparison, distribution, and geography: lines and bars,
parts of a whole, scatter and radar, heatmaps and a calendar, funnels, and a world map.
Each preview below is a real OpenIslands renderer running the same code a live dashboard
uses. For the full registry, see [Islands](/islands/overview.md).

## timeseries.line [#timeseriesline]

A line chart over time, the island for trends. `y` may be a single field or an array of
fields, so one tile can track several measures at once; add a goal line with
`options.goalField`. When two or more series render, a legend with each series' latest
value appears beneath the chart.

<LiveIsland
  type="timeseries.line"
  config="{
  title: &#x22;Revenue vs target&#x22;,
  x: &#x22;month&#x22;,
  y: [&#x22;revenue_eur&#x22;, &#x22;expenses_eur&#x22;],
  options: { goalField: &#x22;target_eur&#x22; },
  format: &#x22;eur&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;month&#x22;, type: &#x22;date&#x22; },
    { name: &#x22;revenue_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;expenses_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;target_eur&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { month: &#x22;2026-01-01&#x22;, revenue_eur: 42_300, expenses_eur: 31_000, target_eur: 45_000 },
    { month: &#x22;2026-02-01&#x22;, revenue_eur: 45_100, expenses_eur: 32_400, target_eur: 47_000 },
    { month: &#x22;2026-03-01&#x22;, revenue_eur: 44_200, expenses_eur: 33_100, target_eur: 49_000 },
    { month: &#x22;2026-04-01&#x22;, revenue_eur: 49_800, expenses_eur: 34_600, target_eur: 51_000 },
    { month: &#x22;2026-05-01&#x22;, revenue_eur: 52_600, expenses_eur: 35_200, target_eur: 53_000 },
    { month: &#x22;2026-06-01&#x22;, revenue_eur: 56_400, expenses_eur: 36_800, target_eur: 55_000 },
  ],
  &#x22;monthly&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "timeseries.line",
  "title": "Revenue vs target",
  "dataset": "monthly",
  "x": "month",
  "y": ["revenue_eur", "expenses_eur"],
  "options": { "goalField": "target_eur" },
  "format": "eur"
}
```

* `x`: the date/time field; accepts `YYYY-MM`, `YYYY-Www`, `YYYY-MM-DD`, or a real
  timestamp (required).
* `y`: a numeric field, or an array of fields for a multi-line chart (required).
* `series`: split one `y` field into a line per distinct value; many values
  auto-show a searchable picker (`options.seriesPicker` forces or disables it).
* `options.goalField`: a column drawn as a neutral goal line; `options.area` fills under
  the lines.
* `colors`: CSS colors per `y` field (or per series value), overriding the palette;
  [`format`](/reference/value-formats.md) styles the axis and tooltips.

<Callout type="info" title="Tip">
  With many series values, pass `series` and let the picker keep the chart legible: it
  draws one series at a time instead of crowding every line onto the canvas.
</Callout>

## category.bar [#categorybar]

A bar chart across discrete categories, the island for comparing groups. Add `group` for
a bar per subgroup, and `stacked: true` to stack those subgroups into one bar per category.

<LiveIsland
  type="category.bar"
  config="{
  title: &#x22;Spend by category&#x22;,
  x: &#x22;category&#x22;,
  y: &#x22;amount_eur&#x22;,
  group: &#x22;quarter&#x22;,
  format: &#x22;eur&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;category&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;quarter&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;amount_eur&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { category: &#x22;Engineering&#x22;, quarter: &#x22;Q1&#x22;, amount_eur: 82_000 },
    { category: &#x22;Engineering&#x22;, quarter: &#x22;Q2&#x22;, amount_eur: 91_000 },
    { category: &#x22;Marketing&#x22;, quarter: &#x22;Q1&#x22;, amount_eur: 44_000 },
    { category: &#x22;Marketing&#x22;, quarter: &#x22;Q2&#x22;, amount_eur: 38_000 },
    { category: &#x22;Operations&#x22;, quarter: &#x22;Q1&#x22;, amount_eur: 27_000 },
    { category: &#x22;Operations&#x22;, quarter: &#x22;Q2&#x22;, amount_eur: 31_000 },
  ],
  &#x22;spend&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "category.bar",
  "title": "Spend by category",
  "dataset": "spend",
  "x": "category",
  "y": "amount_eur",
  "group": "quarter",
  "format": "eur"
}
```

* `x`: the category field (required); `y`: the numeric field (required).
* `group`: a field that splits each category into a bar per distinct value.
* `stacked`: `true` stacks the grouped bars into one bar per category (default `false`).
* `colors`: CSS colors per series (group value or `y` field), overriding the palette.

## category.combo [#categorycombo]

A dual-axis chart: `bars` on the primary y-axis and `lines` on a secondary one, the island
for reading a level against a rate. Plot revenue against margin, volume against price, or any
two measures whose scales would flatten each other on a shared axis. Both `bars` and `lines`
accept a single field or an array.

<LiveIsland
  type="category.combo"
  config="{
  title: &#x22;Revenue vs. margin&#x22;,
  x: &#x22;month&#x22;,
  bars: &#x22;revenue_eur&#x22;,
  lines: &#x22;margin_pct&#x22;,
  format: &#x22;eur&#x22;,
  lineFormat: &#x22;pct&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;month&#x22;, type: &#x22;date&#x22; },
    { name: &#x22;revenue_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;margin_pct&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { month: &#x22;2026-01-01&#x22;, revenue_eur: 42_300, margin_pct: 0.18 },
    { month: &#x22;2026-02-01&#x22;, revenue_eur: 45_100, margin_pct: 0.21 },
    { month: &#x22;2026-03-01&#x22;, revenue_eur: 44_200, margin_pct: 0.19 },
    { month: &#x22;2026-04-01&#x22;, revenue_eur: 49_800, margin_pct: 0.24 },
    { month: &#x22;2026-05-01&#x22;, revenue_eur: 52_600, margin_pct: 0.26 },
    { month: &#x22;2026-06-01&#x22;, revenue_eur: 56_400, margin_pct: 0.29 },
  ],
  &#x22;monthly&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "category.combo",
  "title": "Revenue vs. margin",
  "dataset": "monthly",
  "x": "month",
  "bars": "revenue_eur",
  "lines": "margin_pct",
  "format": "eur",
  "lineFormat": "pct"
}
```

* `x`: the category or date field (required); accepts a category label or any date the bar
  chart understands.
* `bars`: a numeric field, or an array of fields, drawn as bars on the primary y-axis (required).
* `lines`: a numeric field, or an array of fields, drawn as lines on the secondary y-axis (required).
* `stacked`: `true` stacks the bar series into one bar per category (default `false`).
* `colors`: CSS colors per series (bars first, then lines), overriding the palette.
* `format` / `lineFormat`: [value formats](/reference/value-formats.md) for the bar (primary) and line (secondary) axes.

## waterfall.bars [#waterfallbars]

A waterfall, or bridge, chart: an opening anchor, signed `+`/`−` steps that float on the running
total, and closing anchors. Reach for it to walk a P\&L (revenue down to profit), explain a
variance, or bridge an opening balance to a closing one. Mark each anchor row by setting its
`kind` field to `"total"`; every other row is a signed delta. The running-total math lives in the
renderer, so the dataset stays a flat list of `(step, delta, kind)` rows.

<LiveIsland
  type="waterfall.bars"
  config="{ title: &#x22;Operating profit bridge&#x22;, label: &#x22;step&#x22;, value: &#x22;amount_eur&#x22;, kind: &#x22;kind&#x22;, format: &#x22;eur&#x22; }"
  data="sampleData(
  [
    { name: &#x22;step&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;amount_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;kind&#x22;, type: &#x22;varchar&#x22; },
  ],
  [
    { step: &#x22;Revenue&#x22;, amount_eur: 482_000, kind: &#x22;total&#x22; },
    { step: &#x22;COGS&#x22;, amount_eur: -184_000, kind: &#x22;delta&#x22; },
    { step: &#x22;Opex&#x22;, amount_eur: -141_000, kind: &#x22;delta&#x22; },
    { step: &#x22;Operating profit&#x22;, amount_eur: 157_000, kind: &#x22;total&#x22; },
  ],
  &#x22;pnl_bridge&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "waterfall.bars",
  "title": "Operating profit bridge",
  "dataset": "pnl_bridge",
  "label": "step",
  "value": "amount_eur",
  "kind": "kind",
  "format": "eur"
}
```

* `label`: the step-name field on the x axis (required); `value`: the signed delta per step, or the
  absolute level for a `total` row (required).
* `kind`: a field marking anchor rows — a row whose value here is `"total"` draws as an absolute bar
  from zero (opening, closing, or a subtotal). Omit it to make every row a delta.
* `colors`: per-tone CSS colors overriding the defaults (`increase` green, `decrease` red, `total`
  neutral grey) — e.g. `{ "increase": "#16a34a", "decrease": "#dc2626" }`.
* [`format`](/reference/value-formats.md) styles the y axis and the per-step tooltip, which shows each
  step's signed value.

## divergence.bars [#divergencebars]

A diverging bar chart: one signed bar per category or day, extending above or below a zero baseline
and colored by which value band it falls into. Reach for it to show deviation from a reference — a
daily calorie surplus/deficit, budget variance, net cash flow, or a temperature anomaly. Pick it
over `category.bar` when the values are signed and the up/down direction is the message, and over
`waterfall.bars` when each bar stands on its own rather than accumulating into a running total. The
island reads a pre-computed signed `value`; to diverge from a non-zero target, compute the delta in
SQL.

<LiveIsland
  type="divergence.bars"
  config="{
  title: &#x22;Daily calorie delta&#x22;,
  x: &#x22;day&#x22;,
  value: &#x22;delta&#x22;,
  buckets: [
    { gte: 300, color: &#x22;#16a34a&#x22;, label: &#x22;High surplus&#x22; },
    { gte: 0, lt: 300, color: &#x22;#4ade80&#x22;, label: &#x22;Surplus&#x22; },
    { gte: -500, lt: 0, color: &#x22;#fb923c&#x22;, label: &#x22;Deficit&#x22; },
    { lt: -500, color: &#x22;#ef4444&#x22;, label: &#x22;Large deficit&#x22; },
  ],
}"
  data="sampleData(
  [
    { name: &#x22;day&#x22;, type: &#x22;date&#x22; },
    { name: &#x22;delta&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { day: &#x22;2025-06-16&#x22;, delta: 420 },
    { day: &#x22;2025-06-17&#x22;, delta: -180 },
    { day: &#x22;2025-06-18&#x22;, delta: 90 },
    { day: &#x22;2025-06-19&#x22;, delta: -640 },
    { day: &#x22;2025-06-20&#x22;, delta: 260 },
    { day: &#x22;2025-06-21&#x22;, delta: -120 },
    { day: &#x22;2025-06-22&#x22;, delta: 880 },
    { day: &#x22;2025-06-23&#x22;, delta: -540 },
    { day: &#x22;2025-06-24&#x22;, delta: 60 },
  ],
  &#x22;calorie_delta&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "divergence.bars",
  "title": "Daily calorie delta",
  "dataset": "calorie_delta",
  "x": "day",
  "value": "delta",
  "buckets": [
    { "gte": 300, "color": "#16a34a", "label": "High surplus" },
    { "gte": 0, "lt": 300, "color": "#4ade80", "label": "Surplus" },
    { "gte": -500, "lt": 0, "color": "#fb923c", "label": "Deficit" },
    { "lt": -500, "color": "#ef4444", "label": "Large deficit" }
  ]
}
```

* `x`: the category or date field — one bar per row (required); `value`: the signed numeric field,
  rising above the zero baseline when positive and dropping below it when negative (required). Rows
  with a null or non-numeric `value` are skipped.
* `buckets`: color each bar by the band its value falls in, matched top-to-bottom with half-open
  bounds `[gte, lt)` (omit either bound for an open end). Give every bucket a `label` to show a
  legend; a value matching no band renders neutral grey. Omit `buckets` entirely for a default
  green-positive / red-negative two-tone.
* [`format`](/reference/value-formats.md) styles the y axis and the per-bar tooltip.

## rank.list [#ranklist]

A ranked Top-N list with proportional horizontal bars, the island for a leaderboard: top products,
customers, errors, or movers. Rows are sorted by `value`, cut to `limit`, and each bar is sized
against the largest in the visible set, so the list reads as a ranking at a glance.

<LiveIsland
  type="rank.list"
  config="{
  title: &#x22;Top products by revenue&#x22;,
  label: &#x22;product&#x22;,
  value: &#x22;revenue_eur&#x22;,
  secondary: &#x22;region&#x22;,
  sort: &#x22;descending&#x22;,
  limit: 5,
  format: &#x22;eur&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;product&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;revenue_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;region&#x22;, type: &#x22;varchar&#x22; },
  ],
  [
    { product: &#x22;Aurora Pro&#x22;, revenue_eur: 184_200, region: &#x22;EU&#x22; },
    { product: &#x22;Borealis Cloud&#x22;, revenue_eur: 142_800, region: &#x22;US&#x22; },
    { product: &#x22;Cirrus Edge&#x22;, revenue_eur: 96_400, region: &#x22;APAC&#x22; },
    { product: &#x22;Delta Sync&#x22;, revenue_eur: 61_900, region: &#x22;EU&#x22; },
    { product: &#x22;Echo Stream&#x22;, revenue_eur: 38_500, region: &#x22;US&#x22; },
  ],
  &#x22;product_revenue&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "rank.list",
  "title": "Top products by revenue",
  "dataset": "product_revenue",
  "label": "product",
  "value": "revenue_eur",
  "secondary": "region",
  "sort": "descending",
  "limit": 5,
  "format": "eur"
}
```

* `label`: the field naming each row (required); `value`: the numeric field rows are ranked by (required).
* `sort`: `descending` (the default, largest first) or `ascending` to surface the smallest.
* `limit`: the maximum number of rows shown (default `10`).
* `secondary`: an optional field shown beside each row's value (e.g. a region or owner).
* `color`: a CSS color for the bars, overriding the default accent; [`format`](/reference/value-formats.md)
  styles each row's value.

## breakdown.treemap [#breakdowntreemap]

A treemap of part-to-whole composition, the island for how a total splits across parts,
optionally nested. Each leaf's area is proportional to its `value`; supply `parent` to
group leaves under a hierarchy.

<LiveIsland
  type="breakdown.treemap"
  config="{ title: &#x22;Portfolio allocation&#x22;, label: &#x22;asset&#x22;, value: &#x22;value_eur&#x22;, parent: &#x22;class&#x22; }"
  data="sampleData(
  [
    { name: &#x22;class&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;asset&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;value_eur&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { class: &#x22;Equities&#x22;, asset: &#x22;US stocks&#x22;, value_eur: 48_000 },
    { class: &#x22;Equities&#x22;, asset: &#x22;EU stocks&#x22;, value_eur: 22_000 },
    { class: &#x22;Bonds&#x22;, asset: &#x22;Govt bonds&#x22;, value_eur: 18_000 },
    { class: &#x22;Bonds&#x22;, asset: &#x22;Corp bonds&#x22;, value_eur: 9_000 },
    { class: &#x22;Crypto&#x22;, asset: &#x22;BTC&#x22;, value_eur: 12_000 },
    { class: &#x22;Cash&#x22;, asset: &#x22;Savings&#x22;, value_eur: 6_000 },
  ],
  &#x22;portfolio&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "breakdown.treemap",
  "title": "Portfolio allocation",
  "dataset": "portfolio",
  "label": "asset",
  "value": "value_eur",
  "parent": "class"
}
```

* `label`: the field naming each leaf (required); `value`: the numeric field sizing it
  (required). Only positive values render.
* `parent`: an optional field grouping leaves into a two-level hierarchy; omit it for a
  flat treemap.
* `colors`: CSS colors cycled across the top-level nodes, overriding the palette.

## category.pie [#categorypie]

A pie, or a donut with `donut: true`, of one series' share across a handful of categories.
Slices sum by `label` and sort largest-first; each shows its percentage and the tooltip
carries the formatted value. Reach for a treemap instead when the parts nest or there are
many of them.

<LiveIsland
  type="category.pie"
  config="{ title: &#x22;Revenue by product&#x22;, label: &#x22;category&#x22;, value: &#x22;revenue&#x22;, donut: true, format: &#x22;eur&#x22; }"
  data="sampleData(
  [
    { name: &#x22;category&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;revenue&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { category: &#x22;Enterprise&#x22;, revenue: 482_000 },
    { category: &#x22;Professional&#x22;, revenue: 316_000 },
    { category: &#x22;Starter&#x22;, revenue: 158_000 },
    { category: &#x22;Add-ons&#x22;, revenue: 94_000 },
    { category: &#x22;Support&#x22;, revenue: 61_000 },
    { category: &#x22;Training&#x22;, revenue: 27_000 },
  ],
  &#x22;product_revenue&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "category.pie",
  "title": "Revenue by product",
  "dataset": "product_revenue",
  "label": "category",
  "value": "revenue",
  "donut": true,
  "format": "eur"
}
```

* `label`: the field naming each slice (required); `value`: the numeric field sizing it
  (required). Slices sum by label; only positive values render.
* `donut`: `true` cuts an inner radius (a donut); omit for a full pie.
* `colors`: CSS colors per slice (largest-first), overriding the palette;
  [`format`](/reference/value-formats.md) styles the tooltip values.

## correlation.scatter [#correlationscatter]

A scatter plot of two numeric fields, for exploring how they relate. Split points into
colored series with `series`, and turn dots into bubbles by pointing `size` at a third
field. The `x`/`y` fields (and their `xFormat`/`format`) drive the axes and the tooltip.

<LiveIsland
  type="correlation.scatter"
  config="{
  title: &#x22;Spend vs. conversions by channel&#x22;,
  x: &#x22;marketing_spend_eur&#x22;,
  y: &#x22;conversions&#x22;,
  series: &#x22;channel&#x22;,
  size: &#x22;customers&#x22;,
  label: &#x22;channel&#x22;,
  xFormat: &#x22;eur&#x22;,
  format: &#x22;int&#x22;,
}"
  data="sampleData(
  [
    { name: &#x22;channel&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;marketing_spend_eur&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;conversions&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;customers&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { channel: &#x22;Paid Search&#x22;, marketing_spend_eur: 4200, conversions: 180, customers: 120 },
    { channel: &#x22;Paid Social&#x22;, marketing_spend_eur: 3100, conversions: 140, customers: 95 },
    { channel: &#x22;Display&#x22;, marketing_spend_eur: 1800, conversions: 60, customers: 40 },
    { channel: &#x22;Email&#x22;, marketing_spend_eur: 600, conversions: 75, customers: 70 },
    { channel: &#x22;Affiliate&#x22;, marketing_spend_eur: 2400, conversions: 110, customers: 88 },
    { channel: &#x22;Paid Search&#x22;, marketing_spend_eur: 5600, conversions: 230, customers: 160 },
    { channel: &#x22;Paid Social&#x22;, marketing_spend_eur: 4400, conversions: 175, customers: 130 },
    { channel: &#x22;Display&#x22;, marketing_spend_eur: 2600, conversions: 80, customers: 55 },
    { channel: &#x22;Email&#x22;, marketing_spend_eur: 900, conversions: 95, customers: 90 },
  ],
  &#x22;channel_performance&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "correlation.scatter",
  "title": "Spend vs. conversions by channel",
  "dataset": "channel_performance",
  "x": "marketing_spend_eur",
  "y": "conversions",
  "series": "channel",
  "size": "customers",
  "label": "channel",
  "xFormat": "eur",
  "format": "int"
}
```

* `x`, `y`: the numeric fields for the axes (both required); points with a non-numeric
  `x` or `y` are dropped.
* `series`: splits points into one colored series per distinct value, with a legend.
* `size`: a numeric field scaling each point into a bubble; omit for fixed-size dots.
* `label`: names each point in the tooltip; `colors` overrides the per-series palette;
  `xFormat`/[`format`](/reference/value-formats.md) style the x/y values.

## distribution.heatmap [#distributionheatmap]

A matrix heatmap: one value across two categorical dimensions, each cell shaded on a
continuous color scale. Reach for it to show day-by-hour activity, a cohort grid, or a
correlation matrix. Pre-aggregate to one value per `x`/`y` cell in SQL; the renderer keeps
the last value seen for a repeated cell.

<LiveIsland
  type="distribution.heatmap"
  config="{ title: &#x22;Orders by weekday and hour&#x22;, x: &#x22;hour&#x22;, y: &#x22;weekday&#x22;, value: &#x22;orders&#x22;, format: &#x22;int&#x22; }"
  data="sampleData(
  [
    { name: &#x22;hour&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;weekday&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;orders&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { hour: &#x22;9am&#x22;, weekday: &#x22;Mon&#x22;, orders: 12 },
    { hour: &#x22;12pm&#x22;, weekday: &#x22;Mon&#x22;, orders: 34 },
    { hour: &#x22;3pm&#x22;, weekday: &#x22;Mon&#x22;, orders: 21 },
    { hour: &#x22;6pm&#x22;, weekday: &#x22;Mon&#x22;, orders: 47 },
    { hour: &#x22;9am&#x22;, weekday: &#x22;Tue&#x22;, orders: 9 },
    { hour: &#x22;12pm&#x22;, weekday: &#x22;Tue&#x22;, orders: 41 },
    { hour: &#x22;3pm&#x22;, weekday: &#x22;Tue&#x22;, orders: 28 },
    { hour: &#x22;6pm&#x22;, weekday: &#x22;Tue&#x22;, orders: 53 },
  ],
  &#x22;orders_by_hour&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "distribution.heatmap",
  "title": "Orders by weekday and hour",
  "dataset": "orders_by_hour",
  "x": "hour",
  "y": "weekday",
  "value": "orders",
  "format": "int"
}
```

* `x`, `y`: the column and row category fields (both required); a cell sits at each `x`/`y` pair.
* `value`: the numeric field shading each cell (required); the scale spans its range, and
  cell labels show for small grids.
* `colors`: gradient stops overriding the default blues; [`format`](/reference/value-formats.md)
  styles cells, tooltip, and the legend.

## activity.calendar [#activitycalendar]

A calendar heatmap of a daily value, GitHub-contributions style: each day is a cell shaded
by its value, laid out in weeks across the months the data spans. Any parseable date works,
and rows landing on the same day sum. Give it a half-width or wider tile.

<LiveIsland
  type="activity.calendar"
  config="{ title: &#x22;Commits per day&#x22;, date: &#x22;day&#x22;, value: &#x22;commits&#x22;, format: &#x22;int&#x22; }"
  data="sampleData(
  [
    { name: &#x22;day&#x22;, type: &#x22;date&#x22; },
    { name: &#x22;commits&#x22;, type: &#x22;int&#x22; },
  ],
  [
    { day: &#x22;2026-04-02&#x22;, commits: 4 },
    { day: &#x22;2026-04-03&#x22;, commits: 7 },
    { day: &#x22;2026-04-06&#x22;, commits: 2 },
    { day: &#x22;2026-04-09&#x22;, commits: 11 },
    { day: &#x22;2026-04-14&#x22;, commits: 5 },
    { day: &#x22;2026-04-20&#x22;, commits: 9 },
    { day: &#x22;2026-04-27&#x22;, commits: 3 },
    { day: &#x22;2026-05-04&#x22;, commits: 6 },
    { day: &#x22;2026-05-08&#x22;, commits: 13 },
    { day: &#x22;2026-05-12&#x22;, commits: 1 },
    { day: &#x22;2026-05-19&#x22;, commits: 8 },
    { day: &#x22;2026-05-26&#x22;, commits: 4 },
    { day: &#x22;2026-06-01&#x22;, commits: 10 },
    { day: &#x22;2026-06-05&#x22;, commits: 2 },
    { day: &#x22;2026-06-09&#x22;, commits: 7 },
  ],
  &#x22;commits&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "activity.calendar",
  "title": "Commits per day",
  "dataset": "commits",
  "date": "day",
  "value": "commits",
  "format": "int"
}
```

* `date`: the day field; any parseable date or timestamp, bucketed to a UTC day (required).
* `value`: a numeric field mapped to each day's color; same-day rows sum (required).
* `colors`: gradient stops overriding the default blues; [`format`](/reference/value-formats.md)
  styles the tooltip value.

## funnel.steps [#funnelsteps]

A funnel of sequential stages, for conversion or drop-off. Each stage's width is its share
of the largest, so the narrowing reads as the fall-off between steps. By default the stages
follow the row order; set `sort` to reorder them.

<LiveIsland
  type="funnel.steps"
  config="{ title: &#x22;Signup conversion&#x22;, label: &#x22;stage&#x22;, value: &#x22;users&#x22;, sort: &#x22;descending&#x22;, format: &#x22;int&#x22; }"
  data="sampleData(
  [
    { name: &#x22;stage&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;users&#x22;, type: &#x22;bigint&#x22; },
  ],
  [
    { stage: &#x22;Visitors&#x22;, users: 48_200 },
    { stage: &#x22;Signups&#x22;, users: 9_400 },
    { stage: &#x22;Activated&#x22;, users: 5_100 },
    { stage: &#x22;Trials&#x22;, users: 2_300 },
    { stage: &#x22;Paid&#x22;, users: 870 },
  ],
  &#x22;signup_funnel&#x22;,
)"
/>

```jsonc title="manifest.json"
{
  "type": "funnel.steps",
  "title": "Signup conversion",
  "dataset": "signup_funnel",
  "label": "stage",
  "value": "users",
  "sort": "descending",
  "format": "int"
}
```

* `label`: the stage-name field (required); `value`: the numeric count at each stage (required).
* `sort`: `none` (the default, keeping row order), `descending`, or `ascending`.
* `colors`: per-stage CSS colors overriding the palette; [`format`](/reference/value-formats.md)
  styles the labels and tooltip.

## compare.radar [#compareradar]

A radar (spider) chart for comparing entities across several metrics at once. Each field in
`metrics` becomes an axis, and each row becomes a polygon named by `series`. Share one `max`
across every axis when the metrics are on a common scale (e.g. all 0-100 scores).

<LiveIsland
  type="compare.radar"
  config="{ title: &#x22;Product comparison&#x22;, metrics: [&#x22;performance&#x22;, &#x22;price&#x22;, &#x22;design&#x22;, &#x22;support&#x22;, &#x22;ecosystem&#x22;], series: &#x22;product&#x22;, max: 100 }"
  data="sampleData(
  [
    { name: &#x22;product&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;performance&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;price&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;design&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;support&#x22;, type: &#x22;double&#x22; },
    { name: &#x22;ecosystem&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { product: &#x22;Aurora&#x22;, performance: 88, price: 62, design: 90, support: 75, ecosystem: 70 },
    { product: &#x22;Borealis&#x22;, performance: 74, price: 85, design: 68, support: 82, ecosystem: 88 },
    { product: &#x22;Cirrus&#x22;, performance: 80, price: 78, design: 84, support: 70, ecosystem: 76 },
  ],
  &#x22;product_scores&#x22;,
)"
  height="300"
/>

```jsonc title="manifest.json"
{
  "type": "compare.radar",
  "title": "Product comparison",
  "dataset": "product_scores",
  "metrics": ["performance", "price", "design", "support", "ecosystem"],
  "series": "product",
  "max": 100
}
```

* `metrics`: the numeric fields that become the axes (at least one, required).
* `series`: a field naming each row's polygon; omitted, polygons are numbered Series 1, 2, …
* `max`: a fixed max shared by every axis; omitted, each axis maxes at its own metric's peak.
* `colors`: per-polygon CSS colors overriding the palette; [`format`](/reference/value-formats.md)
  styles the tooltip values.

## map.choropleth [#mapchoropleth]

A geographic choropleth: regions shaded by a value on a continuous scale. The map ships as
vendored GeoJSON, so it renders fully offline, with no tiles and no network. Each row's
`region` must match the map's region name; for the built-in `world` map that is the English
country name.

<LiveIsland
  type="map.choropleth"
  config="{ title: &#x22;Revenue by country&#x22;, region: &#x22;country&#x22;, value: &#x22;revenue_eur&#x22;, format: &#x22;eur&#x22; }"
  data="sampleData(
  [
    { name: &#x22;country&#x22;, type: &#x22;varchar&#x22; },
    { name: &#x22;revenue_eur&#x22;, type: &#x22;double&#x22; },
  ],
  [
    { country: &#x22;United States of America&#x22;, revenue_eur: 482_000 },
    { country: &#x22;Germany&#x22;, revenue_eur: 213_000 },
    { country: &#x22;France&#x22;, revenue_eur: 168_000 },
    { country: &#x22;United Kingdom&#x22;, revenue_eur: 154_000 },
    { country: &#x22;Japan&#x22;, revenue_eur: 121_000 },
    { country: &#x22;Brazil&#x22;, revenue_eur: 73_000 },
    { country: &#x22;India&#x22;, revenue_eur: 61_000 },
    { country: &#x22;Australia&#x22;, revenue_eur: 44_000 },
  ],
  &#x22;revenue_by_country&#x22;,
)"
  height="320"
/>

```jsonc title="manifest.json"
{
  "type": "map.choropleth",
  "title": "Revenue by country",
  "dataset": "revenue_by_country",
  "region": "country",
  "value": "revenue_eur",
  "format": "eur"
}
```

* `region`: the field holding each row's region name, matching the map's names (required).
* `value`: the numeric field shading each region (required); regions with no row stay neutral.
* `map`: the registered map name (default `world`); `colors` overrides the gradient;
  [`format`](/reference/value-formats.md) styles the tooltip and legend.

<Callout type="info" title="Tip">
  Region names must match the map exactly. The `world` map uses full English country names:
  `United States of America` (not "United States" or "USA"), `United Kingdom`, `Russia`, and
  so on. Normalize your data's names in SQL to match.
</Callout>


---

*This is one page of the OpenIslands docs. Every page in one file: [/llms-full.txt](/llms-full.txt). Page index: [/llms.txt](/llms.txt). Links above point to `.md` siblings — append `.md` to any page URL for its raw markdown.*
