Bullets

In amCharts 4, bullets can be and are so much more than little geometric shapes plopped on the line. Those literally can be anything - shapes, text, controls, even other charts, or any combination of.

This article will walk you through all of the possibilities, starting from very basic, and ending with ones that will blow your hat off.

Prerequisites

Before diving in, we strongly suggest you familiarize yourself with the amCharts 4's concept of series and list templates.

To do so, take a look at "Series" and "List templates" articles.

Adding bullets to series

All series, regardless of its type, has a property named bullets.

It differs from other list-type series' properties in that it does not hold actual data item bullets, but rather a list of bullet templates that will be added for each of the data items in series.

The other difference is that it does not hold any specific type of items, but can have anything inherited from Container, which in amCharts 4 is a lot of objects, including charts themselves.

A Container is an "advanced" Sprite that can have many children elements in it, and knows how to arrange and position themselves inside.

NOTE Starting from version 4.3.12 bullet can be regular Sprite as well. This means that you can use simple shapes like Circle or Rectangle, or elements like Image.

Creating bullets

Whatever you want to be replicated as bullets over your series data items, you push() into bullets.

bullets is a list of Bullet objects or objects of basically any other type.

Let us show you how.

Using simple objects

To make this work we simple create an instance of an element and push it into bullets:

let square = lineSeries.bullets.push(am4core.Rectangle);
var square = lineSeries.bullets.push(am4core.Rectangle);
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Rectangle"
    }]
  }]
}

Using Bullet class

A Bullet is basically a Container. As we already mentioned, a container is a special type of visual element that can have any number children of any type.

So, to create a bullet out of, say, Rectangle we will need to:

  1. Create Bullet instance;
  2. Create Rectangle instance;
  3. Add rectangle instance to bullet as a child.

Since containers have a little handy method createChild(class) we can condense the last two into a single line of code.

Here's how it would look like:

let bullet = lineSeries.bullets.push(new am4charts.Bullet());
let square = bullet.createChild(am4core.Rectangle);
var bullet = lineSeries.bullets.push(new am4charts.Bullet());
var square = bullet.createChild(am4core.Rectangle);
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "children": [{
        "type": "Rectangle"
      }]
    }]
  }]
}

Configuring bullet

So far so good. However, we still have to apply some settings to our square bullet to make it take shape, like, width and height dimensions. Let's set it at 10/10px:

let bullet = lineSeries.bullets.push(new am4charts.Bullet());
let square = bullet.createChild(am4core.Rectangle);
square.width = 10;
square.height = 10;
var bullet = lineSeries.bullets.push(new am4charts.Bullet());
var square = bullet.createChild(am4core.Rectangle);
square.width = 10;
square.height = 10;
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "children": [{
        "type": "Rectangle",
        "width": 10,
        "height": 10
      }]
    }]
  }]
}

OK, time to take this baby for a spin:

See the Pen amCharts V4: Bullets (1) by amCharts (@amcharts) on CodePen.24419

We're off to a good start. We can see the little squares appearing where the bullet is supposed to go, even though the positioning is a bit off.

Positioning bullets

By default, stuff in a container (remember, a bullet is a container) is positioned by element's top-left corner.

To make our bullets rather be placed by their center, we'll set rectangle's horizontalCenter and verticalCenter properties to "middle":

let bullet = lineSeries.bullets.push(new am4charts.Bullet());
let square = bullet.createChild(am4core.Rectangle);
square.width = 10;
square.height = 10;
square.horizontalCenter = "middle";
square.verticalCenter = "middle";
var bullet = lineSeries.bullets.push(new am4charts.Bullet());
var square = bullet.createChild(am4core.Rectangle);
square.width = 10;
square.height = 10;
square.horizontalCenter = "middle";
square.verticalCenter = "middle";
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "children": [{
        "type": "Rectangle",
        "width": 10,
        "height": 10,
        "horizontalCenter": "middle",
        "verticalCenter": "middle"
      }]
    }]
  }]
}

See the Pen amCharts V4: Bullets (2) by amCharts (@amcharts) on CodePen.24419

Much better!

Configuring bullets

Remember, elements in a bullet are all Sprite-type objects, that have loads of settings that modify their appearance.

Do we perhaps want our square bullets to have an outline? Use Rectangle's stroke* properties to set that.

Or even have shadow? No problem - just add a ShadowFilter to the square element.

In fact, let's try both:

// Add outline to the square bullet
square.stroke = am4core.color("#2F4858");
square.strokeWidth = 1;

// Make square drop shadow by adding a DropShadow filter
let shadow = new am4core.DropShadowFilter();
shadow.dx = 2;
shadow.dy = 2;
square.filters.push(shadow);
// Add outline to the square bullet
square.stroke = am4core.color("#2F4858");
square.strokeWidth = 1;

// Make square drop shadow by adding a DropShadow filter
var shadow = new am4core.DropShadowFilter();
shadow.dx = 2;
shadow.dy = 2;
square.filters.push(shadow);
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "children": [{
        "type": "Rectangle",
        // ...
        "stroke": "#2F4858",
        "strokeWidth": 1,
        "filters": [{
          "type": "DropShadowFilter",
          "dx": 2,
          "dy": 2
        }]
      }]
    }]
  }]
}

See the Pen amCharts V4: Bullets (3) by amCharts (@amcharts) on CodePen.24419

Built-in bullet classes

amCharts 4 comes preloaded with a few ready-made bullet classes that you can use straight away as bullets, without worrying to add anything into them.

At this point we have two: CircleBullet (which creates a bullet with a Circle element in it) and LabelBullet (a bullet with a Label element in it, so you can created value labels).

So, if we just want to use a circle as a bullet, we don't need to go through the trouble of creating a Bullet, then creating a Circle, and adding it to the bullet. We can just create a single CircleBullet instance:

let bullet = lineSeries.bullets.push(new am4charts.CircleBullet());
var bullet = lineSeries.bullets.push(new am4charts.CircleBullet());
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "CircleBullet"
    }]
  }]
}

And, besides creating required element, it will also set correct positioning options and dimensional settings like radius.

If we still need to modify something in the appearance of the bullet's circle element, we will find the Circle object in the bullet's circle property.

Similarly, in bullets created using LabelBullet class we'll find our Label instance we can configure in bullet's label property.

You'll see examples of different bullets used throughout the rest of this article.

Inheriting series color

Usually, we want our bullets to automatically be colored the same as the series. This is why any bullet added to series, will automatically inherit its stroke and fill settings.

Need to carry over any other properties, like fillOpacity? You'll need to manually set it on a bullet or its elements.

Density control

Bullets look generally nice if you have relatively few data points. However, if you have, say, 500 data points, 500 bullets would look a bit awful when crammed together.

Obvious solution would be not to use bullets on charts where you expect a lot of data. But that would mean that there would be no bullets, even if user zooms in the chart where only a few data points are visible.

Good news is that you can have both, by using series' minBulletDistance setting.

In English it means this "if there is less pixels between two adjacent data points, hide the bullets".

The below chart does not have any bullets when it loads, because minBulletDistance prevents them from appearing - too crammed together. Once you start zooming in, the bullets will eventually start popping up. Go ahead, give it a try.

See the Pen Date Based Data by amCharts team (@amcharts) on CodePen.0

Multi-element bullets

Adding multiple elements to our bullet is easy. We just create and push() several elements to the series' bullets list.

Let's try out adding both CircleBullet and LabelBullet from the previous example.

let circleBullet = lineSeries.bullets.push(new am4charts.CircleBullet());
let labelBullet = lineSeries.bullets.push(new am4charts.LabelBullet());
labelBullet.label.text = "{value}";
var circleBullet = lineSeries.bullets.push(new am4charts.CircleBullet());
var labelBullet = lineSeries.bullets.push(new am4charts.LabelBullet());
labelBullet.label.text = "{value}";
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "CircleBullet"
    }, {
      "type": "LabelBullet",
      "label": {
        "text": "{value}"
      }
    }]
  }]
}

Please note how we are setting up a Label instance in our LabelBullet. The text is what we want to be shown in the label, and the {value} in it is an instruction to string formatter to replace with real data from the specific data point the bullet is for.

MORE INFO Read about text formatter and data its binding.

MORE INFO For more details, code and examples about CircleBullet check out "Using Circle Bullets" tutorial.

Now, let's see if it worked:

See the Pen amCharts V4: Bullets (4) by amCharts (@amcharts) on CodePen.24419

It did. At least in a sense that both circle bullets and data labels with correct values are there.

However, they're all overlapped (because they're all centered by default), which means we'll need to apply some shifting to fix the overlapping.

For relatively shifting an element, we can use its dx and dy properties, to apply horizontal and vertical shift respectively. Negative values will shift the element left/upwards, while positive value will shift it right/downwards.

Looks like shifting the label upwards should solve the issue. While at it, i'm going to add a small outline to the circle as well, to make our chart look even nicer:

let circleBullet = lineSeries.bullets.push(new am4charts.CircleBullet());
circleBullet.circle.stroke = am4core.color("#fff");
circleBullet.circle.strokeWidth = 2;

let labelBullet = lineSeries.bullets.push(new am4charts.LabelBullet());
labelBullet.label.text = "{value}";
labelBullet.label.dy = -20;
var circleBullet = lineSeries.bullets.push(new am4charts.CircleBullet());
circleBullet.circle.stroke = am4core.color("#fff");
circleBullet.circle.strokeWidth = 2;

var labelBullet = lineSeries.bullets.push(new am4charts.LabelBullet());
labelBullet.label.text = "{value}";
labelBullet.label.dy = -20;
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "CircleBullet",
      "circle": {
        "stroke": "#fff",
        "strokeWidth": 2
      }
    }, {
      "type": "LabelBullet",
      "label": {
        "text": "{value}",
        "dy": -20
      }
    }]
  }]
}

Now, run the below example and tell me our ugly little duckling from the very first example didn't turn into a beautiful swan!

See the Pen amCharts V4: Bullets (5) by amCharts (@amcharts) on CodePen.24419

Showing tooltip

A bullet, just like any other element on the chart, can have tooltip displayed whenever hovered.

To enable it, use tooltipText property. It works just like text property for a label example above. You can set any text, or formatting directives, or references to data:

circleBullet.tooltipText = "Value: [bold]{value}[/]";
circleBullet.tooltipText = "Value: [bold]{value}[/]";
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "CircleBullet",
      // ...
      "tooltipText": "Value: [bold]{value}[/]"
    }]
  }]
}

Try hovering on the bullets now:

See the Pen amCharts V4: Bullets (5 with tooltip) by amCharts (@amcharts) on CodePen.24419

Bullets in data

So far we have been using identical bullets for all data points.

However, what if we want to modify appearance of the bullet based on the data?

That's where propertyFields functionality comes in.

Property fields is an object, that ties visual element's properties to the actual values in the data.

For example we might bind bullet's angle property to a value in data to dynamically rotate an arrow in it to depict wind direction.

// Add a bullet
let bullet = lineSeries.bullets.push(new am4charts.Bullet());

// Bind `rotation` property to `angle` field in data
bullet.propertyFields.rotation = "angle";

// Add a triangle to act as am arrow
let arrow = bullet.createChild(am4core.Triangle);
arrow.horizontalCenter = "middle";
arrow.verticalCenter = "middle";
arrow.stroke = am4core.color("#fff");
arrow.direction = "top";
arrow.width = 10;
arrow.height = 25;
// Add a bullet
var bullet = lineSeries.bullets.push(new am4charts.Bullet());

// Bind `rotation` property to `angle` field in data
bullet.propertyFields.rotation = "angle";

// Add a triangle to act as am arrow
var arrow = bullet.createChild(am4core.Triangle);
arrow.horizontalCenter = "middle";
arrow.verticalCenter = "middle";
arrow.stroke = am4core.color("#fff");
arrow.direction = "top";
arrow.width = 10;
arrow.height = 25;
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Bullet",
      "children": [{
        "type": "Triangle",
        "horizontalCenter": "middle",
        "verticalCenter": "middle",
        "stroke": "#fff",
        "direction": "top",
        "width": 10,
        "height": 25
      }]
    }]
  }]
}

NOTE A Triangle, besides width and height, also needs its direction set to make it work.

Here's how the above looks in action: (we've dimmed out the line itself so that arrows become more prominent)

See the Pen amCharts V4: Bullets (6) by amCharts (@amcharts) on CodePen.24419

rotation is not the only property you can bind to data. You can bind fill, stroke, opacity, or just about any other property or a combination of.

Bullets outside plot area

Disabling bullet clipping

The chart will mask (clip) bullets that do not fit into plot area. This might result in label bullets not fitting, or parts of the bullet circles cut off, etc.

If we want to disable that, we can set chart's maskBullets setting to false. This will allow bullets to "bleed" over the edge of plot area.

chart.maskBullets = false;
chart.maskBullets = false;
{
  // ...
  "maskBullets": false
}

The following demo shows how using maskBullets allows LabelBullet elements go outside plot area:

See the Pen amChrts 4: Masking bullets by amCharts (@amcharts) on CodePen.24419

Hiding unmasked bullets

Unmasking bullets can have an unwelcome side-effect: when chart is zoomed, bullets might still show well outside plot area.

If you have zooming enabled, we'll need to fix that through the use of adapters:

bullet.adapter.add("dy", function(dy, target) {
  hideBullet(target);
  return dy;
})

function hideBullet(bullet) {
  if (bullet.pixelX < 0 || bullet.pixelX > chart.plotContainer.measuredWidth || bullet.pixelY < 0 || bullet.pixelY > chart.plotContainer.measuredHeight) {
    bullet.visible = false;
  }
  else {
    bullet.visible = true;
  }
}
bullet.adapter.add("dy", function(dy, target) {
  hideBullet(target);
  return dy;
})

function hideBullet(bullet) {
  if (bullet.pixelX < 0 || bullet.pixelX > chart.plotContainer.measuredWidth || bullet.pixelY < 0 || bullet.pixelY > chart.plotContainer.measuredHeight) {
    bullet.visible = false;
  }
  else {
    bullet.visible = true;
  }
}
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Bullet",
      // ...
      "adapter": {
        "dy": function(dy, bullet) {
          var chart = bullet.baseSprite;
          if (bullet.pixelX < 0 || bullet.pixelX > chart.plotContainer.measuredWidth || bullet.pixelY < 0 || bullet.pixelY > chart.plotContainer.measuredHeight) {
            bullet.visible = false;
          }
          else {
            bullet.visible = true;
          }
          return dy;
        }
      }
    }]
  }]
}

See the Pen amCharts V4: Bullets (5 with tooltip) by amCharts team (@amcharts) on CodePen.24419

Using images as bullets

When creating bullets, we're not limited to basic shapes. As we discussed before, a Bullet as a Container which you can add virtually anything to.

That includes images. Let's explore our options here.

Same image for all bullets

If we want to use an image for all bullets in the series, we can just add an instance of the Image into our respective bullet container, just like we were doing with Rectangle objects in previous chapters.

let bullet = lineSeries.bullets.push(new am4charts.Bullet());
let image = bullet.createChild(am4core.Image);
image.href = "/path/to/image.svg";
image.width = 30;
image.height = 30;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
var bullet = lineSeries.bullets.push(new am4charts.Bullet());
var image = bullet.createChild(am4core.Image);
image.href = "/path/to/image/star.svg";
image.width = 30;
image.height = 30;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Bullet",
      "children": [{
        "type": "Image",
        "href": "/path/to/image/star.svg",
        "width": 30,
        "height": 30,
        "horizontalCenter": "middle",
        "verticalCenter": "middle"
      }]
    }]
  }]
}

See the Pen amCharts V4: Bullets (images 1) by amCharts (@amcharts) on CodePen.24419

TIP A href parameter for an Image does not necessarily have to point to a standalone file. It can be a "data URI".

Individual images for data items

We can also specify a separate image for each individual data item as well.

For that we'll turn to the "property fields" - a topic we already touched in "Bullets in data" chapter.

Since all bullets, including its children have access to the same data point, we can add image information to our data items in our source data. Something like this:

[{
  "date": new Date(2018, 3, 20),
  "value": 90,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2MTc1YzAgNyA1IDEyIDEyIDEyaDE3NWM2IDAgMTEtNSAxMS0xMlYxN2MwLTYtNS0xMS0xMS0xMUgxOHoiIGZpbGw9IiMwMDI0N2QiLz48cGF0aCBkPSJNNiAzOXYxN2w4NCA0OS04NCA0OHYxOGw5OS01OCA5OSA1OHYtMThsLTg0LTQ4IDg0LTQ5VjM5bC05OSA1N0w2IDM5eiIgZmlsbD0iI2NmMTQyYiIgcGFpbnQtb3JkZXI9Im1hcmtlcnMgZmlsbCBzdHJva2UiLz48cGF0aCBkPSJNODggNnY4Mkg2djM0aDgydjgyaDM0di04Mmg4MlY4OGgtODJWNkg4OHoiIGZpbGw9IiNjZjE0MmIiIHBhaW50LW9yZGVyPSJtYXJrZXJzIGZpbGwgc3Ryb2tlIi8+PHBhdGggZD0iTTg1IDZ2NzVMNiAzNXY4bDcyIDQySDYyTDYgNTN2N2w0MyAyNUg2djZoODZWNnptMzQgMHY4NWg4NXYtNmgtNDJsNDItMjV2LTdsLTU1IDMyaC0xN2w3Mi00MnYtOGwtNzggNDZWNnpNOTIgMTE5SDZ2Nmg0M0w2IDE0OXY4bDU2LTMyaDE2TDYgMTY3djdsNzktNDV2NzVoN3YtNzl6bTI3IDB2ODVoN3YtNzVsNzggNDV2LTdsLTcyLTQyaDE3bDU1IDMydi04bC00Mi0yNGg0MnYtNmgtODV6IiBzdHlsZT0ibGluZS1oZWlnaHQ6bm9ybWFsO2ZvbnQtdmFyaWFudC1saWdhdHVyZXM6bm9ybWFsO2ZvbnQtdmFyaWFudC1wb3NpdGlvbjpub3JtYWw7Zm9udC12YXJpYW50LWNhcHM6bm9ybWFsO2ZvbnQtdmFyaWFudC1udW1lcmljOm5vcm1hbDtmb250LXZhcmlhbnQtYWx0ZXJuYXRlczpub3JtYWw7Zm9udC1mZWF0dXJlLXNldHRpbmdzOm5vcm1hbDt0ZXh0LWluZGVudDowO3RleHQtYWxpZ246c3RhcnQ7dGV4dC1kZWNvcmF0aW9uLWxpbmU6bm9uZTt0ZXh0LWRlY29yYXRpb24tc3R5bGU6c29saWQ7dGV4dC1kZWNvcmF0aW9uLWNvbG9yOiMwMDA7dGV4dC10cmFuc2Zvcm06bm9uZTt0ZXh0LW9yaWVudGF0aW9uOm1peGVkO3NoYXBlLXBhZGRpbmc6MDtpc29sYXRpb246YXV0bzttaXgtYmxlbmQtbW9kZTpub3JtYWwiIGNvbG9yPSIjMDAwIiBmb250LXdlaWdodD0iNDAwIiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgd2hpdGUtc3BhY2U9Im5vcm1hbCIgb3ZlcmZsb3c9InZpc2libGUiIGZpbGw9IiNmZmYiIHBhaW50LW9yZGVyPSJtYXJrZXJzIGZpbGwgc3Ryb2tlIi8+PC9zdmc+"
}, {
  "date": new Date(2018, 3, 21),
  "value": 102,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2MTc1YzAgNyA1IDEyIDEyIDEyaDE3NWM2IDAgMTEtNSAxMS0xMlYxN2MwLTYtNS0xMS0xMS0xMUgxOHoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNMTA1IDY3bC03IDEyLTYtMiAzIDIxLTktMTEtMiA1LTEyLTEgMyAxMS01IDMgMTMgMTBjMSAxIDUgNCA1IDZsLTEgNCAzLTEgMTQtMnYyMGgydi0yMGwxNCAyIDMgMXMtMi0zLTEtNGMwLTIgNC01IDUtNmw4LTYgNi00LTUtMyAyLTExLTExIDEtMi01LTEwIDEzIDMtMjMtNiAyek0xOCA2QzExIDYgNiAxMSA2IDE3djE3NWMwIDcgNSAxMiAxMiAxMmgzMVY2SDE4ek0xOTMgNmM2IDAgMTEgNSAxMSAxMXYxNzVjMCA3LTUgMTItMTEgMTJoLTMyVjZ6IiBmaWxsPSJyZWQiIHBhaW50LW9yZGVyPSJtYXJrZXJzIGZpbGwgc3Ryb2tlIi8+PC9zdmc+"
}, {
  "date": new Date(2018, 3, 22),
  "value": 65,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2MTc1YzAgNyA1IDEyIDEyIDEyaDE3NWM2IDAgMTEtNSAxMS0xMlYxN2MwLTYtNS0xMS0xMS0xMUgxOHoiIGZpbGw9IiNmZmYiLz48Y2lyY2xlIGN4PSIxMDUiIGN5PSIxMDUiIHI9IjM2IiBmaWxsPSIjYmMwMDJkIiBwYWludC1vcmRlcj0ibWFya2VycyBmaWxsIHN0cm9rZSIvPjwvc3ZnPg=="
}, {
  "date": new Date(2018, 3, 23),
  "value": 62,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2NTVoMTk4VjE3YzAtNi01LTExLTExLTExaC04N3pNNiAxMzh2NTRjMCA3IDUgMTIgMTIgMTJoMTc1YzYgMCAxMS01IDExLTEydi01NHoiIGZpbGw9IiNlZDI5MzciLz48cGF0aCBkPSJNNiA3MnY2NmgxOThWNzJINnoiIGZpbGw9IiNmOWY5ZjkiLz48L3N2Zz4="
}, {
  "date": new Date(2018, 3, 24),
  "value": 55,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2MTc1YzAgNyA1IDEyIDEyIDEyaDU0VjZIMTh6Ii8+PHBhdGggZD0iTTEzOCA2djE5OGg1NWM2IDAgMTEtNSAxMS0xMlYxN2MwLTYtNS0xMS0xMS0xMXoiIGZpbGw9IiNlZDI5MzkiLz48cGF0aCBkPSJNNzIgNnYxOThoNjZWNkg3MnoiIGZpbGw9IiNmYWUwNDIiLz48L3N2Zz4="
}, {
  "date": new Date(2018, 3, 25),
  "value": 81,
  "bullet": "data:image/svg+xml;charset=utf-8;base64,PHN2ZyBoZWlnaHQ9Ijc5NCIgdmVyc2lvbj0iMSIgdmlld0JveD0iMCAwIDIxMCAyMTAiIHdpZHRoPSI3OTQiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZD0iTTE4IDZDMTEgNiA2IDExIDYgMTd2ODhoMTk4VjE3YzAtNi01LTExLTExLTExSDE4eiIgZmlsbD0iI2RmMTEyYiIvPjxwYXRoIGQ9Ik02IDEwNXY4N2MwIDcgNSAxMiAxMiAxMmgxNzVjNiAwIDExLTUgMTEtMTJ2LTg3SDZ6IiBmaWxsPSIjZmZmIi8+PC9zdmc+"
}]

NOTE We mentioned "data URIs" in previous example. The above data example uses them. That mumbolajumbo is actually a Base64-encoded SVG. Data URIs is a good way to embed full image info into our data, without requiring for any external files.

OK, so now that we know we have images in our data, we have to tell an Image in our bullet to use that.

For that, we'll use propertyFields which is basically a way to override any property of an object from the field in its related data.

The field we need to be overriding is href. And we're overriding it values with key "bullet" in our data:

let bullet = lineSeries.bullets.push(new am4charts.Bullet());
let image = bullet.createChild(am4core.Image);
image.propertyFields.href = "bullet";
image.width = 30;
image.height = 30;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
var bullet = lineSeries.bullets.push(new am4charts.Bullet());
var image = bullet.createChild(am4core.Image);
image.propertyFields.href = "bullet";
image.width = 30;
image.height = 30;
image.horizontalCenter = "middle";
image.verticalCenter = "middle";
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Bullet",
      "children": [{
        "type": "Image",
        "propertyFields": {
          "href": "bullet"
        },
        "width": 30,
        "height": 30,
        "horizontalCenter": "middle",
        "verticalCenter": "middle"
      }]
    }]
  }]
}

As you can see, nothing much has changed from the previous example. Only the explicit href setting was replaced with propertyFields.href setting.

Let's see how this looks in real life:

See the Pen amCharts V4: Bullets (images 2) by amCharts (@amcharts) on CodePen.24419

Modifying appearance via states

Let us introduce you to another very powerful feature of amCharts 4 - states.

In short, a state is a set of property values to apply to an element whenever certain conditions are met or some event occurs. Like for instance an element is hovered.

For example, we might want to enlarge the whole bullet whenever we hover over it. Besides overriding simple properties, it can also apply advanced visual effects, such as filters.

Hover state

Let's modify our above example to enlarge the arrows, whenever those are hovered.

let hoverState = bullet.states.create("hover");
hoverState.properties.scale = 1.7;
var hoverState = bullet.states.create("hover");
hoverState.properties.scale = 1.7;
{
  // ...
  "series": [{
    "type": "LineSeries",
    // ...
    "bullets": [{
      "type": "Bullet",
      // ...
      "states": {
        "hover": {
          "properties": {
            "scale": 1.7
          }
        }
      }
    }]
  }]
}

Try running the example below, and hovering on those arrows: (we've also enabled "animated" theme for a nicer transition; read more about themes here)

See the Pen amCharts V4: Bullets (7) by amCharts (@amcharts) on CodePen.24419

NOTE If chart has Cursor enabled, a "hover" state will be applied automatically for all bullets in current Cursor's location. More info about it in "Cursor: Series bullets".

Advanced use

Positioning within column

Putting a bullet on a Line series elbow was easy, since it has a fine connection point.

But what about putting on other shapes, like, say, column? Suppose we want to place a data label in the middle of the column.

Let's try using LabelBullet we examined earlier:

let columnSeries = chart.series.push(new am4charts.ColumnSeries());
columnSeries.dataFields.valueY = "value";
columnSeries.dataFields.dateX = "date";
columnSeries.name = "Sales";

// Add a bullet
let bullet = columnSeries.bullets.push(new am4charts.LabelBullet());
bullet.label.text = "{value}";
var columnSeries = chart.series.push(new am4charts.ColumnSeries());
columnSeries.dataFields.valueY = "value";
columnSeries.dataFields.dateX = "date";
columnSeries.name = "Sales";

// Add a bullet
var bullet = columnSeries.bullets.push(new am4charts.LabelBullet());
bullet.label.text = "{value}";
{
  // ...
  "series": [{
    "type": "ColunSeries",
    // ...
    "bullets": [{
      "type": "LabelBullet",
      "label": {
        "text": "{value}"
      }
    }]
  }]
}

See the Pen amCharts V4: Bullets (9) by amCharts (@amcharts) on CodePen.24419

Functionality is there, but text, stuck on top of the column does not look too good.

We could use bullet's dy property with a negative value to shift the label upwards, which would definitely make things better:

See the Pen amCharts V4: Bullets (10) by amCharts (@amcharts) on CodePen.24419

Better, but not let's stop there.

Let's position our labels in the middle of each respective column. To set vertical positioning of the bullet in respect to the column, we can use its locationX and locationY properties.

Since we're OK with horizontal positioning, we're going to use only locationY which accepts values from 0 (top) to 1 (bottom), with 0.5 being in the middle.

bullet.locationY = 0.5;
bullet.locationY = 0.5;
{
  // ...
  "series": [{
    "type": "ColunSeries",
    // ...
    "bullets": [{
      "type": "LabelBullet",
      // ...
      "locationY": 0.5
    }]
  }]
}

For the kick of it, we've thrown in another bullet (CircleBullet) to serve as a nice background for our bullets:

See the Pen amCharts V4: Bullets (11) by amCharts (@amcharts) on CodePen.24419

Looks good, doesn't it?

There's one caveat, though. For bullets, locationY property means the relative vertical position in the whole height of the column. That means that if our scale would not start at zero, it would be not in the direct center of the currently visible portion of the column.

To work around it, we can drop the bullet concept altogether, and push our items directly into Column series template. (read more about concept of list templates)

let label = columnSeries.columns.template.createChild(am4core.Label);
label.text = "{value}";
label.align = "center";
label.valign = "middle";
label.zIndex = 2;
label.fill = am4core.color("#000");
label.strokeWidth = 0;

let bullet = columnSeries.columns.template.createChild(am4core.Circle);
bullet.locationY = 0.5;
bullet.align = "center";
bullet.valign = "middle";
bullet.fill = am4core.color("#fff");
bullet.fillOpacity = 0.5;
bullet.radius = 20;
var label = columnSeries.columns.template.createChild(am4core.Label);
label.text = "{value}";
label.align = "center";
label.valign = "middle";
label.zIndex = 2;
label.fill = am4core.color("#000");
label.strokeWidth = 0;

var bullet = columnSeries.columns.template.createChild(am4core.Circle);
bullet.locationY = 0.5;
bullet.align = "center";
bullet.valign = "middle";
bullet.fill = am4core.color("#fff");
bullet.fillOpacity = 0.5;
bullet.radius = 20;
{
  // ...
  "series": [{
    "type": "ColunSeries",
    // ...
    "columns": [{
      "children": [{
        "type": "Label",
        "align": "center",
        "valign": "middle",
        "zIndex": 2,
        "strokeWidth": 0
      }]
    }, {
      "children": [{
        "type": "Circle",
        "align": "center",
        "valign": "middle",
        "fill": "#fff",
        "fillOpacity": 0.5,
        "radius": 20
      }]
    }]
  }]
}

See the Pen amCharts V4: Bullets (12) by amCharts (@amcharts) on CodePen.24419

Here, you just learned how to nest various elements in amCharts 4!

MORE INFO For more information about how Containers work, visit "Working with Containers" article.

Showing bullets selectively

Sometimes you will need to show bullets only on certain data items.

The workflow is as follows:

  1. Disable your bullet by setting its disabled = true.
  2. Set up bullet's propertyFields to look for values overriding disabled in data.

Example:

bullet.disabled = true;
bullet.propertyFields.disabled = "disabled";
bullet.disabled = true;
bullet.propertyFields.disabled = "disabled";
{
  // ...
  "series": [{
    "bullets": [{
      // ...
      "disabled": true,
      "propertyFields": {
        "disabled": "disabled"
      }
    }]
  }]
}

This way, all of the bullets will be disabled, except for those that will find false in their respective data items' disabled fields.

See the Pen amCharts V4: Bullets (3) by amCharts team (@amcharts) on CodePen.24419

Bullet performance

Since bullets usually come in packs, they might reduce chart performance.

Please check "Bullets" section in our "Performance" article for tips on how to set up bullets so they don't bog down your website or application.

Value-sensitive bullet adjustments

We can use Adapters to dynamically adjust bullet settings like position, color and rotation, based on the value.

We do have a separate tutorial for such usage:

Related content

Related demos