Skip to content

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.

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.

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.

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.

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"
}]
}]
}

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"
}]
}]
}]
}

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:

Open in CodePen

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.

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"
}]
}]
}]
}
Open in CodePen

Much better!

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
}]
}]
}]
}]
}
Open in CodePen

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.

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.

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.

Open in CodePen

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.

Now, let’s see if it worked:

Open in CodePen

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!

Open in CodePen

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:

Open in CodePen

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
}]
}]
}]
}

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

Open in CodePen

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.

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;

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

Open in CodePen

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;
}
}
Open in CodePen

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.

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";
Open in CodePen

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

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+"
}]

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";

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:

Open in CodePen

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.

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;

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)

Open in CodePen

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}";
Open in CodePen

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:

Open in CodePen

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;

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

Open in CodePen

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;
Open in CodePen

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

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";

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

Open in CodePen

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.

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: