Handling repeating categories on Category Axis

Sometimes you will end up with an XY chart that has non-unique categories in its data. This tutorial will explain how to deal with such situation.

Base chart

We're going to start off with a very basic XY chart that has a category axis, and some matching categories in its data:

[{
  "category": "1st",
  "value": 450
}, {
  "category": "2nd",
  "value": 1200
}, {
  "category": "3rd",
  "value": 1850
}, {
  "category": "1st",
  "value": 1251
}, {
  "category": "2nd",
  "value": 599
}, {
  "category": "3rd",
  "value": 1033
}]

If we run a chart like this, the data points with matching categories will overlap:

See the Pen amCharts 4: Repeating categories (1) by amCharts (@amcharts) on CodePen.24419

That's obviously not what we were looking for.

Let's see how we can rectify the situation.

Adding category suffix

On of the most obvious solution is to make those categories unique.

For example, we could add some suffix to the categories:

[{
  "category": "1st (1)",
  "value": 450
}, {
  "category": "2nd (2)",
  "value": 1200
}, {
  "category": "3rd (3)",
  "value": 1850
}, {
  "category": "1st (4)",
  "value": 1251
}, {
  "category": "2nd (5)",
  "value": 599
}, {
  "category": "3rd (6)",
  "value": 1033
}]

That would surely fix the issue. However, we'd end up with additional numbers on our category labels, that are useless if not outright misleading to user viewing the chart:

See the Pen amCharts 4: Repeating categories (2) by amCharts (@amcharts) on CodePen.24419

Stripping suffix

Luckily, amCharts 4 has a powerful feature - Adapters - that allow using custom code to dynamically change just about anything.

We can surely use those to strip off our excess suffix from the labels:

categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text) {
  return text.replace(/ \(.*/, "");
});
categoryAxis.renderer.labels.template.adapter.add("textOutput", function(text) {
  return text.replace(/ \(.*/, "");
});
{
  // ...
  "xAxes": {
    "type": "CategoryAxis",
    // ...
    "renderer": {
      // ...
      "adapter": {
        "textOutput": function(text) {
          return text.replace(/ \(.*/, "");
        }
      }
    }
  }
}

An Adapter is basically a mean to run some value via custom function, giving a chance to modify it using custom code, before the value is displayed or used.

In the code above, we're using a "textOutput" adapter on axis' label template to replace suffix using a Regular Expression.

Let's see how it turned out:

See the Pen amCharts 4: Repeating categories (3) by amCharts (@amcharts) on CodePen.24419

Perfect!

Automated solution

If we don't want (or can't) change our source data, we may employ chart's beforedatavalidated event to add suffixes to category names automatically, before chart parses the data:

chart.events.on("beforedatavalidated", function() {
  for(let i = 0; i < chart.data.length; i++) {
    chart.data[i].category += " (" + i + ")";
  }
});
chart.events.on("beforedatavalidated", function() {
  for(var i = 0; i < chart.data.length; i++) {
    chart.data[i].category += " (" + i + ")";
  }
});
{
  // ...
  events: {
    "beforedatavalidated": function(ev) {
      var chart = ev.target;
      for(var i = 0; i < chart.data.length; i++) {
        chart.data[i].category += " (" + i + ")";
      }
    }
  }
}

See the Pen
amCharts 4: Repeating categories (3)
by amCharts team (@amcharts)
on CodePen.0