Annotations¶
An annotation is your statement about the data, drawn on a chart: a target line, a highlighted period, a labeled peak, a fitted trend. It is anchored in data coordinates, so it stays attached to the data when the query changes.
bar, line, area, and scatter charts accept an annotations: list. Each entry has an explicit type (rule, band, note, or trend), an anchor on the x or y axis, optional text, and an optional style: that works like a layer's.
Minimum Required for an Annotation¶
An annotation needs a type and what that type draws against.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
type |
rule, band, note, trend |
Selects the annotation. |
x and/or y |
Anchor | Required for rule and band. A note takes both or neither. |
fit |
Fit function | Required for trend, in place of an anchor. |
Minimum Example¶
queries:
monthly_revenue:
columns: [month, revenue]
values:
- ["2026-01-01", 18.4]
- ["2026-02-01", 20.1]
- ["2026-03-01", 19.2]
- ["2026-04-01", 23.6]
- ["2026-05-01", 24.8]
- ["2026-06-01", 27.3]
charts:
revenue_vs_goal:
query: monthly_revenue
type: line
x: month
y: revenue
annotations:
- type: rule
y: 25
text: FY goal
rows:
- revenue_vs_goal
Common Fields¶
Every annotation type accepts these.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
type |
rule, band, note, trend |
Unknown types are an error. |
text |
One line of text | Optional. A template: {{ value \| format }} prints the anchor's value in the axis format, {{ series }} the series name. |
plane |
background, foreground |
See Plane. |
series |
each, total, or a list of series names |
See Series. |
y_column |
A plotted y column | See Which y Column. |
style |
marks: patch |
See Styling. |
List order is draw order within a plane. annotations: is not accepted on pie, donut, map, table, or KPI charts.
Rule¶
A rule is a line across the plot at one value, on either axis. A range on the other axis limits it to a segment.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
x |
Anchor | Vertical rule. |
y |
Anchor | Horizontal rule. |
style.marks.rule |
opacity, stroke |
The line. |
style.marks.text |
Text style | The label. |
Rule Examples¶
A fixed target, a dated event, a segment, and a computed average.
queries:
monthly_revenue:
columns: [month, revenue]
values:
- ["2026-01-01", 18.4]
- ["2026-02-01", 20.1]
- ["2026-03-01", 19.2]
- ["2026-04-01", 23.6]
- ["2026-05-01", 24.8]
- ["2026-06-01", 27.3]
- ["2026-07-01", 26.1]
- ["2026-08-01", 28.9]
charts:
revenue_rules:
query: monthly_revenue
type: line
x: month
y: revenue
annotations:
- type: rule
y: 25
text: FY goal
- type: rule
x: 2026-04-01
text: Pricing change
- type: rule
y: 22
x:
from: 2026-05-01
to: max()
text: Q2 floor
- type: rule
y: avg()
text: "Avg {{ value | format }}"
rows:
- revenue_rules
Band¶
A band is a shaded region over a range, or over one category on a discrete axis. Ranges on both axes make a box.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
x |
Anchor | Range, or a category on a discrete axis. |
y |
Anchor | Range. |
style.marks.rect |
opacity, fill, stroke |
The shaded region. |
style.marks.text |
Text style | The label. |
A missing from or to extends to the axis edge. Ranges are half-open, [from, to): from is included and to is excluded. On a date axis drawn as buckets (bars), a range covers the buckets that start in [from, to).
Band Examples¶
A period on a time axis, and an open-ended threshold on the value axis.
queries:
monthly_orders:
columns: [month, orders]
values:
- ["2026-01-01", 120]
- ["2026-02-01", 132]
- ["2026-03-01", 128]
- ["2026-04-01", 150]
- ["2026-05-01", 171]
- ["2026-06-01", 165]
- ["2026-07-01", 182]
- ["2026-08-01", 190]
charts:
orders_bands:
query: monthly_orders
type: line
x: month
y: orders
annotations:
- type: band
x:
from: 2026-04-01
to: 2026-07-01
text: Summer promo
- type: band
y:
from: 180
text: Capacity
style:
marks:
text:
position:
edge: top
align: start
rows:
- orders_bands
Category Band¶
On a discrete axis, a single value fills that category's slot.
queries:
regions:
columns: [region, bookings]
values:
- [East, 14.2]
- [West, 17.8]
- [North, 9.6]
- [South, 12.4]
charts:
bookings_by_region:
query: regions
type: bar
x: region
y: bookings
style:
orientation: vertical
annotations:
- type: band
x: East
text: Pilot
rows:
- bookings_by_region
Note¶
A note is text at a point, or pinned to the plot frame when it has no anchor. A connector joins the text to its anchor; an enclosure circles or boxes the anchor.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
x, y |
A single value, min(), or max() |
Both, or neither. A range is an error. With min() or max() on one axis and the other omitted, the note sits at the row holding that extreme. |
connector |
line, arrow |
Draws from the anchor to text moved with dx/dy. Needs an anchor. |
enclose |
circle, rect |
Shape around the anchor. Needs an anchor. |
style.marks.text |
Text style plus max_width |
max_width wraps a note with no anchor: pixels, or auto. A note at a point does not wrap. |
style.marks.line |
opacity, curve, length, stroke |
The connector. length is its default distance from the anchor. |
style.marks.circle |
opacity, radius, stroke |
The circle enclosure. |
style.marks.rect |
opacity, fill, padding, stroke |
The rect enclosure. |
A note boxed with enclose: rect is centered on its anchor, so style.marks.text.position does not apply to it unless there is a connector.
Note Examples¶
A callout with a connector, a circled point derived from the data, and a frame note.
queries:
monthly_revenue:
columns: [month, revenue]
values:
- ["2026-01-01", 18.4]
- ["2026-02-01", 20.1]
- ["2026-03-01", 19.2]
- ["2026-04-01", 23.6]
- ["2026-05-01", 24.8]
- ["2026-06-01", 27.3]
- ["2026-07-01", 26.1]
- ["2026-08-01", 28.9]
charts:
revenue_notes:
query: monthly_revenue
type: line
x: month
y: revenue
annotations:
- type: note
x: 2026-03-01
y: 19.2
text: Slow month
connector: arrow
style:
marks:
text:
dx: 50
dy: 60
- type: note
y: max()
text: Record high
enclose: circle
- type: note
text: Revenue in $M
style:
marks:
text:
position:
edge: top
align: start
overlay: true
rows:
- revenue_notes
Trend¶
A trend is a parametric function fitted to the plotted rows and drawn across the x range, with an optional interval. A rolling average, loess, or cumulative series is a new series: derive it in the query and plot it as a layer.
| dbt Charts field | Allowed values | Notes |
|---|---|---|
fit |
linear(), polynomial(<order>), exponential(), log(), power() |
Required. polynomial takes an order of 2 or more. A categorical x cannot be fitted. |
interval |
confidence(<pct>), prediction(<pct>) |
Band around the fit. pct is between 0 and 100, exclusive. |
x |
Range | Limits or extends the drawn span; defaults to the data extent. |
style.marks.line |
opacity, curve, stroke |
The fitted curve. |
style.marks.area |
opacity, fill |
The interval. |
style.marks.text |
Text style plus max_width |
The label, wrapped to max_width. |
On date bars a trend spans whole bars: from the outer edge of the first to the outer edge of the last.
A fit answers "what is the pattern", so it belongs on a line or scatter mark. The mark that draws the trend's column decides:
| Mark | Result |
|---|---|
| Line, scatter | Fits. |
Bar, or the series: total of a stacked bar |
Fits, with WARN-TREND-ON-BARS: a bar reads as length from zero, so a line chart is the better home. To keep the bars, thin them with style.marks.bar.band_width so more of the trend shows. |
| Area, stacked or not | Error. Use layers: to draw the column as a line or scatter, then point y_column at it. |
The chart's type: does not decide: an area chart with a scatter layer takes a trend on the scatter's column.
Trend Examples¶
A linear fit with a 95% confidence band, and a curved fit.
queries:
accounts:
columns: [seats, arr]
values:
- [10, 6.2]
- [30, 9.8]
- [50, 14.9]
- [70, 22.4]
- [90, 30.1]
- [110, 43.5]
- [130, 58.0]
- [150, 75.2]
- [170, 96.3]
- [190, 121.7]
charts:
arr_by_seats:
query: accounts
type: scatter
x: seats
y: arr
annotations:
- type: trend
fit: linear()
interval: confidence(95)
text: Linear fit
- type: trend
fit: polynomial(2)
text: Quadratic fit
rows:
- arr_by_seats
Anchors¶
An anchor is where an annotation sits on an axis. Literals are read in the axis column's type: a number, a date, or a category.
| Form | Example | Meaning |
|---|---|---|
| Literal | y: 25 |
A value in the axis column's type. |
| Range | x: with from: and to: |
Either bound may be omitted: it extends to the axis edge. A rule or trend range limits the segment it draws. |
| Category | x: East |
On a discrete axis: a band fills the category's slot; a rule or note sits at its center. A category the column does not hold is an error that lists the categories. |
| Aggregate | y: avg() |
Computed over the plotted rows. |
| Clock | x: now(), x: current(month) |
Resolved at render time. |
Range bounds take any literal, aggregate, or clock form. Anchor values and range bounds widen the axis domain as data does; pin axis_y.scale.domain to clip.
Functions¶
A closed set, parsed when the board compiles. No nesting, no arithmetic, no column arguments.
| Where | Functions |
|---|---|
| Anchors and range bounds | min(), max(), avg(), median(), percentile(<0-100>) |
| Clock anchors and bounds | now(), current(day \| week \| month \| quarter \| year) |
fit |
linear(), polynomial(<order>), exponential(), log(), power() |
interval |
confidence(<pct>), prediction(<pct>) |
- Aggregates run over the plotted rows of the axis column:
avg()on monthly revenue is the average month, not the average transaction. - Aggregates are an error on a categorical axis. On a time axis only
min()andmax()are valid. now()andcurrent()need a date or timestamp x.current(<unit>)is a period: use it on a band or as a range bound; it expands to that period's[from, to).
Series¶
On a chart with a color channel, a computed annotation (an aggregate or a trend) must say which series it is computed for.
series |
Meaning | Color |
|---|---|---|
each |
One annotation per series. | The series color. |
| List of names | Like each, for only the named series. An unknown name is an error. |
The series color. |
total |
Once, over all rows pooled; on a stacked chart, over the per-x sum. | Theme colors. |
series is an error on an annotation whose anchors are all literal. On a stacked chart only total is valid. On a single-series chart it may be omitted. With small multiples, computed annotations are computed per panel.
queries:
by_segment:
columns: [month, segment, revenue]
values:
- ["2026-01-01", SMB, 6.1]
- ["2026-02-01", SMB, 5.4]
- ["2026-03-01", SMB, 6.6]
- ["2026-04-01", SMB, 7.2]
- ["2026-01-01", Enterprise, 10.2]
- ["2026-02-01", Enterprise, 11.0]
- ["2026-03-01", Enterprise, 12.4]
- ["2026-04-01", Enterprise, 13.1]
charts:
segment_averages:
query: by_segment
type: line
x: month
y: revenue
color: segment
annotations:
- type: rule
y: avg()
series: each
text: "{{ series }} avg"
style:
marks:
text:
position:
edge: bottom
align: end
rows:
- segment_averages
Which y Column¶
y_column names the y column an annotation refers to. It picks the y axis that plots the column, and the rows a computed anchor or trend reads.
It is optional with a literal y anchor (the base axis when omitted) and with a computed anchor on a chart that has one y column and no layers:. It is required for a computed y anchor, a trend, or a note derived with min()/max() on a chart with several y columns or with layers:. It is an error on an annotation with no y anchor, and when the column is plotted twice from different queries or axes.
queries:
subscriptions:
columns: [month, revenue, churn_rate]
values:
- ["2026-01-01", 42, 5.1]
- ["2026-02-01", 46, 4.7]
- ["2026-03-01", 49, 4.4]
- ["2026-04-01", 53, 4.9]
- ["2026-05-01", 58, 4.1]
charts:
revenue_and_churn:
query: subscriptions
type: bar
x: month
y: revenue
layers:
- type: line
y: churn_rate
axis_y:
position: right
annotations:
- type: rule
y: 5
y_column: churn_rate
text: Churn limit
- type: rule
y: avg()
y_column: revenue
text: Avg revenue
style:
marks:
text:
position:
edge: top
align: start
rows:
- revenue_and_churn
Plane¶
plane sets whether the annotation draws behind or in front of the data.
| Value | Draws |
|---|---|
background |
Above the gridlines, below all data marks and layers. |
foreground |
Above all data marks and layers. |
Defaults: bands are background; rules, trends, and notes are foreground on every chart type, so a bar that reaches a rule never covers it.
Positioning¶
style.marks.text.position places an annotation's text against the geometry its type draws.
| Key | Values | Meaning |
|---|---|---|
edge |
top, bottom, left, right; auto on a rule, band or note |
Which side of the geometry. |
align |
start, center, end; auto on a rule, band or note |
Where along that side: left to right on top and bottom, top to bottom on left and right. |
overlay |
true, false |
true: on the line, inside the rect or frame. false: outside, which can be the chart margin. |
dx, dy |
Pixels | A nudge after placement; on a note with a connector, the connector's length. |
| Type | Geometry | Default |
|---|---|---|
rule |
The line. | auto, auto, outside: above the right end. |
band |
The region. | auto: top and center inside an x band; right and center inside a y band. |
note |
The point, or the enclosure. | auto, auto, outside: past the value and centered, above the point on a vertical chart and right of it on a horizontal one. |
note with no anchor |
The plot frame. | bottom, end, overlay. |
trend |
The end of the line. | right, center, outside. |
Text moves off the chart's bars, points, lines, value labels and other annotation text. An auto edge or align is where the text starts, and placement may change it to another side or spot along the side; an explicit one stays fixed. Either way the text may slide along its edge, clear of what it moves off. A note that leaves its side without a connector warns. Rule, band and trend lines never move.
Text that sits in the margin reserves room for itself. A chart whose layout cannot grow its margins fails with ERR-ANNOTATION-MARGIN-UNSUPPORTED; set overlay: true or move the text inside the plot.
Styling¶
style.marks.<mark> on an annotation opens the mark style for each mark its type draws, as on a layer. A mark style the annotation does not draw is accepted and has no effect.
| Type | style.marks keys |
|---|---|
rule |
rule, text |
band |
rect, text |
note |
text, line, circle, rect |
trend |
line, area, text |
The look of every annotation of a type comes from the theme slot style.annotations.<type>.marks, a sibling of style.charts. Set it on the board to restyle every chart, or in a chart's own style.annotations.<type> for that chart. An annotation's own style: wins over both. Leaves left unset fall back to style.charts.marks.
For series: each or a list, the series color fills every color the annotation does not set. A color you set always wins.
A rule's line and its label share one color. The light themes draw it in rung 7 of the gray scaffold; paper and stark keep their own rule ink. Neon sets the stroke color to auto: each rule takes the quietest gray that holds 3:1 against the canvas and the bars or fills it crosses, kept clear of the gridlines. Setting style.marks.rule.stroke.color on a rule, or scoping it to a series, replaces the pick, and the label follows.
queries:
monthly_revenue:
columns: [month, revenue]
values:
- ["2026-01-01", 18.4]
- ["2026-02-01", 20.1]
- ["2026-03-01", 19.2]
- ["2026-04-01", 23.6]
- ["2026-05-01", 24.8]
- ["2026-06-01", 27.3]
style:
annotations:
rule:
marks:
rule:
stroke:
color: category[3]
width: 2
dasharray: "6 3"
text:
font:
color: category[3]
weight: bold
charts:
themed_goal:
query: monthly_revenue
type: line
x: month
y: revenue
annotations:
- type: rule
y: 25
text: FY goal
rows:
- themed_goal
Text Style¶
style.marks.text is a text mark style plus these fields.
| Field | Allowed values | Notes |
|---|---|---|
font |
color, size, weight, style, line_height, family |
The label's type. On a rule, color: line (the default) is the rule's line color. |
halo |
Pixels or auto |
Canvas-colored outline behind the text; 0 draws none. auto (the default) draws 3px only on text over the chart's own bars, points or area fill; a layers: mark doesn't count. |
halo_opacity |
0 to 1 | Opacity of the halo as a whole. |
box |
visible, fill, opacity, padding, radius, stroke |
A box behind the text; visible: true draws it. |
position |
edge, align, overlay |
See Positioning. |
dx, dy |
Pixels | Nudge after placement. |
max_width |
Pixels or auto |
Notes with no anchor and trends only. |
A stroke's dasharray is auto (sized to the width) or dash and gap lengths such as "6 3".
Errors and Warnings¶
An annotation that cannot be placed on its chart fails the chart rather than drawing something wrong.
| Code | When |
|---|---|
ERR-ANNOTATION-INVALID |
The annotation does not fit the chart: a trend that reads an area mark, a value that does not parse in the axis column's type, an aggregate or fit the axis cannot take, a missing or unknown series, from past to, an unknown category, a missing or unknown y_column, or text that does not render. |
ERR-ANNOTATION-UNSUPPORTED |
The chart's layout cannot draw it yet: a bar chart on a continuous time axis, or a number anchor on a bar chart's category axis. |
ERR-ANNOTATION-MARGIN-UNSUPPORTED |
Text falls outside the plot on a chart with fixed margins. |
WARN-ANNOTATION-NOTE-MOVED |
A note's auto text moved to another side of its point to clear the chart, and it has no connector. |
WARN-TREND-ON-BARS |
A trend reads a bar mark. It still draws. |
WARN-ANNOTATION-NOTHING-DRAWN |
The annotation resolves but has nothing to draw: it falls outside the plot, names no slot on the axis, has no plotted rows in its scope, or a trend has too few rows. The rest of the chart renders. |
Also an error: a key the type does not take, a note with only one of x and y, connector or enclose on a note with no anchor, an unknown function, nesting, or arithmetic, and now() or current() on a non-date x.