Using custom functions for data item grouping

In amCharts 4, DateAxis allows dynamic data item grouping. This tutorial will show how you can take this even further and use custom function to apply custom logic when calculating aggregate values for the groups.

Prerequisites

VERSION INFO The functionality described in this tutorial requires version 4.9.35 or later.

Before we go any further, make sure you understand how data item grouping works, and how to enable it.

The "Dynamic data item grouping" section of the DateAxis tutorial explains that in great detail.

Base chart

Here's a basic chart with dynamic data item grouping enabled.

We will be using it as a starting point throughout this tutorial.

See the Pen Using custom functions to modify aggregate values by amCharts team (@amcharts) on CodePen.24419

This chart has two ColumnSeries, using the same value as a "data field".

Naturally, both series produce identical columns, because the same aggregate value mechanism is used for both.

Let's take a look at how we can use adapters to modify aggregation mechanism.

Modifying aggregate values

The modification of the aggregated value is done via Series' groupValue adapter.

NOTE An adapter is a custom function, that can be used to dynamically modify certain values. More about adapters.

A groupValue adapter will receive and should return an object following this structure:

{
  dataItem: XYSeriesDataItem,
  interval: ITimeInterval,
  dataField: IXYSeriesDataFields,
  date: Date,
  value: number
}

dataItem is a reference to the original data item of unaggregated data. Should you need to access original data point, it's available in its dataContext.

interval indicates specific date/time interval this value is being aggregated for.

dataField is a data field, e.g. valueY.

date is the rounded date of the data item.

value is a pre-calculated aggregate value - what you would have if we were not to modify it.

To modify aggregate value, we will need to modify value property of the passed in object.

Let's do something completely useless as an example: let's randomize the numbers:

series2.adapter.add("groupValue", function(val) {
  val.value *= Math.random();
  return val;
});
series2.adapter.add("groupValue", function(val) {
  val.value *= Math.random();
  return val;
});
{
  // ...
  "series": [{
    // ...
    "adapter": {
      "groupValue": function(val) {
        val.value *= Math.random();
        return val;
      }
    }
  }]
}

Now, if we run the demo, we'll see how two series differ completely, even though they are based on the same data.

See the Pen Using custom functions to modify aggregate values by amCharts team (@amcharts) on CodePen.24419

NOTE The aggregate-value-modifying adapter is relevant only when chart is in grouping mode. Should you zoom the chart in significantly for it to stop grouping, you'll see the columns reverting to their original values in data.

Of course the above example just illustrates the point. You will apply your own logic as needed.

Referring to original data

Sometimes, we might need to refer to the original data, in order to do our custom calculations.

That's where dataItem comes in handy, as it has dataContext property - a reference to original data object.

Let's modify our code above, to replace series value with a completely different value field (value2) from our data:

series2.adapter.add("groupValue", function(val) {
  val.value = val.dataItem.dataContext.value2;
  return val;
});
series2.adapter.add("groupValue", function(val) {
  val.value = val.dataItem.dataContext.value2;
  return val;
});
{
  // ...
  "series": [{
    // ...
    "adapter": {
      "groupValue": function(val) {
        val.value = val.dataItem.dataContext.value2;
        return val;
      }
    }
  }]
}

Example:

See the Pen Using custom functions to modify aggregate values by amCharts team (@amcharts) on CodePen.24419

Modifying group data item

So far we have been using groupValue adapter to modify a raw value that goes into making group data item.

There's another adapter - groupDataItem available, that works in a similar way, but is instead applied to the value of an already aggregated group data item, in case we want to modify it, or even apply some completely custom fuzzy logic.

Furthermore, the data item reference passed into this adapter will have references to all the raw values that comprise this grouped data item.

It is accessible via groupDataItems list.

Let's see how we can mirror built-in "sum" grouping function, using our own adapter.

series2.adapter.add("groupDataItem", function(val) {
  val.value = 0;
  for(let i = 0; i < val.dataItem.groupDataItems.length; i++) {
    let item = val.dataItem.groupDataItems[i]
    val.value += item.valueY;
  }
  return val;
});
series2.adapter.add("groupDataItem", function(val) {
  val.value = 0;
  for(var i = 0; i < val.dataItem.groupDataItems.length; i++) {
    var item = val.dataItem.groupDataItems[i]
    val.value += item.valueY;
  }
  return val;
});
{
  // ...
  "series": [{
    // ...
    "adapter": {
      "groupDataItem": function(val) {
        val.value = 0;
        for(var i = 0; i < val.dataItem.groupDataItems.length; i++) {
          var item = val.dataItem.groupDataItems[i]
          val.value += item.valueY;
        }
        return val;
      }
    }
  }]
}

See the Pen Using custom functions to modify group data item by amCharts team (@amcharts) on CodePen.24419

And here's another example, which uses "extreme" value as group value - similarly to min or max value, but dynamically, whichever is farther from zero.

See the Pen Using adapters with data grouping to force extreme aggregate value by amCharts team (@amcharts) on CodePen.24419