Data

This tutorial will look at how data is loaded, updated, and used in amCharts 5.

The data object

All amCharts 5 components that use data have a property named data.

It's not a simple storage for data but rather object based on class ListData.

Any data manipulation - setting data, inserting, removing, or updating values for data points - is done via many of the ListData methods. The object will take care of the rest - no need to let the chart know we've updated something - it will just happen.

In this tutorial, we will cover the most common of ListData's methods, but make sure you visit its full class reference for more options.

Setting data

Data set set via ListData object of the component that is direct user of the data.

For example, in XY Chart the users of data are its series, not the chart itself.

Chart's legend is another example of a data user.

To set initial data or replace all of the current data, we use setAll() method:

series.data.setAll([{
  category: "Research",
  value: 1000
}, {
  category: "Marketing",
  value: 1200
}, {
  category: "Sales",
  value: 850
}]);
series.data.setAll([{
  category: "Research",
  value: 1000
}, {
  category: "Marketing",
  value: 1200
}, {
  category: "Sales",
  value: 850
}]);

Using setAll() will overwrite any data that may have been set previously. The series will be immediately redrawn to reflect the new data.

IMPORTANT It's a good practice to make sure that setting data happens as late into code as possible. Once you set data, all related objects are created, so any configuration settings applied afterwards might not carry over.

Data order

IMPORTANTDate axis expects series data to be sorted in ascending order. Otherwise, chart will be prone to all kinds of plotting anomalies.

Incremental updates

Adding single data point

To add a single data point to the end of the data, we use push() method:

series.data.push({
  category: "HR",
  value: 500
});
series.data.push({
  category: "HR",
  value: 500
});

Should we want to add data points to the beginning of data, we would use unshift() instead:

series.data.unshift({
  category: "HR",
  value: 500
});
series.data.unshift({
  category: "HR",
  value: 500
});

We can insert new data point at specific index, too. For that we'd use insertIndex(). The function takes index (zero-based) and value to insert into data:

series.data.insertIndex(2, {
  category: "HR",
  value: 500
});
series.data.insertIndex(2, {
  category: "HR",
  value: 500
});

Adding multiple data points

Similarly, to add multiple data points to the end of the data, we can use pushAll() method:

series.data.pushAll([{
  category: "HR",
  value: 500
}, {
  category: "Logistics",
  value: 450
}]);
series.data.pushAll([{
  category: "HR",
  value: 500
}, {
  category: "Logistics",
  value: 450
}]);

Updating existing data

To update existing data points, we use method setIndex().

The method takes index (zero-based) and new data as parameters. For example, let's update values of the second data point:

series.data.setIndex(1, {
  category: "Marketing",
  value: 1000
});
series.data.setIndex(1, {
  category: "Marketing",
  value: 1000
});

Calling setIndex() will take care of all the recalculation and redrawing of the required elements. It will even animate from one value to another, if we have animations enabled.

See the Pen Stacked column chart by amCharts team (@amcharts) on CodePen.

Removing data items

If we need to remove data items, we can use removeIndex() method:

series.data.removeIndex(0);
series.data.removeIndex(0);

The above will remove the first data item from series.

Pre-processing data

ListData comes with a capability to pre-process the data before it is passed onto a chart.

For that it has a property: processor. It can be set to an instance of DataProcessor, which can do the following:

  • Convert string-based dates or Date objects into timestamps, which is expected format for time-based data in amCharts 5.
  • Convert string-based numbers into proper numeric values.
  • Replace empty values with something else.

Creating a processor

Like with any object in amCharts 5, data processor is created using its new() static method:

series.data.processor = am5.DataProcessor.new(root, {});
series.data.processor = am5.DataProcessor.new(root, {});

The above will create the processor, which will do nothing, until we set some of its properties.

IMPORTANT In amCharts 5, data is parsed and processed immediately after it is set (e.g. via data.setAll() call), so the processor needs to be set before setting actual data, or it won't kick in.

Data mutation

Data processor will manipulate values in the source array!

If you need your source data to remain intact, you may need to make a hard copy of the data and pass it into series instead.

This is also why you don't need to define data processors for multiple series if they use the same data.

// Source data
let data = [{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}];

// Creating processor only for the first series
series1.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"]
});

// Setting data
series1.data.setAll(data);
series2.data.setAll(data);
series3.data.setAll(data);
// Source data
var data = [{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}];

// Creating processor only for the first series
series1.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"]
});

// Setting data
series1.data.setAll(data);
series2.data.setAll(data);
series3.data.setAll(data);

Parsing numeric values

Chart expects numbers in data where numeric data fields are used. If we have values supplied as strings in our data, the chart will fail.

We can use data processor to automatically convert those strings into numeric values, by setting numericFields property:

// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"]
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}]);
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"]
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}]);

Parsing dates

If we use a date axis in our chart, the values that specify date/time will need to come in JavaScript timestamp format, which is number of milliseconds since "UNIX epoch".

If our data is in any other way we may need to tell data processor to convert those dates into timestamp.

To specify what fields hold dates we use dateFields:

// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"]
});

series.data.setAll([{
  date: new Date(2021, 0, 1),
  value: "1000"
}, {
  date: new Date(2021, 0, 2),
  value: "800"
}]);
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"]
});

series.data.setAll([{
  date: new Date(2021, 0, 1),
  value: "1000"
}, {
  date: new Date(2021, 0, 2),
  value: "800"
}]);

The above example uses Date objects to specify dates.

If our data holds dates as strings, we will additionally have to specify dateFormat so that processor knows how to parse it into a timestamp:

// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"],
  dateFormat: "yyyy-MM-dd"
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}]);
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"],
  dateFormat: "yyyy-MM-dd"
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000"
}, {
  date: "2021-01-02",
  value: "800"
}]);

MORE INFO The date format is described in "Formatting dates" tutorial.

The following format codes are supported when parsing dates:

PeriodCodeNo.ExampleComment
eraG1AD"AD" or "BC"
yeary1..n1996Calendar year.
Y1..n1997Week year.
monthM1..209One or two digit month number.
3SepShort month name.
4SeptemberFull month name.
weekw1..227Week of the year.
W13Week of the month.
dayd1..21Day of the month.
D1..3345Day of the year.
am/pma1AM"AM" or "PM"
2A.M."A.M." or "P.M."
3A"A" or "P"
hourh1..211Hour [1-12].
H1..213Hour [0-23].
K1..20Hour [0-11].
k1..224Hour [1-24].
minutem1..259Minute.
seconds1..212Second.
S1..23456Fractional Second.
x1..n1507908460868Timestamp.
n1..3029Milliseconds.
zoneZ1GMT-08:00Time zone in GMT format.
2-0800Time zone in RFC 822 format.
otheri12017-10-14T05:24:17.872ZDate/time formatted according to ISO8601 format.

NOTE If dateFormat is not set, and processor encounters a string-based value it needs to parse, it will use chart-wide date format (root.dateFormatter.dateFormat).

See the Pen amCharts v5: Stacked step lines by amCharts team (@amcharts) on CodePen.

Parsing colors

Colors in amCharts 4 are represented by Color objects. If you need data processor to automatically convert string-based or hex color codes to Color objects, you can use colorFields.

Data processor recognizes the following formats:

  • Hex strings: "#ff0000"
  • RGBA notation: "rgb(255, 0, 0)" or "rgba(255, 0, 0, 1)"
  • Hex numbers: 0xff0000 or 16711680
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  colorFields: ["color"]
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000",
  color: "#ff0000"
}, {
  date: "2021-01-02",
  value: "800",
  color: "#ff0000"
}]);
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  colorFields: ["color"]
});

series.data.setAll([{
  date: "2021-01-01",
  value: "1000",
  color: "#ff0000"
}, {
  date: "2021-01-02",
  value: "800",
  color: "#ff0000"
}]);

Deep processing

Processor can convert input values from deeper objects, too.

numericFields, dateFields, and colorFields accept strings using dot notation to identify hierarchy.

For example consider data like this:

[{
  date: new Date(2021, 0, 1),
  value: 1000,
  columnSettings: {
    fill: "#d6e681"
  }
}, {
  date: new Date(2021, 0, 2),
  value: 800,
  columnSettings: {
    fill: "#babf95"
  }
}, {
  date: new Date(2021, 0, 3),
  value: 700,
  columnSettings: {
    fill: "#c4ad83"
  }
}, {
  date: new Date(2021, 0, 4),
  value: 1200,
  columnSettings: {
    fill: "#c6b677"
  }
}, {
  date: new Date(2021, 0, 5),
  value: 740,
  columnSettings: {
    fill: "#dbb957"
  }
}]

We can use "columnSettings.fill" to allow processor to get to resolving fill color:

series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"],
  colorFields: ["columnSettings.fill"]
});
series.data.processor = am5.DataProcessor.new(root, {
  numericFields: ["value"],
  dateFields: ["date"],
  colorFields: ["columnSettings.fill"]
});

Replacing empty values

In some cases we need to replace empty values (e.g. null or empty strings) with something else. That's where processors emptyAs setting comes in:

// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  emptyAs: 0
});

series.data.setAll([{
  category: "Research",
  value: 1000
}, {
  category: "Marketing",
  value: null
}, {
  category: "Sales",
  value: 850
}]);
// Processor needs to be set before data
series.data.processor = am5.DataProcessor.new(root, {
  emptyAs: 0
});

series.data.setAll([{
  category: "Research",
  value: 1000
}, {
  category: "Marketing",
  value: null
}, {
  category: "Sales",
  value: 850
}]);

In the above, without the processor, a data point for "Marketing" would be not plotted. With data processor, it will be replaced with a zero, which is a proper numeric value and thus will plot the data point.

External data

Loading

amCharts 5 comes with a helper utility am5.net.load().

It takes at least one argument (a URL of the request) and returns a Promise object with net load result (INetLoadResult).

am5.net.load("/data/mydata.json").then(function(result) {
  // This gets executed when data finishes loading
  // ... do something
  console.log(result.response);
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});
am5.net.load("/data/mydata.json").then(function(result) {
  // This gets executed when data finishes loading
  // ... do something
  console.log(result.response);
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});

The result passed to handler functions is an object that implements INetLoadResult interface. It contains a bunch of useful references, but the most important one is response, which contains the actual output of the requested URL.

MORE INFO There are more to am5.net.load() than the bare minimum outlined above. Please refer to "Getting the most out of net.load utility" tutorial for information about passing in target data user object, setting HTTP request options, and more.

Parsing

Now that we've set up loading of data, we need to parse it before it can be used by chart.

amCharts 5 comes with two build-in parsers: CSV and JSON.

Those are represented by classes CSVParser and JSONParser respetively, both of which have a static method parse() so they do not need to be instantiated as objects.

parse() method takes two parameters:

  • data - input data as a string.
  • options (optional) - format-specific options.

It returns parsed data, which can be set directly to its intended user, e.g. series:

am5.net.load("/data/mydata.json").then(function(result) {
  // This gets executed when data finishes loading
  series.data.setAll(am5.JSONParser.parse(result.response));
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});
am5.net.load("/data/mydata.json").then(function(result) {
  // This gets executed when data finishes loading
  series.data.setAll(am5.JSONParser.parse(result.response));
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});

CSV format can have a lot of configuration options, such as column delimiters, column names, etc.

am5.net.load("/data/mydata.csv").then(function(result) {
  // This gets executed when data finishes loading
  series.data.setAll(am5.CSVParser.parse(result.response, {
    delimiter: ";",
    reverse: true,
    skipEmpty: true,
    useColumnNames: true
  }));
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});
am5.net.load("/data/mydata.csv").then(function(result) {
  // This gets executed when data finishes loading
  series.data.setAll(am5.CSVParser.parse(result.response, {
    delimiter: ";",
    reverse: true,
    skipEmpty: true,
    useColumnNames: true
  }));
}).catch(function(result) {
  // This gets executed if there was an error loading URL
  // ... handle error
  console.log("Error loading " + result.xhr.responseURL);
});

We can also use standalone data parser to parse date/numeric/color fields in data after it is parsed:

am5.net.load("/data/mydata.csv").then(function(result) {
  
  // Parse data
  let data = am5.CSVParser.parse(result.response, {
    delimiter: ";",
    reverse: true,
    skipEmpty: true,
    useColumnNames: true
  });

  // Process data
  let processor = am5.DataProcessor.new(root, {
    dateFields: ["date"],
    dateFormat: "yyyy-MM-dd",
    numericFields: ["value", "volume"]
  });
  processor.processMany(data);

  // Use parsed/processed data
  series.data.setAll(data);

});
am5.net.load("/data/mydata.csv").then(function(result) {
  
  // Parse data
  var data = am5.CSVParser.parse(result.response, {
    delimiter: ";",
    reverse: true,
    skipEmpty: true,
    useColumnNames: true
  });

  // Process data
  var processor = am5.DataProcessor.new(root, {
    dateFields: ["date"],
    dateFormat: "yyyy-MM-dd",
    numericFields: ["value", "volume"]
  });
  processor.processMany(data);

  // Use parsed/processed data
  series.data.setAll(data);

});

External data example

The following Gantt chart example combines a few techniques described above, like loading and parsing external data, as well as using data processor to parse date and color fields.

See the Pen Gantt Chart with external data by amCharts team (@amcharts) on CodePen.

User data

Each element in amCharts 5 can have any arbitrary data attached to it using its userData setting.

This setting is not used by chart in any way, and acts purely as custom data storage for later retrieval/use from the object.

It can be set using set() or setAll() methods:

chart.set("userData", {
  foo: "bar"
});
chart.set("userData", {
  foo: "bar"
});

Or via new() syntax:

let yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
  extraTooltipPrecision: 1,
  userData: {
    foo: "bar",
    someArbitraryValue: 100
  },
  renderer: am5xy.AxisRendererY.new(root, {
    minGridDistance: 30
  })
}));
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
  extraTooltipPrecision: 1,
  userData: {
    foo: "bar",
    someArbitraryValue: 100
  },
  renderer: am5xy.AxisRendererY.new(root, {
    minGridDistance: 30
  })
}));

As with any other setting, it can be retrieved using get() method:

chart.get("userData");
chart.get("userData");

Related content