Date axis

Date axis is used to display date-based data with a natural time scale.

Data granularity

The single most important setting for a date axis is baseInterval which describes granularity of data used in the chart.

The value of baseInterval is an object of type ITimeInterval which has two keys:

  • timeUnit - what time unit is used in data.
  • count (optional) - number of time units.

For example, if we have daily data (one data item per day), we'd set up axis like this:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

The timeUnit can be one of the following: "millisecond", "second", "minute", "hour", "day", "week", "month", "year".

NOTE A lot of of other aspects and functionality of a date axis depends on a value of baseInterval as we will see throughout this tutorial.

Grid granularity

How it functions?

Date axis' will choose where and how to display its grid based on available space.

The axis renderer's setting minGridDistance controls how close together grid (and related ticks/labels) elements can be.

This determines theoretical number of grid lines a grid can display.

When axis calculates that number it will check how far those grid lines are in terms of time.

This will determine the granularity of the time units that grid uses.

For example, let's say our chart displays 30 days worth of daily data.

If our chart is wide enough to comfortably display all 30 grid lines, it will display a grid line for each day.

However, if the chart does not fit all 30 of days, it will revert to some bigger time intervals, like displaying a grid line every two days.

If that still not enough to display grids spaced out enough, it will revert to displaying grid every three, four, or five days, then weeks, months, etc.

The granularity depends on current date range of the axis, meaning that it will update dynamically when zooming the chart in and out:

Customizing grid granularity

We can control to what time intervals we want grid of the date axis to switch by setting its gridIntervals setting.

It's an array of ITimeInterval objects, each specifying an allowed option for grid lines.

Date axis will use the most granular interval that satisfies grid density restrictions.

For example, let's restrict grid to just days and months:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    gridIntervals: [
      { timeUnit: "day", count: 1 },
      { timeUnit: "month", count: 1 }
    ],
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    gridIntervals: [
      { timeUnit: "day", count: 1 },
      { timeUnit: "month", count: 1 }
    ],
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Default grid intervals

The default value for gridIntervals is:

[
  { timeUnit: "millisecond", count: 1 },
  { timeUnit: "millisecond", count: 5 },
  { timeUnit: "millisecond", count: 10 },
  { timeUnit: "millisecond", count: 50 },
  { timeUnit: "millisecond", count: 100 },
  { timeUnit: "millisecond", count: 500 },
  { timeUnit: "second", count: 1 },
  { timeUnit: "second", count: 5 },
  { timeUnit: "second", count: 10 },
  { timeUnit: "second", count: 30 },
  { timeUnit: "minute", count: 1 },
  { timeUnit: "minute", count: 5 },
  { timeUnit: "minute", count: 10 },
  { timeUnit: "minute", count: 15 },
  { timeUnit: "minute", count: 30 },
  { timeUnit: "hour", count: 1 },
  { timeUnit: "hour", count: 3 },
  { timeUnit: "hour", count: 6 },
  { timeUnit: "hour", count: 12 },
  { timeUnit: "day", count: 1 },
  { timeUnit: "day", count: 2 },
  { timeUnit: "day", count: 3 },
  { timeUnit: "day", count: 4 },
  { timeUnit: "day", count: 5 },
  { timeUnit: "week", count: 1 },
  { timeUnit: "month", count: 1 },
  { timeUnit: "month", count: 2 },
  { timeUnit: "month", count: 3 },
  { timeUnit: "month", count: 6 },
  { timeUnit: "year", count: 1 },
  { timeUnit: "year", count: 2 },
  { timeUnit: "year", count: 5 },
  { timeUnit: "year", count: 10 },
  { timeUnit: "year", count: 50 },
  { timeUnit: "year", count: 100 },
  { timeUnit: "year", count: 200 },
  { timeUnit: "year", count: 500 },
  { timeUnit: "year", count: 1000 },
  { timeUnit: "year", count: 2000 },
  { timeUnit: "year", count: 5000 },
  { timeUnit: "year", count: 10000 },
  { timeUnit: "year", count: 100000 }
]

Labels and ticks

The granularity of grid also may affect how related labels and ticks are placed.

For more information about how to position labels/ticks with their location and multiLocation settings, visit "XY chart axes: Location of axis elements" tutorial.

Date formats

Labels

The current grid granularity determines the date/time format of the labels, too.

Instead of using date format as set in a global date formatter, date axis defines own list of formats, suitable for each time unit a grid interval its grid can have.

Label date formats

The formats used for each time unit are set in date axis' dateFormats setting.

It's an object with a key corresponding to each time unit ( "millisecond", "second", "minute", "hour", "day", "week", "month", "year") with a value corresponding to its format.

Here's how it looks like by default, with International English locale:

{
  "millisecond": "mm:ss SSS",
  "second": "HH:mm:ss",
  "minute": "HH:mm",
  "hour": "HH:mm",
  "day": "MMM dd",
  "week": "MMM dd",
  "month": "MMM",
  "year": "yyyy"
}

We can easily change the formats as needed:

xAxis.get("dateFormats")["day"] = "MM/dd";
xAxis.get("dateFormats")["day"] = "MM/dd";

Period change date formats

A label representing start of a period, e.g. start of the month when grid uses daily time unit, will be formatted according to different format, by default.

We can turn this behavior off by setting markUnitChange setting to false on a date axis.

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    markUnitChange: false,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    baseInterval: { timeUnit: "day", count: 1 },
    markUnitChange: false,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

We can also override the date format for such labels.

Similarly to dateFormats there's another setting: periodChangeDateFormats.

Formats from periodChangeDateFormats will be used only for the period beginning labels, like for January 1st in the following example.

xAxis.get("periodChangeDateFormats")["day"] = "MMM";
xAxis.get("periodChangeDateFormats")["day"] = "MMM";

See the Pen
Line series with bullets
by amCharts team (@amcharts)
on CodePen.0

Axis tooltip

Axis will use the date format suitable for the granularity of the data (as set via baseInterval) for its tooltip (when used with cursor).

We can override that format using using tooltipDateFormat setting:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    tooltipDateFormat: "MMM d, yyyy",
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    tooltipDateFormat: "MMM d, yyyy",
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Axis date scope

Date axis will automatically calculate its scope (start and end date/time) from the data of actual series that are using it.

There are a few ways to override those start and end timestamps.

Custom scope

We can set start date using min setting of the axis. Similarly, end date can be overridden using max.

Those are numeric values of timestamps:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    min: new Date(2021, 0, 1).getTime(),
    max: new Date(2021, 1, 1).getTime(),
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    min: new Date(2021, 0, 1).getTime(),
    max: new Date(2021, 1, 1).getTime(),
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

The above will make date axis display scale from Jan 1st to Feb 1st, 2021.

We can specify both min and max, or just one of them. If either min or max is not set, the axis will use automatically-calculated value for it.

Relative scope extension

We can also make date axis expand its scope relatively to automatically-calculated range, using its extraMin and extraMax settings.

Those are relative values indicating how much of the automatically-calculated value range to add to either min or max.

For example, extraMax: 0.1 will make axis expand its scope by 10 past last data item.

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    extraMax: 0.1,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    extraMax: 0.1,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Dynamic data item grouping

Date axis can also orchestrate dynamic grouping of series data items.

It works like this: when there are more data items in currently selected time scope, it aggregates data items into larger representative data items.

For example, if we have a year worth of data - 365 days - it can aggregate those into weeks, or 52 data items.

Using grouping, a chart can handle hundreds of thousands of data items, without sacrificing performance and readability of the chart.

Data grouping is also dynamic. If user zooms in, and time scope changes, chart may revert to showing more granular data.

Here's a very basic chart with data grouping and 50K data points:

See the Pen
Date axis grid granularity
by amCharts team (@amcharts)
on CodePen.0

Enabling grouping

To enable grouping simply set axis' groupData setting to true:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Grouping threshold

Grouping occurs when there's a number of intervals in current scope is bigger than threshold value, which is 500 by default.

Please note that this is number of intervals, not actual data items.

For example, let's say we have a daily data (baseInterval: { timeUnit: "day" }) which spans 10 years.

That means ~3650 base intervals, which is way more than 500.

Grouping kicks in.

The chart now tries to group into weeks, resulting in ~520 weeks, which is still more than 500.

The chart now goes on to months, which gives us just 120 aggregate data items. Threshold is bigger, so that's how the chart will be shown: in monthly increments.

If zoom changes, the chart will re-evaluate the current range and number of intervals in it, and will adjust granularity of displayed data items as necessary.

The threshold can be change using groupCount setting:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupCount: 300,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupCount: 300,
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Grouping intervals

The groups into which the chart will try to group data items is set via groupIntervals setting, with the default being this:

groupIntervals: [
  { timeUnit: "millisecond", count: 1 },
  { timeUnit: "millisecond", count: 10 },
  { timeUnit: "millisecond", count: 100 },
  { timeUnit: "second", count: 1 },
  { timeUnit: "second", count: 10 },
  { timeUnit: "minute", count: 1 },
  { timeUnit: "minute", count: 10 },
  { timeUnit: "hour", count: 1 },
  { timeUnit: "day", count: 1 },
  { timeUnit: "week", count: 1 },
  { timeUnit: "month", count: 1 },
  { timeUnit: "year", count: 1 }
]

Let's say we don't want to group data items into weeks, or anything smaller than day, we can do this:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupIntervals: [

      { timeUnit: "day", count: 1 },
      { timeUnit: "month", count: 1 },
      { timeUnit: "year", count: 1 }
    ],
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupIntervals: [

      { timeUnit: "day", count: 1 },
      { timeUnit: "month", count: 1 },
      { timeUnit: "year", count: 1 }
    ],
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

Forcing group interval

We can also force the chart to group data items into specific interval, regardless if the threshold (groupCount) hasn't been passed.

The setting groupInterval is for that:

let xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupInterval: { timeUnit: "month", count: 1 },
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);
var xAxis = chart.xAxes.push(
  am5xy.DateAxis.new(root, {
    groupData: true,
    groupInterval: { timeUnit: "month", count: 1 },
    renderer: am5xy.AxisRendererX.new(root, {})
  })
);

The above will force grouping into months.

Zooming

Zooming to date range

We can use date axis' method zoomToDates() to zoom the axis to specific date range:

xAxis.zoomToDates(new Date(2021, 0, 1), new Date(2022, 0, 1));
xAxis.zoomToDates(new Date(2021, 0, 1), new Date(2022, 0, 1));

Pre-zooming

Once data is loaded on some series the date axis gets its scope of dates from, we can use zoomToDates to pre-zoom it to some date range:

series.events.once("datavalidated", function(ev) {
  ev.target.get("xAxis").zoomToDates(new Date(2021, 0, 1), new Date(2022, 0, 1));
});
series.events.once("datavalidated", function(ev) {
  ev.target.get("xAxis").zoomToDates(new Date(2021, 0, 1), new Date(2022, 0, 1));
});

Read more about pre-zooming using series events in "Zoom and pan: Pre-zooming axes".

Events

Date axis does not have specific events that are triggered when it is zoomed.

Instead, we can use private setting change events on selectionMin and selectionMax private settings:

xAxis.onPrivate("selectionMin", function(value, target) {
  var start = new Date(value);
  console.log("Start date changed:", start);
});

xAxis.onPrivate("selectionMax", function(value, target) {
  var end = new Date(value);
  console.log("End date changed:", end);
});
xAxis.onPrivate("selectionMin", function(value, target) {
  var start = new Date(value);
  console.log("Start date changed:", start);
});

xAxis.onPrivate("selectionMax", function(value, target) {
  var end = new Date(value);
  console.log("End date changed:", end);
});

See the Pen
amCharts 5: Line chart with scroll and zoom
by amCharts team (@amcharts)
on CodePen.0

Category date axis

There's a special version of the category axis, called "category date axis", or CategoryDateAxis.

In a nutshell it's a DateAxis that acts like a CategoryAxis, i.e. it displays grid/labels for only those time periods there is actual data for.

It can be used to display irregularly-spaced date-based data, where maintaining natural time scale is not important, e.g. removing weekends from the axis, because there is not data for those days.

For more information, please refer to "Category date axis" tutorial.