AM Charts - XY Charts

AM Charts

What is an XY chart?

We use XY chart to plot any kind of serial 2/3-dimensional data, including line, area, column, bar, candlestick, ohlc, stepline, even heatmaps. Basically, any data, that requires 2 dimensions can be depicted using XY chart. This tutorial will guide you through the fundamentals.

What do we need in order to create an XY chart?

Before we can do anything, we need to create a chart object, which in this case an instance of an XYChart class. An XY chart, at the very least needs the following things:

  1. Chart instance;
  2. Data;
  3. At least two axes - vertical and horizontal;
  4. At least one series.
<script src="//www.amcharts.com/lib/4/core.js"></script>
<script src="//www.amcharts.com/lib/4/charts.js"></script>

var chart = am4core.create("chartdiv", am4charts.XYChart);

Just like for most chart types, the data for a XY chart is an array of objects. Each of those objects will represent a data item and must at least two values, because XY chart is plots data in two dimensions. XY chart supports any kind of dimensional data. It can be string-based categories, numeric values, dates, etc. You can mix and match any of those.

As mentioned earlier in this article, for the XY chart to function, it needs to have at least two axes - one vertical (Y) and one horizontal (X), hence the XY title.

Data can take many shapes, so we have different axes for different requirements. For example, taking the sample data we have shown earlier in this article, a country is a "category", and "litres" is numeric value. If we'd like to build a simple column chart, we'd probably go for a CategoryAxis horizontally, and ValueAxis vertically. That is just a basic example. In reality you can use any type of axis on both X and Y. As long as data is compatible with it, you're all set.

Each XY chart has two list properties: xAxes and yAxes. When you create an axis of particular type, you push() it into appropriate list. Let's say, building on previous date example, we want to plot data that are string-based names (countries) horizontally, and numeric values (liters) vertically. We'll need to create an instance of CategoryAxis and push it into xAxes, as well as instance of ValueAxis for pushing into yAxes.

var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

Notice, the dataFields in CategoryAxis definition. This type of axis is special in such way, that it needs to access data (so that it can extract a list of categories). The dataFields instructs which property in data objects holds category names.

How can we configure axes for XY charts?

We can configure axes just like any other object - by setting their properties. We already saw how we need to specify category data field for CategoryAxis. There is a number of other settings that can be set. For example, if we'd like to add an axis title, we'd use its title property, which happens to be an instance of Label. So we'd actually need to set title.text:

var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Countries";

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Litres sold (M)";

There's so much more to axes than that. You can configure its appearance down to a bolt, including grid, cursor tooltips, gaps, labels, and so much more. But this is a topic to a dedicated concept article about Axes.

What is the purpose of CategoryAxis?

Displays equally divided string-based increments - categories. Requires dataFields.category to be set so that it can extract actual category names from data. Category labels are displayed as they are in data.

What is the purpose of DateAxis?

Used to position items on a date/time scale. Will format labels according to its DataFormatter. (more about formatters)

What is the purpose of DurationAxis?

The values on this axis is treated as time duration. This is different than date or time, since grid/labels do not point to specific date/time but rather are placed at increment of time. Will format labels according to its DurationFormatter. (more about formatters)

What is the purpose of ValueAxis?

Used to position items based on their numeric value. Will format labels according to its NumberFormatter. (more about formatters)

Can an XY chart contain any number of series?

For the XY chart to be useful it needs to have at least one series. It can contain any number of series of different types - the chart will handle that gracefully.

How should we choose one series-related class or another?

A series is represented by a series class. Depending on what we need to show, and what data we have we may choose one series-related class or another. As in most other chart types, XY chart's series are held in a List accessible via series property. To create a new Series, we just create an instance of series push() it into chart's series list. Let's try creating a series for column graph, which happens to be represented by ColumnSeries class.

var columnSeries = chart.series.push(new am4charts.ColumnSeries());

How can we bind a series to data?

The most and foremost requirement when configuring series, is to specify on where it should get its data from. Series' property dataFields is used for exactly that (more about series' data fields)

series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";

What should we do if we have a DateAxis instead of a CategoryAxis?

Different series types and different date require different data fields. Let's say if we had a DateAxis instead of CategoryAxis. We'd need to set dateX instead of categoryX. Likewise, for some series like CandlestickSeries, we'll require way more than just one field for value. It will require data fields for open, high, low, and close values.

How can we configure series?

Now that we have series bound to data, we can use their properties to configure series appearance and related properties. For example, we can set series' name, so that it can be nicely represented in Legend and tooltips. We can also use tooltipText to specify what is displayed when user hovers over series. Or, we can specify series visual settings - some via direct properties, some via templates.

var series = chart.series.push(new am4charts.ColumnSeries());
series.name = "Sales";
series.columns.template.tooltipText = "Series: {name}\nCategory: {categoryX}\nValue: {valueY}";
series.columns.template.fill = am4core.color("#104547"); // fill
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";

There's a caveat about setting tooltipText. Setting it directly on series will work only if you are also using chart cursor. Otherwise, you should set it on series' elements templates, such as columns.template.

What is the purpose of CandlestickSeries?

Displays a special kind of graph (candlestick).Requires four value fields: open, high, low, close. (more info)

What is the purpose of ColumnSeries?

Displays columns, or bars.

What is the purpose of ColumnSeries3D?

Displays 3D columns, or bars

What is the purpose of ConeSeries?

Displays cones or cylinders.

What is the purpose of CurvedColumnSeries?

Displays columns as smoothed filled sines (more info)

What is the purpose of LineSeries?

Displays a line graph.Can also be used to create an area graph.

What is the purpose of StepLineSeries?

Displays a stepped line graph.MORE INFO Check this tutorial on configuring StepLineSeries

What is the purpose of OHLCSeries?

Displays a special kind of graph (OHLC).Requires four value fields: open, high, low, close.

Can we mix and match multiple series on the same chart?

You can mix and match multiple series on the same chart. Let's add a LineSeries on top of the ColumnSeries we already have.

var series = chart.series.push(new am4charts.ColumnSeries());
series.name = "Sales";
series.columns.template.tooltipText = "Series: {name}\nCategory: {categoryX}\nValue: {valueY}";
series.columns.template.fill = am4core.color("#104547"); // fill

var series2 = chart.series.push(new am4charts.LineSeries());
series2.name = "Units";
series2.stroke = am4core.color("#CDA2AB");
series2.strokeWidth = 3;
series2.dataFields.valueY = "units";
series2.dataFields.categoryX = "country";

It's worth noting, that adding multiple ColumnSeries to the chart will automatically arrange them into nice clusters.

It is possible to manipulate the order in which series are drawn using their zIndex properties. Refer to "Manipulating order" section in Series article for further info.

How can we enable bullets on XY chart?

Bullets on series, especially Line series, can increase readability and overall visual appeal of the chart. We do have a separate article on "Bullets". There's a lot of info and useful examples there. Make sure you have it in your reading list.

Can we stack multiple series on an XY chart?

Multiple series of the same type can be stacked together, but only if they are attached to the same axes. For example several Line series can be stacked on one another. Similarly, Column series can be stacked to form "stacks".

Enabling stacking for series is easy: just set its stacked property to true. The chart will take care of everything else. Basically, stacked = true means this in human language: "Try to hop on previous series back". Naturally, this means that this setting has no effect on the first defined series, because, well, there's no previous series to it. Here's a simple chart with three Column series; all stacked = true.

am4core.useTheme(am4themes_animated);
am4core.useTheme(am4themes_kelly);

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

chart.marginRight = 400;

// Add data
chart.data = [{
  "country": "Lithuania",
  "research": 501.9,
  "marketing": 250,
  "sales": 199
}, {
  "country": "Czech Republic",
  "research": 301.9,
  "marketing": 222,
  "sales": 251
}, {
  "country": "Ireland",
  "research": 201.1,
  "marketing": 170,
  "sales": 199
}, {
  "country": "Germany",
  "research": 165.8,
  "marketing": 122,
  "sales": 90
}, {
  "country": "Australia",
  "research": 139.9,
  "marketing": 99,
  "sales": 252
}, {
  "country": "Austria",
  "research": 128.3,
  "marketing": 85,
  "sales": 84
}, {
  "country": "UK",
  "research": 99,
  "marketing": 93,
  "sales": 142
}, {
  "country": "Belgium",
  "research": 60,
  "marketing": 50,
  "sales": 55
}, {
  "country": "The Netherlands",
  "research": 50,
  "marketing": 42,
  "sales": 25
}];

//console.log('chart', chart);

// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Local country offices";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;

var  valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Expenditure (M)";

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "research";
series.dataFields.categoryX = "country";
series.name = "Research";
series.tooltipText = "{name}: [bold]{valueY}[/]";
series.stacked = true;

var series2 = chart.series.push(new am4charts.ColumnSeries());
series2.dataFields.valueY = "marketing";
series2.dataFields.categoryX = "country";
series2.name = "Marketing";
series2.tooltipText = "{name}: [bold]{valueY}[/]";
series2.stacked = true;

var series3 = chart.series.push(new am4charts.ColumnSeries());
series3.dataFields.valueY = "sales";
series3.dataFields.categoryX = "country";
series3.name = "Sales";
series3.tooltipText = "{name}: [bold]{valueY}[/]";
series3.stacked = true;

// Add cursor
chart.cursor = new am4charts.XYCursor();

How can we have 100% stack?

There's no single setting to enable 100% stacks in amCharts 4. Enabling this stack type requires a two-setting combo. Let's explore them. First of all, we will need to bind our series to one additional data field: "valueYShow". Basically, this data field means this: "When showing series, use value from this field rather than the one specified in "valueY"".

series.dataFields.valueY = "research";
series.dataFields.valueYShow = "totalPercent";

Now, if we had percent values in the data, we could just go ahead and use them. However, most probably, we don't, so we will need the to tell the chart to calculate them for us.

That's where Value axis' calculateTotals settings comes into play. Setting it to true (it's disabled by default) will instruct the chart to populate our data items with pre-calculated values, such us percent value of the data item in comparison to other data items in the same category.

valueAxis.calculateTotals = true;

Why don't we turn this on by default? Doing this kind of calculations is a costly operation, especially on large data sets. Would be waste of computer resources and potential performance overhead to do them if they are not used.

OK, so now that we have our series use calculated field as it's display value, and we have chart calculating those values, we need to do some last touches.

Since Value axis is intelligent, and will try to optimize it's scale, we may end up with a chart that shows values that are not exactly 0 to 100. For that we'll need to use axis' min / max as well as strictMinMax settings to enforce an exact 0 to 100 scale:

valueAxis.calculateTotals = true;
valueAxis.min = 0;
valueAxis.max = 100;
valueAxis.strictMinMax = true;

We also might want to add "%" to values on the our Value axis, so that users know those are percent they are looking at. For this purpose we may use an adapter:

valueAxis.renderer.labels.template.adapter.add("text", function(text) {
  return text + "%";
});
// Apply chart themes
am4core.useTheme(am4themes_animated);
am4core.useTheme(am4themes_kelly);

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
chart.data = [{
  "country": "Lithuania",
  "research": 501.9,
  "marketing": 250,
  "sales": 199
}, {
  "country": "Czech Republic",
  "research": 301.9,
  "marketing": 222,
  "sales": 251
}, {
  "country": "Ireland",
  "research": 201.1,
  "marketing": 170,
  "sales": 199
}, {
  "country": "Germany",
  "research": 165.8,
  "marketing": 122,
  "sales": 90
}, {
  "country": "Australia",
  "research": 139.9,
  "marketing": 99,
  "sales": 252
}, {
  "country": "Austria",
  "research": 128.3,
  "marketing": 85,
  "sales": 84
}, {
  "country": "UK",
  "research": 99,
  "marketing": 93,
  "sales": 142
}, {
  "country": "Belgium",
  "research": 60,
  "marketing": 50,
  "sales": 55
}, {
  "country": "The Netherlands",
  "research": 50,
  "marketing": 42,
  "sales": 25
}];

// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Local country offices";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;

var  valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Expenditure (%)";
valueAxis.calculateTotals = true;
valueAxis.min = 0;
valueAxis.max = 100;
valueAxis.strictMinMax = true;
valueAxis.renderer.labels.template.adapter.add("text", function(text) {
  return text + "%";
});

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "research";
series.dataFields.valueYShow = "totalPercent";
series.dataFields.categoryX = "country";
series.name = "Research";
series.tooltipText = "{name}: [bold]{valueY}[/]";
series.stacked = true;

var series2 = chart.series.push(new am4charts.ColumnSeries());
series2.dataFields.valueY = "marketing";
series2.dataFields.valueYShow = "totalPercent";
series2.dataFields.categoryX = "country";
series2.name = "Marketing";
series2.tooltipText = "{name}: [bold]{valueY}[/]";
series2.stacked = true;

var series3 = chart.series.push(new am4charts.ColumnSeries());
series3.dataFields.valueY = "sales";
series3.dataFields.valueYShow = "totalPercent";
series3.dataFields.categoryX = "country";
series3.name = "Sales";
series3.tooltipText = "{name}: [bold]{valueY}[/]";
series3.stacked = true;

// Add cursor
chart.cursor = new am4charts.XYCursor();

Can we have stacking of series with gaps in data?

No. Please note, that the chart does not support stacking of series with gaps in data. This means that if you have data set directly on series, each stacked series must include equal number and category/date data points, or the stack will break.

Can we mix stacked and non-stacked series?

Yes. We can mix stacked and non-stacked series on the same chart. As explained earlier, stacked = true means you need this series piggyback on top of the previous one. So, if you want some series not to participate in the stack, you need to ensure that it's stacked is not set to true, and that the series going after it does not have its stacked set to true. Here's a version of the previous chart, with one non-stacked series:

// Apply chart themes
am4core.useTheme(am4themes_animated);
am4core.useTheme(am4themes_kelly);

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
chart.data = [{
  "country": "Lithuania",
  "research": 501.9,
  "marketing": 250,
  "sales": 199
}, {
  "country": "Czech Republic",
  "research": 301.9,
  "marketing": 222,
  "sales": 251
}, {
  "country": "Ireland",
  "research": 201.1,
  "marketing": 170,
  "sales": 199
}, {
  "country": "Germany",
  "research": 165.8,
  "marketing": 122,
  "sales": 90
}, {
  "country": "Australia",
  "research": 139.9,
  "marketing": 99,
  "sales": 252
}, {
  "country": "Austria",
  "research": 128.3,
  "marketing": 85,
  "sales": 84
}, {
  "country": "UK",
  "research": 99,
  "marketing": 93,
  "sales": 142
}, {
  "country": "Belgium",
  "research": 60,
  "marketing": 50,
  "sales": 55
}, {
  "country": "The Netherlands",
  "research": 50,
  "marketing": 42,
  "sales": 25
}];

// Create axes
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Local country offices";
categoryAxis.renderer.grid.template.location = 0;
categoryAxis.renderer.minGridDistance = 20;

var  valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Expenditure (M)";

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "research";
series.dataFields.categoryX = "country";
series.name = "Research";
series.tooltipText = "{name}: [bold]{valueY}[/]";
// This has no effect
// series.stacked = true;

var series2 = chart.series.push(new am4charts.ColumnSeries());
series2.dataFields.valueY = "marketing";
series2.dataFields.categoryX = "country";
series2.name = "Marketing";
series2.tooltipText = "{name}: [bold]{valueY}[/]";
// Do not try to stack on top of previous series
// series2.stacked = true;

var series3 = chart.series.push(new am4charts.ColumnSeries());
series3.dataFields.valueY = "sales";
series3.dataFields.categoryX = "country";
series3.name = "Sales";
series3.tooltipText = "{name}: [bold]{valueY}[/]";
series3.stacked = true;

// Add cursor
chart.cursor = new am4charts.XYCursor();

// Add legend
chart.legend = new am4charts.Legend();

Can we have multiple stacks?

Yes. The above also enables us to collect multiple series into separate stacks. Each time a series "breaks the stack" by not setting its stacked to true, a new stack will begin, with all subsequent series stacking up on the breaking series' back.

Unless otherwise stated, the content of this page is licensed under Creative Commons Attribution-ShareAlike 3.0 License