Using adapters for value-sensitive bullet adjustments

If your chart uses bullets - either as shapes or text labels - you might find yourself in situations where you might need to adjust certain aspect of bullet based on specific value or value change. It might be alignment of a label, or a color of dot.

This article will explain how you can achieve that easily using amCharts 4 concept of Adapters.

REQUIRED READING Before we begin, make sure you familiarize yourself with the concept by reading our "Adapters" article.

Adjusting position of a label bullet

Value labels in amCharts 4 are basically bullets with text labels in them. There is a ready made LabelBullet class for you to use as a bullet.

By default all bullets, including label bullets, are positioned right in the center of the value.

As you might already learned in our "Bullets" article, you can adjust the position to better suit your requirements.

However, single position might not suit all of the data items.

Say you have a waterfall column chart, that shows floating columns. You want to display the label on top of the column when value change is positive, and at the bottom when it is negative.

That's where adapters come in.

Adapter allows defining custom function that is run on a value or setting just before it is used.

Like for example Label bullet's verticalCenter property:

bullet.label.adapter.add("verticalCenter", function(center, target) {
  if (!target.dataItem) {
    return center;
  }
  let values = target.dataItem.values;
  return values.valueY.value > values.openValueY.value
    ? "bottom"
    : "top";
});
bullet.label.adapter.add("verticalCenter", function(center, target) {
  if (!target.dataItem) {
    return center;
  }
  var values = target.dataItem.values;
  return values.valueY.value > values.openValueY.value
    ? "bottom"
    : "top";
});
{
  // ...
  "series": [{
    // ...
    "bullets": [{
      // ...
      "label": {
        "adapter": {
          "verticalCenter": function(center, target) {
            if (!target.dataItem) {
              return center;
            }
            var values = target.dataItem.values;
             return values.valueY.value > values.openValueY.value
               ? "bottom"
               : "top";
          }
        }
      }
    }]
  }]
}

In the above code, our adapter function grabs data item and compares its open and close values. Then returns proper setting for the verticalCenter which then in turn is set on the appropriate label bullet, resulting in correct placement.

See the Pen Adapter on bullets by amCharts (@amcharts) on CodePen.

IMPORTANT Notice how we check if dataItem is set on the adapter target? It is important. If we don't do that, there might be situations where our whole chart will error out if adapter is run and dataItem is not set.

Modifying bullet appearance

Another use case is that we might want to display a green upwards-pointing triangle when the value grows, and red downwards-pointing triangle when the value falls.

We can create a simple Bullet with a Triangle in it. Then add two separate adapters : one for fill (color) and the other for rotation (direction).

Let's get cracking:

arrow.adapter.add("fill", function(fill, target) {
  if (!target.dataItem) {
    return fill;
  }
  let values = target.dataItem.values;
  return values.valueY.previousChange >= 0
    ? am4core.color("green")
    : am4core.color("red");
});

arrow.adapter.add("rotation", function(rotation, target) {
  if (!target.dataItem) {
    return rotation;
  }
  let values = target.dataItem.values;
  return values.valueY.previousChange >= 0
    ? 0
    : 180;
});
arrow.adapter.add("fill", function(fill, target) {
  if (!target.dataItem) {
    return fill;
  }
  var values = target.dataItem.values;
  return values.valueY.previousChange >= 0
    ? am4core.color("green")
    : am4core.color("red");
});

arrow.adapter.add("rotation", function(rotation, target) {
  if (!target.dataItem) {
    return rotation;
  }
  var values = target.dataItem.values;
  return values.valueY.previousChange >= 0
    ? 0
    : 180;
});
{
  // ...
  "series": [{
    // ...
    "bullets": [{
      // ...
      "type": "Bullet",
      "children": [{
        "type": "Triangle",
        // ...
        "adapter": {
          "fill": function(fill, target) {
            if (!target.dataItem) {
              return fill;
            }
            var values = target.dataItem.values;
            return values.valueY.previousChange >= 0
              ? am4core.color("green")
              : am4core.color("red");
          },
          "rotation": function(rotation, target) {
            if (!target.dataItem) {
              return rotation;
            }
            var values = target.dataItem.values;
            return values.valueY.previousChange >= 0
              ? 0
              : 180;
          }
        }
      }]
    }]
  }]
}

Here's how it turned out:

See the Pen amCharts 4: Adapter on bullets (1) by amCharts (@amcharts) on CodePen.