Tutorials & Demos – amCharts 5 Documentation https://www.amcharts.com/docs/v5 Fri, 25 Feb 2022 14:24:35 +0000 en-US hourly 1 https://wordpress.org/?v=5.9.1 https://www.amcharts.com/docs/v5/wp-content/uploads/sites/7/2021/06/favicon-128.png Tutorials & Demos – amCharts 5 Documentation https://www.amcharts.com/docs/v5 32 32 Syncing axis widths across multiple charts https://www.amcharts.com/docs/v5/tutorials/syncing-axis-widths-across-multiple-charts/ Fri, 25 Feb 2022 14:10:35 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=204552 This demo shows how we can use events and "ghost labels" to sync width of vertical axes across multiple charts.

See the Pen
Stacked axes
by amCharts team (@amcharts)
on CodePen.0

]]>
Custom loading indicator https://www.amcharts.com/docs/v5/tutorials/custom-loading-indicator/ Mon, 21 Feb 2022 09:38:26 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=201867 This tutorial will show how we can build a custom loading indicator that can be toggled on and off as needed.

The final loading indicator will contain following elements:

  • Main container / curtain.
  • Label
  • Icon

Let's walk through all of them step-by-step.

Main container / curtain

We will use Container object, placed over chart to add all other elements to.

We will also use its semi-transparent background to as a "curtain" to dim out underlying chart.

let indicator = root.container.children.push(am5.Container.new(root, {
  width: am5.p100,
  height: am5.p100,
  layer: 1000,
  background: am5.Rectangle.new(root, {
    fill: am5.color(0xffffff),
    fillOpacity: 0.7
  })
}));
var indicator = root.container.children.push(am5.Container.new(root, {
  width: am5.p100,
  height: am5.p100,
  layer: 1000,
  background: am5.Rectangle.new(root, {
    fill: am5.color(0xffffff),
    fillOpacity: 0.7
  })
}));

Note, how we are placing the container directly into root element so that we cover all of its area.

The layer: 1000 ensures that indicator is placed into a separate layer, well above any other elements in the chart.

Label

Now, let's add a Label as a child to the main container:

indicator.children.push(am5.Label.new(root, {
  text: "Loading...",
  fontSize: 25,
  x: am5.p50,
  y: am5.p50,
  centerX: am5.p50,
  centerY: am5.p50
}));
indicator.children.push(am5.Label.new(root, {
  text: "Loading...",
  fontSize: 25,
  x: am5.p50,
  y: am5.p50,
  centerX: am5.p50,
  centerY: am5.p50
}));

The x, y, centerX, and centerY settings ensure that our label is centered within container vertically and horizontally.

Icon

We will use an element Graphics for the icon.

It's easy to use, since we can use its setting svgPath to add actual icon shape:

let hourglass = indicator.children.push(am5.Graphics.new(root, {
  width: 32,
  height: 32,
  fill: am5.color(0x000000),
  x: am5.p50,
  y: am5.p50,
  centerX: am5.p50,
  centerY: am5.p50,
  dy: -45,
  svgPath: "M12 5v10l9 9-9 9v10h24V33l-9-9 9-9V5H12zm20 29v5H16v-5l8-8 8 8zm-8-12-8-8V9h16v5l-8 8z"
}));
var hourglass = indicator.children.push(am5.Graphics.new(root, {
  width: 32,
  height: 32,
  fill: am5.color(0x000000),
  x: am5.p50,
  y: am5.p50,
  centerX: am5.p50,
  centerY: am5.p50,
  dy: -45,
  svgPath: "M12 5v10l9 9-9 9v10h24V33l-9-9 9-9V5H12zm20 29v5H16v-5l8-8 8 8zm-8-12-8-8V9h16v5l-8 8z"
}));

NOTEIf we wanted to use an external image - say a PNG - we could use element of type Picture (more about using images).

As a final touch, let's also add an animation, which rotates the icon:

var hourglassanimation = hourglass.animate({
  key: "rotation",
  to: 180,
  loops: Infinity,
  duration: 2000,
  easing: am5.ease.inOut(am5.ease.cubic)
});
var hourglassanimation = hourglass.animate({
  key: "rotation",
  to: 180,
  loops: Infinity,
  duration: 2000,
  easing: am5.ease.inOut(am5.ease.cubic)
});

NOTEFor more information about animations, refer to "Animations" tutorial.

Toggling on/off

Now that we have our indicator ready, we can turn it off by calling its hide() method.

Similarly, we can reveal it using show().

Let's build a function that toggles the indicator:

function toggleIndicator() {
  if (indicator.isHidden()) {
    hourglassanimation.play();
    indicator.show();
  }
  else {
    hourglassanimation.pause();
    indicator.hide();
  }
}
function toggleIndicator() {
  if (indicator.isHidden()) {
    hourglassanimation.play();
    indicator.show();
  }
  else {
    hourglassanimation.pause();
    indicator.hide();
  }
}

Please note that we also pause the animation when indicator is hidden. No point in wasting resources on animation that is not visible.

Working demo

See the Pen
amCharts 5: Loading indicator
by amCharts team (@amcharts)
on CodePen.0

]]>
Using adapters on category axis labels https://www.amcharts.com/docs/v5/tutorials/using-adapters-on-category-axis-labels/ Sun, 20 Feb 2022 16:04:22 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=201863 This demo shows how we can dynamically modify labels of the CategoryAxis using an adapter.

We need to add an adapter on an axis label template, which is accessible via axis' renderer:

xAxis.get("renderer").labels.template.adapters.add("text", function(text, target) {
  if (target.dataItem && target.dataItem.dataContext) {
    return target.dataItem.dataContext.categoryLabel;
  }
  return text;
});
xAxis.get("renderer").labels.template.adapters.add("text", function(text, target) {
  if (target.dataItem && target.dataItem.dataContext) {
    return target.dataItem.dataContext.categoryLabel;
  }
  return text;
});

In this particular example, actual category name is being replaced with a different value extracted from data.

See the Pen
amCharts 5: Using adapaters on CategoryAxis labels
by amCharts team (@amcharts)
on CodePen.0

]]>
Triggering bullet hover with an XY cursor https://www.amcharts.com/docs/v5/tutorials/triggering-bullet-hover-with-an-xy-cursor/ Sun, 20 Feb 2022 15:42:17 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=201859 This demo shows how we can use XYCursor event "cursormoved" to automatically apply hover state to each bullet in the same category.

See the Pen
Triggering bullet hover with cursor
by amCharts team (@amcharts)
on CodePen.0

]]>
Show only one series at a time https://www.amcharts.com/docs/v5/tutorials/show-only-one-series-at-a-time/ Fri, 18 Feb 2022 06:52:42 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=201850 This demo shows how we can use series settings events to ensure only one series is shown on an XY chart at a time, by toggling off other series automatically.

See the Pen
Show only one series at a time
by amCharts team (@amcharts)
on CodePen.0

]]>
One pulled slice per pie series https://www.amcharts.com/docs/v5/tutorials/one-pulled-slice-per-pie-series/ Mon, 14 Feb 2022 08:46:14 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=201843 Normally, when you click/tap a slice on a Pie chart, it would pull out a little. You can have multiple slices pulled out that way. This tutorial will show how you can set up Pie chart in order to allow only one slice to be pulled at any given time.

Task

We want the slice to pull out when clicked/tapped. This part is already covered.

We also want currently pulled out slice to pull back into place when some other slice is pulled out. This we'll need to implement.

Solution

We are going to use slice template's "click" event to set up a custom function, which checks for any currently pulled out slices and makes them go back.

NOTEFor more information on how events work in amCharts 5, read "Events" tutorial. Also make sure to read about the concept of "List templates".

OK, so whenever a slice is pulled out, it becomes "active" (it's "active" setting is set to true). And vice versa, setting it to false will make the slice pop back into its place.

Also, when a slice (or any other object for that matter) is clicked/tapped, it generates a "click" event. If there's an event handler defined, it is executed.

Let's use these two bits of code to implement our task:

series.slices.template.events.on("click", function(ev) {
  series.slices.each(function(slice) {
    if (slice != ev.target && slice.get("active")) {
      slice.set("active", false);
    }
  })
});
series.slices.template.events.on("click", function(ev) {
  series.slices.each(function(slice) {
    if (slice != ev.target && slice.get("active")) {
      slice.set("active", false);
    }
  })
});

And here's the complete working example.

See the Pen
amCharts 5: One pulled slice per series
by amCharts team (@amcharts)
on CodePen.0

]]>
Creating multi-content PDF export https://www.amcharts.com/docs/v5/tutorials/creating-multi-content-pdf-export/ Tue, 25 Jan 2022 09:17:20 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=193802 This extensive tutorial will show how you can use pdfmake library, which is bundled with amCharts 5 plugin Exporting, to generate full page, multi-content report PDF documents.

Disclaimer

Majority of this tutorial is based on using pdfmake. It's an excellent third party library enabling generating PDF documents in JavaScript. Our aim is to walk you through basics of using pdfmake. For advanced usage, support, and community head over to pdfmake page.

The task

To start with, let's say we want to generate something like this:

Here's a live version:

See the Pen
amCharts 5: Multi-content export
by amCharts team (@amcharts)
on CodePen.0

Building a PDF

Preparing

Let's create a function, that will handle the exporting. It can be invoked with a press of a button, or on some other event.

function savePDF() {
// This is where the magic will be happening
}
function savePDF() {
// This is where the magic will be happening
}

Getting a pdfmake object

You already know that we will be using pdfmake to build our PDF.

It comes with the plugin Exporting, so we will need to import or load it first:

import * as am5exporting from "@amcharts/amcharts5/plugins/exporting";
<script src="https://cdn.amcharts.com/lib/5/plugins/exporting.js"></script>

Then create a plugin instance:

let exporting = am5plugins_exporting.Exporting.new(root, {
  menu: am5plugins_exporting.ExportingMenu.new(root, {})
});
var exporting = am5plugins_exporting.Exporting.new(root, {
  menu: am5plugins_exporting.ExportingMenu.new(root, {})
});

We'll grab a pdfmake object right out of the plugin's instance, using its getPdfmake() method:

function savePDF() {
  exporting.getPdfmake().then(function(pdfmake) {
     // pdfmake is ready
     // ...
  );
}
function savePDF() {
  exporting.getPdfmake().then(function(pdfmake) {
     // pdfmake is ready
     // ...
  );
}

Since pdfmake is loaded dynamically on-demand, the async accessor exporting.getPdfmake() returns a Promise, not the object itself. This is why we are using then() clause, which kicks in when pdfmake is loaded and ready.

All of the subsequent code in this tutorial will go inside this promise handler.

NOTEWhile we will need to create an exporting object for each chart individually, we only need one pdfmake object, so we'll do it for the first chart only.

Chart snapshots

Besides getting pdfmake object we have a few other asynchronous operations on our hands: getting snapshots of all the charts.

This is done via exporting object's export() method. Just like getPdfmake(), it returns a Promise.

Since we need all of those to create a document, let's combine all of them into a single Promise.all call.

function savePDF() {
  let exporting = am5plugins_exporting.Exporting.new(root, {
    menu: am5plugins_exporting.ExportingMenu.new(root, {})
  });
  
  let exporting2 = am5plugins_exporting.Exporting.new(root2, {
    menu: am5plugins_exporting.ExportingMenu.new(root2, {})
  });
  
  let exporting3 = am5plugins_exporting.Exporting.new(root3, {
    menu: am5plugins_exporting.ExportingMenu.new(root3, {})
  });
  
  let exporting4 = am5plugins_exporting.Exporting.new(root4, {
    menu: am5plugins_exporting.ExportingMenu.new(root4, {})
  });
  
  Promise.all([
    exporting.getPdfmake(),
    exporting.export("png"),
    exporting2.export("png"),
    exporting3.export("png"),
    exporting4.export("png")
  ]).then(function(res) { 
     // pdfmake and chart snapshots are ready
     // res[0] contains pdfmake instance
     // res[1] contains shapshot of chart 1
     // etc.
     let pdfMake = res[0];
  );
}
function savePDF() {
  var exporting = am5plugins_exporting.Exporting.new(root, {
    menu: am5plugins_exporting.ExportingMenu.new(root, {})
  });
  
  var exporting2 = am5plugins_exporting.Exporting.new(root2, {
    menu: am5plugins_exporting.ExportingMenu.new(root2, {})
  });
  
  var exporting3 = am5plugins_exporting.Exporting.new(root3, {
    menu: am5plugins_exporting.ExportingMenu.new(root3, {})
  });
  
  var exporting4 = am5plugins_exporting.Exporting.new(root4, {
    menu: am5plugins_exporting.ExportingMenu.new(root4, {})
  });
  
  Promise.all([
    exporting.getPdfmake(),
    exporting.export("png"),
    exporting2.export("png"),
    exporting3.export("png"),
    exporting4.export("png")
  ]).then(function(res) { 
     // pdfmake and chart snapshots are ready
     // res[0] contains pdfmake instance
     // res[1] contains shapshot of chart 1
     // etc.
     var pdfMake = res[0];
  );
}

MORE INFO For more about getting chart snapshots read here. To learn about Promise.all check this MDN article.

Document definition

Now that we have all of our async stuff loaded and ready, let's create a PDF document definition.

pdfmake uses a structured JavaScript object to describe the documents:

// pdfmake is ready
// Create document template
let doc = {
pageSize: "A4",
pageOrientation: "portrait",
pageMargins: [30, 30, 30, 30],
content: []
};
// pdfmake is ready
// Create document template
var doc = {
pageSize: "A4",
pageOrientation: "portrait",
pageMargins: [30, 30, 30, 30],
content: []
};

As you may already guessed, all content that needs to go into PDF document will be included as objects into content array.

MORE INFOThe document definition is described in great detail in pdfmake documentation. Make sure you read it for much more usage tips and options.

Headers and text

Now that we have our (blank) PDF document definition object ready, let's add some text to it.

doc.content.push({
text: "In accumsan velit in orci tempor",
fontSize: 20,
bold: true,
margin: [0, 20, 0, 15]
});

doc.content.push({
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...",
fontSize: 15,
margin: [0, 0, 0, 15]
});
doc.content.push({
text: "In accumsan velit in orci tempor",
fontSize: 20,
bold: true,
margin: [0, 20, 0, 15]
});

doc.content.push({
text: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...",
fontSize: 15,
margin: [0, 0, 0, 15]
});

NOTEThe margins in pdfmake margin definition do not start with top as in CSS, but rather from left and go clockwise: left, top, right bottom.

MORE INFOFor more information and options for adding text read here.

Charts

Adding images is similar to text.

We already have snapshots of our charts in res array, so we can start pushing those into document content.

doc.content.push({
image: res[1],
width: 530
});
doc.content.push({
image: res[1],
width: 530
});

width is important. Without it, your image might not fit into the page. The full width of the A4 page is 595.28 (Letter is 612).

Make sure you take document's margins into account when calculating allowed image width.

MORE INFOFor more info about adding images read here.

Tables

Now, let's add a table.

doc.content.push({
table: {
headerRows: 1,
widths: [ "*", "*", "*", "*" ],
body: [
[
{ text: "USA", bold: true },
{ text: "Japan", bold: true },
{ text: "France", bold: true },
{ text: "Mexico", bold: true }
],
[ "2500", "2500", "2200", "1200" ],
[ "800", "1200", "990", "708" ],
[ "2100", "2150", "900", "1260" ],
]
}
});
doc.content.push({
table: {
headerRows: 1,
widths: [ "*", "*", "*", "*" ],
body: [
[
{ text: "USA", bold: true },
{ text: "Japan", bold: true },
{ text: "France", bold: true },
{ text: "Mexico", bold: true }
],
[ "2500", "2500", "2200", "1200" ],
[ "800", "1200", "990", "708" ],
[ "2100", "2150", "900", "1260" ],
]
}
});

MORE INFOThis page in pdfmake documentation has more details about adding and formatting tables.

Columns

Next up: two charts set side by side.

For that we'll need to utilize content columns.

doc.content.push({
columns: [{
image: res[2],
width: 250
}, {
image: res[3],
width: 250
}],
columnGap: 30
});
doc.content.push({
columns: [{
image: res[2],
width: 250
}, {
image: res[3],
width: 250
}],
columnGap: 30
});

Columns work like mini-content: you just add regular content definition objects, except they will be laid out horizontally next to each other.

We can add any content to columns, mix and match them, like for example image and text:

doc.content.push({
columns: [{
image: res[4],
width: 150
}, {
stack: [{
text: "Maecenas congue leo vel tortor faucibus, non semper odio viverra...",
fontSize: 15,
margin: [0, 0, 0, 15]
}, {
text: "Fusce sed quam pharetra, ornare ligula id, maximus risus...",
fontSize: 15,
margin: [0, 0, 0, 15]
}],
width: "*"
}],
columnGap: 30
});
doc.content.push({
columns: [{
image: res[4],
width: 150
}, {
stack: [{
text: "Maecenas congue leo vel tortor faucibus, non semper odio viverra...",
fontSize: 15,
margin: [0, 0, 0, 15]
}, {
text: "Fusce sed quam pharetra, ornare ligula id, maximus risus...",
fontSize: 15,
margin: [0, 0, 0, 15]
}],
width: "*"
}],
columnGap: 30
});

MORE INFOAs usual, more details and options in pdfmake docs: columns and stacks of paragraphs.

Download & print

That's it. We're done configuring our document. Let's trigger download.

pdfmake has that included:

pdfMake.createPdf(doc).download("report.pdf");
pdfMake.createPdf(doc).download("report.pdf");

Or if we'd like to print it:

pdfMake.createPdf(doc).print();
pdfMake.createPdf(doc).print();

MORE INFOFor more options about how to handle final PDF document read this page of pdfmake documentation.

Full example

See the Pen
amCharts 5: Multi-content export
by amCharts team (@amcharts)
on CodePen.0

]]>
Disabling context menu on chart https://www.amcharts.com/docs/v5/tutorials/disabling-context-menu-on-chart/ Mon, 24 Jan 2022 12:55:24 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=193792 Normally, browsers will display a context menu when you press right mouse button over some element. If done over amCharts 5, it will display a context menu for a <canvas> element. This tutorial will show how you can disable default behavior of a right-click on canvas elements, as well as attach built-in rightclick event to apply your own logic.

Disabling built-in context menu

To disable default context menu, we'll need to cancel contextmenu event, on the <div> root element is placed in:

root.dom.addEventListener("contextmenu", function(ev) {
  ev.preventDefault();
});
root.dom.addEventListener("contextmenu", function(ev) {
  ev.preventDefault();
});

Or we can use amCharts 5' built-in utility, which returns a Disposer:

am5.utils.addEventListener(root.dom, "contextmenu", function(ev) {
  ev.preventDefault();
});
am5.utils.addEventListener(root.dom, "contextmenu", function(ev) {
  ev.preventDefault();
});

And to be completely on the safe side, we can add that disposer to root element disposer list, so when root element is disposed, the event handler goes with it:

root.addDisposer(
  am5.utils.addEventListener(root.dom, "contextmenu", function(ev) {
    ev.preventDefault();
  })
);
root.addDisposer(
  am5.utils.addEventListener(root.dom, "contextmenu", function(ev) {
    ev.preventDefault();
  })
);

Adding right-click event

With built-in context menu out of the way, we can add amCharts rightclick event on the target elements.

The following code adds one to Force-directed tree nodes:

series.nodes.template.events.on("rightclick", function(ev) {
  console.log("Right click")
});
series.nodes.template.events.on("rightclick", function(ev) {
  console.log("Right click")
});

See the Pen
amCharts 5: Disabling built-in context menu
by amCharts team (@amcharts)
on CodePen.0

]]>
Axis labels on base line https://www.amcharts.com/docs/v5/tutorials/axis-labels-on-base-line/ Mon, 24 Jan 2022 08:45:05 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=193793 This demo shows how we adapters to position X axis labels under base (zero) line, rather than fixed at the bottom of the plot area.

See the Pen
Showing axis label near 0 line
by amCharts team (@amcharts)
on CodePen.0

]]>
Auto-adjusting chart height based on a number of data items https://www.amcharts.com/docs/v5/tutorials/auto-adjusting-chart-height-based-on-a-number-of-data-items/ Thu, 20 Jan 2022 08:07:04 +0000 https://www.amcharts.com/docs/v5/?post_type=tutorials&p=191151 This tutorial will explain how you can easily make your chart grow or contract based on an actual number of data.

The problem

Let's take a simple bar chart:

It looks OK.

However, it height is fixed, so if we had more bars, it might start looking a bit awkward:

Or, if there are too few, we end up with huge bars wasting up space:

This is happening because the category axis will always try to adjust size of the bars for a better fit.

However, we can't always anticipate number of bars in our data, and therefore cannot hardcode the height of the chart container.

The solution

An obvious solution is to have chart container auto-adjust its height.

We will do that by utilizing series' "datavalidated" event, which kicks in at the moment it has finished processing its data.

At that moment we already know precisely how many bars we have, so we can calculate our target height for the chart container.

let cellSize = 30;
series.events.on("datavalidated", function(ev) {

  let series = ev.target;
  let chart = series.chart;
  let xAxis = chart.xAxes.getIndex(0);

  // Calculate how we need to adjust chart height
  let chartHeight = series.data.length * cellSize + xAxis.height() + chart.get("paddingTop", 0) + chart.get("paddingBottom", 0);

  // Set it on chart's container
  chart.root.dom.style.height = chartHeight + "px";
});
var cellSize = 30;
series.events.on("datavalidated", function(ev) {

  var series = ev.target;
  var chart = series.chart;
  var xAxis = chart.xAxes.getIndex(0);

  // Calculate how we need to adjust chart height
  var chartHeight = series.data.length * cellSize + xAxis.height() + chart.get("paddingTop", 0) + chart.get("paddingBottom", 0);

  // Set it on chart's container
  chart.root.dom.style.height = chartHeight + "px";
});

See the Pen
Untitled
by amCharts team (@amcharts)
on CodePen.0

]]>