Skip to content

Generating static chart snapshots

This tutorial will introduce to a concept of generating static snapshots of the charts.

In some cases, we do not need chart interactivity.

For example, if we want to show a grid with multiple sparklines/microcharts, we might need only the visual representation.

In such a case it would be waste of resources - memory and CPU - to keep multiple interactive charts on the page.

This is where generating static snapshots comes in handy.

The process involves:

  • Creating a temporary <div> element.
  • Creating a live chart in it.
  • Getting a snapshot of the chart as a static image or canvas.
  • Disposing the live chart as well as the temporary <div> element.
  • Placing the static chart image where we want it to be on the page.

Since chart creation as well as its export are asynchronous we will be using Promises.

We will also be using amCharts 5 plugin Exporting for generating static snapshot of the chart.

The following function takes these parameters:

  • width and height - dimensions for the temporary <div> element, that the chart will be created in.
  • chartFunc - a function that actually creates the chart. It should accept a reference to a <div> element that it should create itself in, as well as data to be used for the chart. It should return a Root element it creates. Modify this for your needs.
  • data - chart data, to be passed on to chartFunc.

The chartFunc returns a Promise, which when resolves receives a full-fledged <canvas> element.

function getChartCanvas(width, height, chartFunc, data) {
// Create promise
const promise = new Promise(function(resolve, reject) {
// Create temporary container for chart
let div = document.createElement("div");
div.style.width = width + "px";
div.style.height = height + "px";
div.style.position = "absolute";
div.style.top = "-1000px";
document.body.appendChild(div);
// Generate chart
const root = chartFunc.call(this, div, data);
let timeout;
root.events.on("frameended", function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
// Export chart
const exporting = am5plugins_exporting.Exporting.new(root, {
backgroundOpacity: 0
});
exporting.getCanvas({}).then(function(canvas) {
canvas.style.position = "relative";
canvas.style.top = "";
canvas.style.width = width + "px";
canvas.style.height = height + "px";
// Remove container and chart
root.dispose();
document.body.removeChild(div);
resolve(canvas);
});
}, 100);
});
});
return promise;
}

Most of the code above is self-explanatory, but the "frameended" part is worth exploring further.

As we mentioned before, chart creation is an asynchronous process. This is why we can’t start exporting it as soon as chart is created, lest we get a blank canvas.

To fix that, we are debouncing our export code 100ms until "frameended" events stop kicking in - at which point we know that the chart has finished building itself completely.

If we’d prefer a PNG image instead of canvas, we can modify the exporting function to return image instead:

function getChartImage(width, height, chartFunc, data) {
// Create promise
const promise = new Promise(function(resolve, reject) {
// Create temporary container for chart
const div = document.createElement("div");
div.style.width = width + "px";
div.style.height = height + "px";
div.style.position = "absolute";
div.style.top = "-1000px";
document.body.appendChild(div);
// Generate chart
const root = chartFunc.call(this, div, data);
let timeout;
root.events.on("frameended", function() {
if (timeout) {
clearTimeout(timeout);
}
timeout = setTimeout(function() {
// Export chart
const exporting = am5plugins_exporting.Exporting.new(root, {
backgroundOpacity: 0
});
exporting.exportImage("png", {}).then(function(datauri) {
var img = document.createElement("img");
img.src = datauri;
img.style.position = "relative";
img.style.top = "";
img.style.width = width + "px";
img.style.height = height + "px";
// Remove container and chart
root.dispose();
document.body.removeChild(div);
resolve(img);
});
}, 100);
});
});
return promise;
}

Here’s how simple function that creates a live chart might look like:

function createVolumeChart(div, data) {
const root = am5.Root.new(div);
root.setThemes([
am5themes_Micro.new(root)
]);
const chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelX: "none",
wheelY: "none"
}));
chart.plotContainer.set("wheelable", false);
chart.zoomOutButton.set("forceHidden", true);
const xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
maxDeviation: 0,
baseInterval: { timeUnit: "day", count: 1 },
renderer: am5xy.AxisRendererX.new(root, {})
}));
const yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {})
}));
const series = chart.series.push(am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
valueYField: "volume",
valueXField: "date",
fill: am5.color(0x999999)
}));
series.data.setAll(data);
return root;
}

Here’s how we may use the snapshot function:

getChartCanvas(100, 50, createVolumeChart, data).then(function(canvas) {
document.body.appendChild(canvas);
});

The following demo uses above functions to create a grid of stock tickers with static sparklines.

Open in CodePen

And here’s the same demo exporting PNGs instead:

Open in CodePen