Overview
Annotations allow you to add visual elements like lines, shapes, and labels to your charts. amCharts 5 provides drawing series that enable users to draw annotations directly on the chart.
Advanced drawing tools are primarily available in the Stock Chart package. For basic XY charts, you can create custom interactive elements using bullets and event handlers.
Drawing Series
Drawing series extend the standard series classes and allow users to create annotations interactively.
Basic Drawing Setup
Create a basic drawing series:
import * as am5 from "@amcharts/amcharts5";
import * as am5xy from "@amcharts/amcharts5/xy";
// Create a line series for drawing
const drawingSeries = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Drawing",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date"
})
);
Interactive Drawing Points
Add draggable bullets for interactive drawing:
const drawingSeries = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Drawing",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date"
})
);
// Invisible draggable bullet
drawingSeries.bullets.push(function() {
const bulletCircle = am5.Circle.new(root, {
radius: 6,
fillOpacity: 0,
fill: drawingSeries.get("fill"),
draggable: true,
cursorOverStyle: "pointer"
});
bulletCircle.events.on("dragged", function(e) {
handleDrag(e);
});
return am5.Bullet.new(root, {
sprite: bulletCircle
});
});
// Visible bullet
drawingSeries.bullets.push(function() {
const bulletCircle = am5.Circle.new(root, {
radius: 5,
fill: drawingSeries.get("fill")
});
return am5.Bullet.new(root, {
sprite: bulletCircle
});
});
// Drag handler
function handleDrag(e) {
const point = chart.plotContainer.toLocal(e.point);
const date = xAxis.positionToValue(xAxis.coordinateToPosition(point.x));
const value = yAxis.positionToValue(yAxis.coordinateToPosition(point.y));
const dataItem = e.target.dataItem;
dataItem.set("valueX", date);
dataItem.set("valueXWorking", date);
dataItem.set("valueY", value);
dataItem.set("valueYWorking", value);
}
Use two bullets - one invisible and draggable for interaction, and one visible for display. This prevents visual flickering during drag operations.
Adding Drawing Points
Add points to the drawing series on click:
const drawingData = [];
chart.plotContainer.events.on("click", function(e) {
const point = chart.plotContainer.toLocal(e.point);
const date = xAxis.positionToValue(xAxis.coordinateToPosition(point.x));
const value = yAxis.positionToValue(yAxis.coordinateToPosition(point.y));
drawingData.push({
date: date,
value: value
});
drawingSeries.data.setAll(drawingData);
});
Axis Ranges for Annotations
Use axis ranges to add static annotations:
// Add a horizontal line at a specific Y value
const rangeDataItem = yAxis.makeDataItem({
value: 100
});
const range = yAxis.createAxisRange(rangeDataItem);
rangeDataItem.get("grid").setAll({
stroke: am5.color(0xff0000),
strokeOpacity: 1,
strokeWidth: 2,
strokeDasharray: [5, 5]
});
rangeDataItem.get("label").setAll({
text: "Target: 100",
fill: am5.color(0xff0000),
background: am5.RoundedRectangle.new(root, {
fill: am5.color(0xffffff),
fillOpacity: 0.9
})
});
Vertical Line Annotation
Add a vertical line at a specific date:
const rangeDataItem = xAxis.makeDataItem({
value: new Date(2024, 0, 15).getTime()
});
const range = xAxis.createAxisRange(rangeDataItem);
rangeDataItem.get("grid").setAll({
stroke: am5.color(0x00ff00),
strokeOpacity: 1,
strokeWidth: 2
});
rangeDataItem.get("label").setAll({
text: "Important Event",
location: 0,
fill: am5.color(0x00ff00)
});
Range Annotations
Highlight a range on the chart:
const rangeDataItem = xAxis.makeDataItem({
value: new Date(2024, 0, 1).getTime(),
endValue: new Date(2024, 0, 31).getTime()
});
const range = xAxis.createAxisRange(rangeDataItem);
rangeDataItem.get("axisFill").setAll({
fill: am5.color(0x3b82f6),
fillOpacity: 0.2,
visible: true
});
rangeDataItem.get("label").setAll({
text: "Q1 2024",
location: 0.5,
inside: true
});
Set location: 0.5 to position the label in the middle of the range.
Series Axis Ranges
Apply different styling to portions of a series:
const series = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Series",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date"
})
);
// Create a range for the series
const rangeDataItem = xAxis.makeDataItem({
value: new Date(2024, 0, 1).getTime(),
endValue: new Date(2024, 2, 1).getTime()
});
const seriesRange = series.createAxisRange(rangeDataItem);
// Style the line within the range
seriesRange.strokes.template.setAll({
stroke: am5.color(0xff0000),
strokeWidth: 3
});
seriesRange.fills.template.setAll({
fill: am5.color(0xff0000),
fillOpacity: 0.2,
visible: true
});
Custom Shapes and Labels
Add custom shapes using bullets:
series.bullets.push(function(root, series, dataItem) {
// Only add bullet to specific data points
if (dataItem.get("valueX") === new Date(2024, 0, 15).getTime()) {
const container = am5.Container.new(root, {});
// Add a circle
container.children.push(
am5.Circle.new(root, {
radius: 10,
fill: am5.color(0xff0000),
stroke: am5.color(0xffffff),
strokeWidth: 2
})
);
// Add a label
container.children.push(
am5.Label.new(root, {
text: "Event",
centerY: am5.p50,
centerX: am5.p50,
populateText: true,
dy: -20,
background: am5.RoundedRectangle.new(root, {
fill: am5.color(0xffffff),
fillOpacity: 0.9
})
})
);
return am5.Bullet.new(root, {
sprite: container
});
}
});
For advanced drawing capabilities, use the Stock Chart package:
import * as am5stock from "@amcharts/amcharts5/stock";
// Drawing series types available in Stock Chart:
// - SimpleLineSeries
// - TrendLineSeries
// - HorizontalLineSeries
// - VerticalLineSeries
// - FibonacciSeries
// - RegressionSeries
// - ParallelChannelSeries
// - RectangleSeries
// - EllipseSeries
// - PolylineSeries
// - DoodleSeries
// - LabelSeries
// - CalloutSeries
// - IconSeries
Stock Chart drawing tools provide a complete annotation system with built-in UI controls for selecting and configuring different drawing types.
Drawing Series Settings
Common settings for drawing series:
const drawingSeries = am5stock.DrawingSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
series: mainSeries,
strokeColor: am5.color(0x000000),
fillColor: am5.color(0x000000),
strokeOpacity: 1,
fillOpacity: 0.2,
strokeWidth: 2,
strokeDasharray: [],
snapToData: true,
field: "value" // "open", "value", "low", "high"
});
Interactive Elements
Make annotations interactive:
const rangeDataItem = yAxis.makeDataItem({
value: 100
});
const range = yAxis.createAxisRange(rangeDataItem);
const grid = rangeDataItem.get("grid");
grid.setAll({
stroke: am5.color(0xff0000),
strokeOpacity: 1,
strokeWidth: 2,
interactive: true,
cursorOverStyle: "pointer"
});
grid.events.on("click", function(e) {
console.log("Annotation clicked");
});
Best Practices
Use axis ranges for static annotations that don’t need to be moved by users.
For user-generated annotations, implement a drawing mode that can be toggled on/off to prevent accidental additions.
When allowing users to draw on charts, always validate the coordinates to ensure they fall within valid data ranges.
Drawing series in Stock Charts automatically handle serialization, allowing you to save and restore user drawings.
Complete Drawing Example
Here’s a complete example combining multiple annotation techniques:
import * as am5 from "@amcharts/amcharts5";
import * as am5xy from "@amcharts/amcharts5/xy";
const root = am5.Root.new("chartdiv");
const chart = root.container.children.push(
am5xy.XYChart.new(root, {
panX: true,
panY: true,
wheelX: "panX",
wheelY: "zoomX"
})
);
const xAxis = chart.xAxes.push(
am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "day", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {})
})
);
const yAxis = chart.yAxes.push(
am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
})
);
// Main series
const series = chart.series.push(
am5xy.LineSeries.new(root, {
name: "Data",
xAxis: xAxis,
yAxis: yAxis,
valueYField: "value",
valueXField: "date"
})
);
// Add horizontal line annotation
const targetLine = yAxis.makeDataItem({ value: 100 });
const targetRange = yAxis.createAxisRange(targetLine);
targetLine.get("grid").setAll({
stroke: am5.color(0xff0000),
strokeOpacity: 1,
strokeWidth: 2,
strokeDasharray: [5, 5]
});
targetLine.get("label").setAll({
text: "Target",
fill: am5.color(0xff0000)
});
// Add range highlight
const highlightRange = xAxis.makeDataItem({
value: new Date(2024, 0, 1).getTime(),
endValue: new Date(2024, 0, 31).getTime()
});
const highlight = xAxis.createAxisRange(highlightRange);
highlightRange.get("axisFill").setAll({
fill: am5.color(0x3b82f6),
fillOpacity: 0.1,
visible: true
});
// Set data and animate
series.data.setAll(data);
series.appear(1000);
chart.appear(1000, 100);