Skip to main content

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
    });
  }
});

Stock Chart Drawing Tools

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);