Anatomy of 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.

Creating 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.

This tutorial will walk through all of these.

Importing modules/scripts

Needless to say, before you can use modules/objects, you need to make sure all required modules (in TypeScript), or files (in JavaScript) are imported. Please refer to our Getting started articles for more details:

For a XY chart, we'll need to import core (main module) and charts modules.

import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";
<script src="//cdn.amcharts.com/lib/4/core.js"></script>
<script src="//cdn.amcharts.com/lib/4/charts.js"></script>

We'll be importing and referring these as am4core and am4charts in the course of this article, and overally in our demos and tutorials.

Creating chart instance

For that we're going to be using am4core.create() function. (or am4core.createFromConfig() if you are using JSON-based config approach)

let chart = am4core.create("chartdiv", am4charts.XYChart);
var chart = am4core.create("chartdiv", am4charts.XYChart);
var chart = am4core.createFromConfig({
  // ... chart config
}, "chartdiv", am4charts.XYChart);

NOTE There's a special comment on JSON-based setup down this article. Read on.

Data

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 have at least two values because XY chart 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.

In-line data

We assign the data to the data to chart's data property:

chart.data = [{
  "country": "Lithuania",
  "litres": 501
}, {
  "country": "Czechia",
  "litres": 301
}, {
  "country": "Ireland",
  "litres": 201
}, {
  "country": "Germany",
  "litres": 165
}, {
  "country": "Australia",
  "litres": 139
}, {
  "country": "Austria",
  "litres": 128
}, {
  "country": "UK",
  "litres": 99
}, {
  "country": "Belgium",
  "litres": 60
}, {
  "country": "The Netherlands",
  "litres": 50
}];
chart.data = [{
  "country": "Lithuania",
  "litres": 501
}, {
  "country": "Czechia",
  "litres": 301
}, {
  "country": "Ireland",
  "litres": 201
}, {
  "country": "Germany",
  "litres": 165
}, {
  "country": "Australia",
  "litres": 139
}, {
  "country": "Austria",
  "litres": 128
}, {
  "country": "UK",
  "litres": 99
}, {
  "country": "Belgium",
  "litres": 60
}, {
  "country": "The Netherlands",
  "litres": 50
}];
{
  // ...
  "data": [{
    "country": "Lithuania",
    "litres": 501
  }, {
    "country": "Czechia",
    "litres": 301
  }, {
    "country": "Ireland",
    "litres": 201
  }, {
    "country": "Germany",
    "litres": 165
  }, {
    "country": "Australia",
    "litres": 139
  }, {
    "country": "Austria",
    "litres": 128
  }, {
    "country": "UK",
    "litres": 99
  }, {
    "country": "Belgium",
    "litres": 60
  }, {
    "country": "The Netherlands",
    "litres": 50
  }]
}

External data

We can also make the chart load external data using dataSource.

chart.dataSource.url = "chart_data.json";
chart.dataSource.url = "chart_data.json";
{
  // ...
  "dataSource": {
    "url": "chart_data.json"
  }
}

MORE INFO Please refer to our "External data" article, for more details.

Order of date-based data

IMPORTANT If you are plotting date/time-based data, make sure that data items are sorted in ascending order. More info here.

Axes

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.

NOTE You can have more than two axes. Please refer for lower sections of this article for more info about such usage.

Creating axes

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.

let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
var categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
{
  // ...
  "xAxes": [{
    "type": "CategoryAxis",
    "dataFields": {
      "category": "country"
    }
  }],
  "yAxes": [{
    "type": "ValueAxis"
  }]
}

So far so good.

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.

Setting up axes

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:

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

let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Litres sold (M)";
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)";
{
  // ...
  "xAxes": [{
    "type": "CategoryAxis",
    "dataFields": {
      "category": "country",
      "title": {
        "text": "Countries"
      }
    }
  }],
  "yAxes": [{
    "type": "ValueAxis",
    "title": {
      "text": "Litres sold (M)"
    }
  }]
}

More about axes

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.

XY chart axis types

An XY chart can use any combination of the following axis types:

Axis class Comment
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.

DateAxis Used to position items on a date/time scale.Will format labels according to its DataFormatter. (more about formatters)
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)

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

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.

MORE INFO We do have a dedicated "Series" article that goes into concept of series in greater detail. We suggest you take a look there before proceeding with this article. We'll touch basic aspects that relate to XY chart here.

Creating series

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.

let series = chart.series.push(new am4charts.ColumnSeries());
var series = chart.series.push(new am4charts.ColumnSeries());
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    // Series settings
  }]
}

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

Again, building up on our data example further up this article, we know that our column series should get categories from "category" field to be used on horizontal (x) axis, and numeric value from "litres" to be used on vertical (y) axis.

series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    "dataFields": {
      "valueY": "litres",
      "categoryX": "country"
    }
  }]
}

IMPORTANT Different series types and different data 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.

Configuring 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. (more info)

Or, we can specify series visual settings - some via direct properties, some via templates.

All of the above is discussed in Series article. We'll just drop a small example here for those itching to get going:

let 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";
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";
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    "name": "Sales",
    "dataFields": {
      "valueY": "litres",
      "categoryY": "country"
    },
    "columns": {
      "tooltipText": "Series: {name}\nCategory: {categoryX}\nValue: {valueY}",
      "fill": "#104547"
    }
  }]
}

NOTE 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.

XY chart series

XY chart supports the following series classes:

Series class / Data fields Data fields Example Comment
CandlestickSeries ICandlestickSeriesDataFields Displays a special kind of graph (candlestick).Requires four value fields: open, high, low, close. (more info)
ColumnSeries IColumnSeriesDataFields Displays columns, or bars.
ColumnSeries3D IColumnSeries3DDataFields Displays 3D columns, or bars.
ConeSeries IConeSeriesDataFields Displays cones or cylinders.
CurvedColumnSeries ICurvedColumnSeriesDataFields Displays columns as smoothed filled sines (more info).
LineSeries ILineSeriesDataFields Displays a line graph.Can also be used to create an area graph.
StepLineSeries IStepLineSeriesDataFields Displays a stepped line graph.MORE INFO Check this tutorial on configuring StepLineSeries.
OHLCSeries IOHLCSeriesDataFields Displays a special kind of graph (OHLC).Requires four value fields: open, high, low, close.

Mixing series

You can mix and match multiple series on the same chart.

Let's add a LineSeries on top of the ColumnSeries we already have.

let 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");
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";

let 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";
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";
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    "name": "Sales",
    "dataFields": {
      "valueY": "litres",
      "categoryY": "country"
    },
    "columns": {
      "tooltipText": "Series: {name}\nCategory: {categoryX}\nValue: {valueY}",
      "fill": "#104547"
    }
  }, {
    "type": "LineSeries",
    "stroke": "#CDA2AB",
    "strokeWidth": 3,
    "dataFields": {
      "valueY": "units",
      "categoryY": "country"
    }
  }]
}

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

TIP 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.

Enabling bullets

Bullets on series, especially Line series, can increase readability and overall visual appeal of the chart.

MORE INFO 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.

Stacking

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 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.

See the Pen amCharts V4: XY chart example (stacked 1) by amCharts (@amcharts) on CodePen.

100% stacks

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";
series.dataFields.valueY = "research";
series.dataFields.valueYShow = "totalPercent";
{
  // ...
  "series": [{
     // ...
    "dataFields": {
      // ...
      "valueY": "research",
      "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;
valueAxis.calculateTotals = true;
{
  // ...
  "yAxes": [{
    // ...
    "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;
valueAxis.calculateTotals = true;
valueAxis.min = 0;
valueAxis.max = 100;
valueAxis.strictMinMax = true;
{
  // ...
  "yAxes": [{
    // ...
    "calculateTotals": true,
    "min": 0,
    "max": 100,
    "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 + "%";
});
valueAxis.renderer.labels.template.adapter.add("text", function(text) {
  return text + "%";
});
{
  // ...
  "yAxes": [{
    // ...
    "renderer": {
      "labels": {
        "text": function(text) {
          return text + "%";
        }
      }
    }
  }]
}

MORE INFO If you're not familiar with adapter concept, read up our "Adapters" article.

Let's see how it turned out:

See the Pen amCharts V4: XY chart example (stacked 2) by amCharts (@amcharts) on CodePen.

Note about stacked data

IMPORTANT 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.

Mixing stacked and non-stacked series

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:

See the Pen amCharts V4: XY chart example (stacked 3) by amCharts (@amcharts) on CodePen.

Multiple stacks

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.

Line series with gaps

We'd also like to mention a special feature of a Line series - it's ability to break and form "gaps".

Gaps over "empty" data points

Normally, a Line series would "jump over" empty data items - data point that do not have a value for this series - connecting two adjacent data items that have value with a straight line. This allows having uninterrupted line series regardless of how erratic our source data is.

Here's an example of such data:

[{
  "date": new Date(2018, 3, 20),
  "value": 90
}, {
  "date": new Date(2018, 3, 21),
  "value": 102
}, {
  "date": new Date(2018, 3, 22)
}, {
  "date": new Date(2018, 3, 23),
  "value": 125
}, {
  "date": new Date(2018, 3, 24),
  "value": 55
}]

As you can see, there is data missing for April 22nd (months in Date constructor are zero-based).

A series, that uses value fields, would still be drawn in full, "jumping over" 22nd.

If we'd rather like the line to break there, we can set Line series' connect property to false.

Below, you will find an example that shows two series, both with gaps in their data. One has connect = false set. The other is not.

Observe, how the first breaks, while the other bridges the gaps:

See the Pen amCharts 4: Line series with gaps by amCharts (@amcharts) on CodePen.

Gaps over missing data points

In previous section we saw how a Line series can break over "empty" data points. Now, let's talk how it can also break over "missing" data points.

Let's reformat the data from the previous example:

[{
  "date": new Date(2018, 3, 20),
  "value": 90
}, {
  "date": new Date(2018, 3, 21),
  "value": 102
}, {
  "date": new Date(2018, 3, 23),
  "value": 125
}, {
  "date": new Date(2018, 3, 24),
  "value": 55
}]

Now, April 22nd is missing altogether.

If we set connect = false our line will still break on that date, even if there's no "breaking" data point there.

The feature (available since version 4.2.4) responsible for that is called autoGapCount, and it works like this: if the distance between two adjacent data points is bigger than baseInterval * autoGapCount and connect = false the line breaks.

The default for this setting is 1.1 which means that if the distance is at least 10% greater than the granularity of data, we consider it a gap.

Looking at the sample data above, we have daily granularity: distance between each data point is one day (24 hours). Now, since April 22nd is missing the distance between 21st and 23rd becomes two days (48 hours) which satisfies "gap" criteria (24 hours * 1.1 = 26.4 hours). Boom - a gap.

This allows fine-grained control of what to consider a gap in the data. Say we want a line to break only if there's a distance of three days between data points: we set autoGapCount = 3. Now, if there's distance of one or two days between two data points, they'll be connected, even if connect = false.

IMPORTANT Automatic gaps discussed in this section will work only with DateAxis. If you have ValueAxis as your X axis, the only way to insert gaps, is via empty data points, as discussed in previous section.

Complete example

OK, time to plop it all together: (notice how we've updated our data to accommodate for the second series we've just added)

// Import modules
import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";

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

// Add data
chart.data = [{
  "country": "Lithuania",
  "litres": 501.9,
  "units": 250
}, {
  "country": "Czech Republic",
  "litres": 301.9,
  "units": 222
}, {
  "country": "Ireland",
  "litres": 201.1,
  "units": 170
}, {
  "country": "Germany",
  "litres": 165.8,
  "units": 122
}, {
  "country": "Australia",
  "litres": 139.9,
  "units": 99
}, {
  "country": "Austria",
  "litres": 128.3,
  "units": 85
}, {
  "country": "UK",
  "litres": 99,
  "units": 93
}, {
  "country": "Belgium",
  "litres": 60,
  "units": 50
}, {
  "country": "The Netherlands",
  "litres": 50,
  "units": 42
}];

// Create axes
let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Countries";

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

// Create series
let series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";
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";
<script src="//cdn.amcharts.com/lib/4/core.js"></script>
<script src="//cdn.amcharts.com/lib/4/charts.js"></script>

<div id="chartdiv" style="width: 100%; height: 400px;"></div>

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

// Add data
chart.data = [{
  "country": "Lithuania",
  "litres": 501.9,
  "units": 250
}, {
  "country": "Czech Republic",
  "litres": 301.9,
  "units": 222
}, {
  "country": "Ireland",
  "litres": 201.1,
  "units": 170
}, {
  "country": "Germany",
  "litres": 165.8,
  "units": 122
}, {
  "country": "Australia",
  "litres": 139.9,
  "units": 99
}, {
  "country": "Austria",
  "litres": 128.3,
  "units": 85
}, {
  "country": "UK",
  "litres": 99,
  "units": 93
}, {
  "country": "Belgium",
  "litres": 60,
  "units": 50
}, {
  "country": "The Netherlands",
  "litres": 50,
  "units": 42
}];

// Create axes
let categoryAxis = chart.xAxes.push(new am4charts.CategoryAxis());
categoryAxis.dataFields.category = "country";
categoryAxis.title.text = "Countries";

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

// Create series
var series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";
series.name = "Sales";
series.columns.template.tooltipText = "Series: {name}\nCategory: {categoryX}\nValue: {valueY}";
series.columns.template.fill = am4core.color("#00ff00"); // 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";
</script>
<script src="//cdn.amcharts.com/lib/4/core.js"></script>
<script src="//cdn.amcharts.com/lib/4/charts.js"></script>

<div id="chartdiv" style="width: 100%; height: 400px;"></div>

<script>
// Create chart instance
var chart = am4core.createFromConfig({
  "data": [{
    "country": "Lithuania",
    "litres": 501.9,
    "units": 250
  }, {
    "country": "Czech Republic",
    "litres": 301.9,
    "units": 222
  }, {
    "country": "Ireland",
    "litres": 201.1,
    "units": 170
  }, {
    "country": "Germany",
    "litres": 165.8,
    "units": 122
  }, {
    "country": "Australia",
    "litres": 139.9,
    "units": 99
  }, {
    "country": "Austria",
    "litres": 128.3,
    "units": 85
  }, {
    "country": "UK",
    "litres": 99,
    "units": 93
  }, {
    "country": "Belgium",
    "litres": 60,
    "units": 50
  }, {
    "country": "The Netherlands",
    "litres": 50,
    "units": 42
  }],
  "xAxes": [{
    "type": "CategoryAxis",
    "dataFields": {
      "category": "country",
      "title": {
        "text": "Countries"
      }
    }
  }],
  "yAxes": [{
    "type": "ValueAxis",
    "title": {
      "text": "Litres sold (M)"
     }
  }],
  "series": [{
    "type": "ColumnSeries",
    "dataFields": {
      "valueY": "litres",
      "categoryX": "country"
    },
    "name": "Sales",
    "columns": {
      "tooltipText": "Series: {name}\nCategory: {categoryX}\nValue: {valueY}",
      "stroke": "#ff0000",
      "fill": "#00ff00"
    }
  }, {
    "type": "LineSeries",
    "stroke": "#CDA2AB",
    "strokeWidth": 3,
    "dataFields": {
      "valueY": "units",
      "categoryX": "country"
    },
    "name": "Units"
  }]
}, "chartdiv", am4charts.XYChart);
</script>

And see what it looks like:

See the Pen amCharts V4: XY chart example (column) by amCharts (@amcharts) on CodePen.

Additional controls

Legend

Now that we have several Series, it makes sense to display a legend to identify them.

It's easy, just instantiate Legend class and assign to chart's legend property:

chart.legend = new am4charts.Legend();
chart.legend = new am4charts.Legend();
{
  // ...
  "legend": {
    // We can skip "type" here since there's only
    // one type of legend available, so the chart
    // will be able to figure this out for itself.
  }
}

MORE INFO There's more to it than that. For more insights about it, check out dedicated "Legend" concepts article.

Cursor

A chart cursor is another control that can add interactivity to the chart, making it more usable, such as revealing tooltips for all series in the hovered category.

To add it, create an instance of XYCursor and assign to chart's cursor property:

chart.cursor = new am4charts.XYCursor();
chart.cursor= new am4charts.XYCursor();
{
  // ...
  "cursor": {
    "type": "XYCursor"
  }
}

MORE INFO For more information about chart cursors, visit our dedicated "Chart cursor" article.

Scrollbars

The more data points chart has, the more crammed up it gets.

At some point you may want to enable users to "zoom in" into particular sections of the chart.

That's where scrollbars come handy. They offer a familiar control enabling user to easily select part of the data range for closer examination.

A chart can have both horizontal and vertical scrollbars.

As with other chart controls, to add a scrollbar you instantiate a class (Scrollbar) and assign to chart's property: scrollbarX for horizontal scrollbar, and scrollbarY for vertical one.

Let's add both.

chart.scrollbarX = new am4core.Scrollbar();
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarX = new am4core.Scrollbar();
chart.scrollbarY = new am4core.Scrollbar();
{
  // ...
  "scrollbarX": {
    "type": "Scrollbar"
  },
  "scrollbarY": {
    "type": "Scrollbar"
  }
}

Scrollbar with preview

We've shown you a basic scrollbar functionality so far.

There's a special case of XYChartScrollbar. It's an advanced version of a scrollbar, compatible with XYChart only, hence the name.

Besides usual scrollbar functionality, it can also display a preview of one or more chart series. To use it, simply use am4charts.XYChartScrollbar class to instantiate a scrollbar, as well as push() any of the series that you want to be displayed as a preview on it.

Let's add both.

let scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
var scrollbarX = new am4charts.XYChartScrollbar();
scrollbarX.series.push(series);
chart.scrollbarX = scrollbarX;
{
  // ...
  "scrollbarX": {
    "type": "XYChartScrollbar",
    "series": ["s1"] // reference series by its id
  }
}

Want to have a this advanced Scrollbar but without actual series? Simply hide Scrollbar's container which holds the series:

scrollbarX.scrollbarChart.seriesContainer.hide();
scrollbarX.scrollbarChart.seriesContainer.hide();
{
  // ...
  "scrollbarX": {
    // ...
    "scrollbarChart": {
      "seriesContainer": {
        "opacity": 0
      }
    }
  }
}

Customizing scrollbars

Make sure you check out our "Customizing chart scrollbar" tutorial for ways to customize the scrollbar.

Themes

Earlier in this article we set specific colors directly to our series.

It's not overly convenient, is it? Much better approach is to use themes to automatically apply colors to chart elements.

amCharts 4 comes with a set of distinctive themes. You can use either them, or go an make your own.

Bets of all, you can apply several themes to the same chart. To spice up your chart with animations, you can apply "animated" theme, and to use colors for Material palette - use "material" theme at the same time.

Themes are not loaded automatically, which means you'll need to load them just like any other chart resource.

When they are loaded, you can use function am4core.useTheme(theme) to "turn on" the theme. Any chart instantiated after that, will have that theme or multiple themes applied.

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

Naturally, having themes applied negates the need to hardcode colors directly in your chart config. So we have removed respective lines from the live demo below.

MORE INFO To learn more about using themes, please refer to "Themes" article.

Updated demo

Here's how our chart from before looks now:

See the Pen amCharts V4: XY chart example (with additional controls) by amCharts (@amcharts) on CodePen.

Going 3D

Certain elements of the XYChart can come in a 3D flavor.

To utilize them, we'll need to drop XYChart in favor of another class: XYChart3D.

Once we do that, we can start adding a special 3D-enabled series, like ColumnSeries3D and ConeSeries.

To avoid cluttering up the article even more, we'll skip the code block and get you the live demo. Click on the JS tab for the code:

See the Pen amCharts V4: XY chart example (3D) by amCharts (@amcharts) on CodePen.

Advanced scenarios

Multiple axes

Earlier in this article we just plopped a LineSeries on top of ColumnSeries. Series automatically attaches to the the first vertical axis and first horizontal axis, unless told otherwise.

That chart worked because both series used values of roughly the same scale. However, if the values would differ dramatically, our chart would become crippled with series with larger values overtaking the one with small ones.

For such scenarios we need multiple axes.

Each series on XYChart has two properties: xAxis and yAxis.

We can use those to tell series axes we want them to be attached to.

Let's add both.

// First value axis
let valueAxis = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis.title.text = "Litres sold (M)";

// Second value axis
let valueAxis2 = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis2.title.text = "Units sold";
valueAxis2.renderer.opposite = true;

// First series
let series = chart.series.push(new am4charts.ColumnSeries());
series.dataFields.valueY = "litres";
series.dataFields.categoryX = "country";
series.name = "Sales";
series.tooltipText = "{name}: [bold]{valueY}[/]";

// Second series
let series2 = chart.series.push(new am4charts.LineSeries());
series2.dataFields.valueY = "units";
series2.dataFields.categoryX = "country";
series2.name = "Units";
series2.tooltipText = "{name}: [bold]{valueY}[/]";
series2.strokeWidth = 3;
series2.yAxis = valueAxis2;
// First value axis
var valueAxis = chart.yAxes.push(new am4core.ValueAxis());
valueAxis.title.text = "Litres sold (M)";

// Second value axis
var valueAxis2 = chart.yAxes.push(new am4charts.ValueAxis());
valueAxis2.title.text = "Units sold";
valueAxis2.renderer.opposite = true;

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

// Second series
var series2 = chart.series.push(new am4charts.LineSeries());
series2.dataFields.valueY = "units";
series2.dataFields.categoryX = "country";
series2.name = "Units";
series2.tooltipText = "{name}: [bold]{valueY}[/]";
series2.strokeWidth = 3;
series2.yAxis = valueAxis2;
{
  // ...
  "yAxes": [{
    "type": "ValueAxis",
    "title": {
      "text": "Litres sold (M)"
    }
  }, {
    "type": "ValueAxis",
    "id": "v2",
    "title": {
      "text": "Units sold"
    },
    "renderer": {
      "opposite": true
    }
  }],
  "series": [{
    "type": "ColumnSeries",
    "dataFields": {
      "valueY": "litres",
      "categoryY": "country"
    },
    "name": "Sales",
    "columns": {
      "tooltipText": "Series: {name}\nCategory: {categoryX}\nValue: {valueY}",
      "stroke": "#ff0000",
      "fill": "#00ff00"
    }
  }, {
    "type": "LineSeries",
    "stroke": "#CDA2AB",
    "strokeWidth": 3,
    "dataFields": {
      "valueY": "units",
      "categoryY": "country"
    },
    "name": "Units",
    "yAxis": "v2" // referring to ValueAxis by its id
  }]
}

Here's how it turned out. Even though we have updated our "Units" series data to be orders of magnitude larger, both series are displaying nicely, because they are bound to separate axes and therefore are using different value scales.

See the Pen amCharts V4: XY chart example (multiple axes) by amCharts (@amcharts) on CodePen.

Non-standard axis setups

XY chart supporting any number and combination of axis types, allows some creative uses.

For example, you could have three horizontal DateAxis each with a different LineSeries attached to them to display comparative timelines from different periods, superimposed over each other. E.g. show metric dynamics from different years.

Or you could have just two ValueAxis (vertical and horizontal) to turn it into scatter plot.

As an example, let us show how you can easily create a Heatmap chart by utilizing two CategoryAxis - one vertical and one horizontal.

See the Pen amCharts V4: XY chart example (heatmap) by amCharts (@amcharts) on CodePen.

The above demo, besides our regular suspects - series and axes - also introduces a concept of HeatLegend.

MORE INFO Learn more about HeatLegend in "Legend" article.

Smoothed lines

In amCharts 4, each Line series are straight or curved (smoothed). By default all lines are straight - connecting two data points with a straight line.

To smooth the lines out, we have two Line series settings: tensionX and tensionY.

A tension measures how much line is attracted to the data point horizontally (tensionX) ant vertically (tensionY).

Values can be either 0 - no tension at all, and 1 - maximum tension.

Default setting is 1 - maximum tension, resulting in straight lines.

 

NOTE If tensionY is lower than default 1, the line may and probably will dip or spike below or above actual values, which may create the illusion of inflated values. Use with caution.

Alternate smoothing algorithm

So far we have looked at the default smoothing alogrithm, which can be modified with the tensionX and tensionY settings.

LineSeries supports two additional alternative algorithms, enabled via series' setting smoothing: "monotoneX", and "monotoneY".

These two settings do not support any additional properties - tensionX and tensionY will be ignored if set.

The upside of using these two is that it will ensure correct smoothing on irregularly spaced data items, as well as make the line not go beyond the actual value vertically, or horizontally.

Use monotoneX for horizontal series, and monotoneY for vertical ones.

series.smoothing = "monotoneX";
series.smoothing = "monotoneX";
{
  // ...
  "series": [{
    // ...
    "smoothing": "monotoneX"
  }]
}

See the Pen LineSeries with monotoneX smoothing by amCharts team (@amcharts) on CodePen.

Related tutorials

Related demos