Creating themes

amCharts 4 comes with a plethora of pre-made themes you can use for your charts. You can combine them by applying several themes to a single chart. But what, if you don't find what you're happy with? This tutorial will show how you can create your own theme.

Prerequisites

This tutorial shows how you can create your own theme. We're going to assume you know what themes are and how to use them.

If you need a recap, head over our "Themes" article.

How theme works

In a nutshell a theme is a single function. It works like this:

Theme workflow

 

That's basically it. Whenever a new object is created (any object really - a chart, an axis, a grid line, a label) it's passed through a theme function allowing it to modify the object the way it see fits.

Since this happens immediately in the constructor of the object, theme does not have a chance to overwrite any user-set properties because, user haven't had even a chance to set them, yet.

Creating a theme

Theme function

OK, so now that we know how themes work (and that a theme is just a function) we come to a pretty self-evident conclusion that in order to create a theme, all we need to do is to define a function.

Let's try that:

function am4themes_myTheme(target) {
}
function am4themes_myTheme(target) {
}

There you go - you have created your new theme.

It does nothing, but it is a theme and you can use it just like you would use any other theme:

am4core.useTheme(am4themes_myTheme);
am4core.useTheme(am4themes_myTheme);

Theme naming

Technically, you can name your them function any way you like (yes, even MySuperAwesomeNewTheme). However, for the sake of consistency with amCharts 4 bundled themes, we suggest you follow the am4themes_[themeNameInCamelCase] syntax.

That's why in the example above we went with am4themes_myTheme instead of just naming it myTheme.

Adding functionality

Creating empty themes might be fun for a while, but they'd be much more useful if they actually did something.

Let's try adding some flesh to it.

/* Define a custom theme */
function am4themes_myTheme(target) {
  if (target instanceof am4charts.Axis) {
    target.background.fill = am4core.color("#DCCCA3");
  }
}

/* Apply it */
am4core.useTheme(am4themes_myTheme);
/* Define a custom theme */
function am4themes_myTheme(target) {
  if (target instanceof am4charts.Axis) {
    target.background.fill = am4core.color("#DCCCA3");
  }
}

/* Apply it */
am4core.useTheme(am4themes_myTheme);

The above code will check if the object is of type Axis which is a base class of all axis types. If it is it will set background color for it.

Let's see ho that worked:

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

Boom! It did.

OK, but now some labels are protruding from the vertical axis. Let's remove the first and last labels.

Normally, we would set Axis' renderer properties minLabelPostion and maxLabelPosition.

Unfortunately, we can't do that directly on target.renderer. Why? Remember the theme is applied in object's constructor, meaning that not all of its sub-objects, like in this instance a renderer, is already available.

The solution is to create a separate check for instanceof AxisRenderer:

function am4themes_myTheme(target) {
  if (target instanceof am4charts.Axis) {
    target.background.fill = am4core.color("#DCCCA3");
  }
  
  if (target instanceof am4charts.AxisRenderer) {
    target.minLabelPosition = 0.1;
    target.maxLabelPosition = 0.9;
  }
}
function am4themes_myTheme(target) {
  if (target instanceof am4charts.Axis) {
    target.background.fill = am4core.color("#DCCCA3");
  }
  
  if (target instanceof am4charts.AxisRenderer) {
    target.minLabelPosition = 0.1;
    target.maxLabelPosition = 0.9;
  }
}

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

Using class hierarchy

When working with themes instanceof check is super useful. It will check and resolve to true if the object is not directly instantiated from a class being checked, but even from parent class.

This allows setting up convenient scoping mechanisms.

For example, in the above example with checked against Axis class, which is a mother of all Axes. This means that the custom settings will be applied to all axes, e.g. a ValueAxis because it extends Axis.

This is even true for multi-level inheritance. A DateAxis which does not directly extend Axis but extends ValueAxis will be affected as well.

If we wanted to target only, say, Date axes, we could check for instanceof am4charts.DateAxis.

Similarly, if we wanted to limit labels to only vertical axes, we could check for instanceof am4charts.AxisRendererY. This way only renderers of vertical axes would be affected, regardless of axis type.

Themes and colors

Probably the most common usage for themes is to define color sets that will be applied to charts and their visual elements.

Color sets

Whenever a chart needs to color multiple elements differently, it creates an instance of a ColorSet object.

A ColorSet is a collection of pre-defined colors and some rules how to generate new ones if it ever runs out.

As an example, let's take a Pie series. Whenever Pie series uses a local ColorSet object to pluck a new color for each of its slices.

Another example, an XY chart with multiple series will use ColorSet to apply a unique color for each of its series.

If we don't have any theme, colors are plucked from the default list of colors, resulting in a default looking chart:

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

A theme that aims to modify the color schemes will modify the ColorSet object, or more precisely its list property, which is an array of colors:

function am4themes_myTheme(target) {
  if (target instanceof am4core.ColorSet) {
    target.list = [
      am4core.color("red")
    ];
  }
}
function am4themes_myTheme(target) {
  if (target instanceof am4core.ColorSet) {
    target.list = [
      am4core.color("red")
    ];
  }
}

I know we only added one color to the Color set, but let's take a leap of faith and see how that affects our Pie chart:

See the Pen amCharts 4: Creating themes (4) by amCharts (@amcharts) on CodePen.24419

Even though we defined only one color, in a list all the slices look differently.

The first slice is eye-burning red, just like we defined it. But, since ColorSet has built-in mechanisms to generate matching but distinctive colors when the list runs out, it colored our slices for us.

We wanted to show you this feature, how whole new color sets can be generated with just one base color.

Now, let's try some multi-data set. Let's shamelessly grab colors from this awesome website.

function am4themes_myTheme(target) {
  if (target instanceof am4core.ColorSet) {
    target.list = [
      am4core.color("#1BA68D"),
      am4core.color("#E7DA4F"),
      am4core.color("#E77624"),
      am4core.color("#DF3520"),
      am4core.color("#64297B"),
      am4core.color("#232555")
    ];
  }
}
function am4themes_myTheme(target) {
  if (target instanceof am4core.ColorSet) {
    target.list = [
      am4core.color("#1BA68D"),
      am4core.color("#E7DA4F"),
      am4core.color("#E77624"),
      am4core.color("#DF3520"),
      am4core.color("#64297B"),
      am4core.color("#232555")
    ];
  }
}

Let's check how this funky color list translates onto a real chart:

See the Pen amCharts 4: Creating themes (5) by amCharts (@amcharts) on CodePen.24419

Interface colors

How interface colors are used

amCharts 4 has a special color set that is used for defining colors for common elements.

For example, it knows how button should be colored. What color should all labels be. The color of the grid elements. Etc. (full list coming up)

Similarly to the ColorSet usage in previous chapter, whenever some common element is created, say, a Button, it instantiates a InterfaceColorSet to get its colors from.

Unlike ColorSet, InterfaceColorSet does not dish out colors in a row. It has a specific color for a specific usage. Our new proverbial Button will ask questions from the Interface color set, like "how should I color my borders?" or "what color of my text label should be?".

As another example, an Axis renderer when rendering a grid will ask Interface color set: "what color we are using for grid today?"

Think of Interface color set as a set of default colors for specific uses.

Modifying interface colors

Naturally, if we want to modify default interface colors, we meddle with an InterfaceColorSet in our theme.

Since it does not have a dumb list of colors - each color needs to be tied to a specific purpose - we can't modify its list directly. Instead we will be using its setFor(purpose, color) method.

Let's start with a simple chart as an example. We've added a Scrollbar. It's a good interface element, since it has two grips, that are in fact buttons, hence perfect subjects.

See the Pen amCharts 4: Creating themes (6) by amCharts (@amcharts) on CodePen.24419

Let's see if we can make some changes to it by using setFor() method in our theme:

function am4themes_myTheme(target) {
  if (target instanceof am4core.InterfaceColorSet) {
    target.setFor("secondaryButton", am4core.color("#6DC0D5"));
    target.setFor("secondaryButtonHover", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonDown", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonActive", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonText", am4core.color("#FFFFFF"));
    target.setFor("secondaryButtonStroke", am4core.color("#467B88"));
  }
}
function am4themes_myTheme(target) {
  if (target instanceof am4core.InterfaceColorSet) {
    target.setFor("secondaryButton", am4core.color("#6DC0D5"));
    target.setFor("secondaryButtonHover", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonDown", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonActive", am4core.color("#6DC0D5").lighten(-0.2));
    target.setFor("secondaryButtonText", am4core.color("#FFFFFF"));
    target.setFor("secondaryButtonStroke", am4core.color("#467B88"));
  }
}

We know that all three grips on a Scrollbar are treated as a "secondary button", hence us using "secondaryButton*" purposes in the above example.

Let's try that on a live chart:

See the Pen amCharts 4: Creating themes (7) by amCharts (@amcharts) on CodePen.24419

Yup, that worked.

List of interface color purpose codes (used in setFor())

Code Default Purpose Used in
stroke color("#e5e5e5") Line (not used at the moment)
fill color("#f3f3f3") Fill Preloader, Scrollbar.
primaryButton color("#6794dc") Fill Navbar links, Zoom out button, Focus filter.
primaryButtonHover color("#6771dc") Fill (hovered element) "
primaryButtonDown color("#68dc75") Fill (pressed element) "
primaryButtonActive color("#68dc75") Fill (active element) "
primaryButtonText color("#FFFFFF") Text "
primaryButtonStroke color("#FFFFFF") Border "
secondaryButton color("#d9d9d9") Fill Scrollbar, Map polygons, Scrollbar, and most of the buttons.
secondaryButtonHover color("#d9d9d9").brighten(-0.25) Fill (hovered element) "
secondaryButtonDown color("#d9d9d9").brighten(-0.35) Fill (pressed element) "
secondaryButtonActive color("#d9d9d9").brighten(0.35) Fill (active element) "
secondaryButtonText color("#000000") Text "
secondaryButtonStroke color("#FFFFFF") Border "
grid color("#000000") Line color Axis (axis line, grid, breaks, ticks), XY cursor, Map lines.
background color("#ffffff") Fill Fill of any container, including main chart element, Axis break fill, XY Chart Scrollbar background, etc..
alternativeBackground color("#000000") Fill Axis fills, full-width XY cursor, Gauge clock hand, Flow diagram links, Shadow filter, misc map controls, Navigation bar arrow, etc.
text color("#000000") Text Text color (useful for changing text color for dark themes).
alternativeText color("#FFFFFF") Text Axis tooltip text, Scrollbar grip visual elements.
disabledBackground color("#999999") Fill (disabled element) Disabled Legend items, toggled off Chord and Sankey nodes.
positive color("#67dc75") Fill Positive Candlesticks.
negative color("#dc6788") Fill Negative Candlesticks.