Skip to main content

Annotated Time Series

A line chart with labeled event markers, so viewers can connect metric movements to campaigns, releases, and incidents.

  • Good for: annotating a single metric's trend with releases, campaigns, or incidents; explaining a spike or dip by the event that caused it; sharing a timeline where context matters as much as the numbers.
  • Not great for: comparing many series at once (use a multi-line or faceted sparkline chart), categorical data with no date axis, or trends that have no notable events to mark.

Syntax

Use the following AML definition to add the Annotated Time Series to your custom chart library.

CustomChartDef annotated_time_series {
label: 'Annotated Time Series'
description: 'To plot a metric over time with labeled event markers tied to specific dates.'
fields {
field date {
label: 'Date'
type: 'dimension'
data_type: 'date'
sort {
apply_order: 1
direction: 'asc'
}
}
field value {
label: 'Value'
type: 'measure'
sort {
apply_order: 2
direction: 'desc'
}
}
field event_label {
label: 'Event Label'
type: 'dimension'
sort {
apply_order: 3
direction: 'asc'
}
}
}
options {
option line_color {
type: 'color-picker'
label: 'Line Color'
default_value: '#255dd4'
}
option event_color {
type: 'color-picker'
label: 'Event Marker Color'
default_value: '#e5484d'
}
}
template: @vgl
{
"data": {"values": @{values}},
"layer": [
{
"transform": [
{"aggregate": [{"op": "sum", "field": @{fields.value.name}, "as": @{fields.value.name}}], "groupby": [@{fields.date.name}]}
],
"mark": {"type": "line", "tooltip": true, "color": @{options.line_color.value}},
"params": [
{"name": "normalPointSelection", "select": {"type": "point", "toggle": "true", "clear": "mouseup"}}
],
"encoding": {
"x": {
"field": @{fields.date.name},
"type": "temporal"
},
"y": {
"field": @{fields.value.name},
"type": "quantitative",
"axis": {
"format": @{fields.value.format},
"formatType": "holisticsFormat"
}
},
"tooltip": [
{"field": @{fields.date.name}, "type": "temporal", "title": "Date"},
{"field": @{fields.value.name}, "type": "quantitative", "title": "Value", "format": @{fields.value.format}, "formatType": "holisticsFormat"}
]
}
},
{
"transform": [
{"filter": "datum['@{fields.event_label.name}'] != null && datum['@{fields.event_label.name}'] != ''"}
],
"layer": [
{
"mark": {"type": "rule", "strokeDash": [4, 4], "color": @{options.event_color.value}},
"encoding": {
"x": {"field": @{fields.date.name}, "type": "temporal"},
"tooltip": [
{"field": @{fields.event_label.name}, "type": "nominal", "title": "Event"},
{"field": @{fields.date.name}, "type": "temporal", "title": "Date"}
]
}
},
{
"mark": {
"type": "text",
"angle": 270,
"align": "left",
"baseline": "bottom",
"dx": 4,
"color": @{options.event_color.value}
},
"encoding": {
"x": {"field": @{fields.date.name}, "type": "temporal"},
"text": {"field": @{fields.event_label.name}}
}
}
]
}
],
"holisticsConfig": {
"crossFilterSignals": ["normalPointSelection"],
"contextMenuSignals": ["normalPointSelection"]
},
"config": {
"background": null,
"view": {"stroke": null},
"font": "Inter",
"axis": {
"title": null,
"ticks": false,
"labelPadding": 10,
"labelFontSize": 11,
"labelColor": "#858B9E",
"titleColor": "#858B9E",
"labelOverlap": "parity",
"gridDash": [8, 3],
"gridColor": "#F4F6F8",
"domainColor": "#bec1cb"
},
"axisX": {"grid": false, "labelAngle": 0},
"axisY": {"domain": false, "grid": true},
"legend": {"orient": "bottom", "title": null, "labelFontSize": 11, "labelColor": "#858B9E", "symbolStrokeWidth": 0}
}
}
;;
}

Required fields

An Annotated Time Series expects exactly three fields. The template draws the line from date and value; event_label adds the dashed markers and labels.

FieldLabelTypeRole
dateDatedimensionTime axis (x). Sorted ascending (apply_order: 1).
valueValuemeasureLine height (y). Sorted descending (apply_order: 2).
event_labelEvent LabeldimensionEvent name shown as a vertical marker. Sorted ascending (apply_order: 3).

Data requirements: Pre-aggregate to one row per date; the template plots value directly without summing, so duplicate dates draw a jagged or overlapping line. Populate event_label only on the dates that have an event and leave it null or empty otherwise (the template skips rows where it is null or '' for markers but still draws them on the line).

Sample data:

datevalueevent_label
2024-01-014200
2024-01-024350Product launch
2024-01-035100
2024-01-044980
2024-01-056200Press feature
2024-01-065800

Options

Set these options to adjust the chart without editing the Vega template. The CustomChartDef block above declares each option's type and allowed values.

OptionDefaultEffect
line_color#255dd4Color of the metric line.
event_color#e5484dColor of the event marker rules and their labels.

Known limitations

  • Event markers depend on a sparse label column. The label field must be null or empty on non-event dates. A value on every row draws a marker on every date and clutters the chart.

  • No second metric. The template plots one value series. Comparing several metrics needs a different chart or a template edit.

  • Dense event labels overlap. Labels render vertically at each event date, so many events close together collide. Keep events spaced or filter to the notable ones.


Open Markdown
Let us know what you think about this document :)