States

A "state" in amCharts 4 world is a collection of properties and settings that can be dynamically applied to a chart element whenever certain event occurs or programatically via API. The chart engine intelligently handles transitions between states, applying only overriding properties, and gently animating between current and target values, if you have animations enabled.

Some states come pre-defined, such as "default" or "hidden" state. Some are created upon need, such as "hover". And, finally, some are completely custom, applied by your custom code according to your own logic and requirements.

This article aims at explaining how states work, how to create and configure them, as well how they can be used to create awesome UX.

Prerequisites

Before we plunge into exploring states, we strongly suggest familiarizing with another amCharts 4's unique concept - list templates - as it will be used quite extensively in the examples throughout this article.

Creating

Each element in the chart has a property named states. It's a collection (or a DictionaryTemplate as we called it here). It binds a state name (like "hover") to an actual SpriteState object that defines properties and other values to be applied whenever the state is applied to that element.

To create a state we use states.create(name). For example, the following will create a "hover" state for all columns in a Column series, we'd use states of that series' column template. (please, if you haven't done so, read about list templates now ;)

let hoverState = columnSeries.columns.template.states.create("hover");
var hoverState = columnSeries.columns.template.states.create("hover");
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    // ...
    "states": {
      "hover": {
        // ...
      }
    }
  }]
}

NOTE "hover" is an example of a reserved-name state. It's not created automatically, but it will be applied to the hovered element automatically, if you create it.

Configuring

Now that we have a state instance in a variable, let's explore how we can configure it.

Properties

As mentioned before, a state, when applied to an element, can override any of its properties with a new value.

Each state instance has a property named properties which is a simple object where we put override values. A key is a property we want to override, and value is the value we want to override it with.

Here's how state's properties might look if we want to override a fill color and opacity when the state is applied to an element:

{
  "fill": am4core.color("#E94F37"),
  "fillOpacity": 0.8
}

Let's put this into perspective of a real example:

let hoverState = columnSeries.columns.template.states.create("hover");
hoverState.properties.fill = am4core.color("#E94F37");
hoverState.properties.fillOpacity = 0.8;
var hoverState = columnSeries.columns.template.states.create("hover");
hoverState.properties.fill = am4core.color("#E94F37");
hoverState.properties.fillOpacity = 0.8;
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    // ...
    "states": {
      "hover": {
        "properties": {
          "fill": "#E94F37",
          "fillOpacity": 0.8
        }
      }
    }
  }]
}

And now, onto a live example: (go ahead, try hovering over the columns)

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

We've intentionally created our column series with bluish, almost translucent fills. When a column is hovered, a "hover" state is automatically applied to it, which makes it gently transit to the new color and also increase fill opacity, as we have specified in properties.

Filters

Besides basic properties, a state can also apply filters to its target elements.

For that purpose, it has filters property, which is a list, you can push filters, like a DropShadowFilter, to.

Let's see how it actually works, by enhancing our sample from before to display a gentle shadow over each column, but make it more prominent when "hover" state is applied.

/* Create a gentle shadow for columns */
let shadow = columnSeries.columns.template.filters.push(new am4core.DropShadowFilter);
shadow.opacity = 0.1;

/* Create hover state */
let hoverState = columnSeries.columns.template.states.create("hover");
hoverState.properties.fill = am4core.color("#396478");
hoverState.properties.dx = -5;
hoverState.properties.dy = -5;

/* Slightly shift the shadow and make it more prominent on hover */
let hoverShadow = hoverState.filters.push(new am4core.DropShadowFilter);
hoverShadow.dx = 6;
hoverShadow.dy = 6;
hoverShadow.opacity = 0.3;
/* Create a gentle shadow for columns */
var shadow = columnSeries.columns.template.filters.push(new am4core.DropShadowFilter);
shadow.opacity = 0.1;

// Create hover state
var hoverState = columnSeries.columns.template.states.create("hover");
hoverState.properties.fill = am4core.color("#396478");
hoverState.properties.dx = -5;
hoverState.properties.dy = -5;

// Slightly shift the shadow and make it more prominent on hover
var hoverShadow = hoverState.filters.push(new am4core.DropShadowFilter);
hoverShadow.dx = 6;
hoverShadow.dy = 6;
hoverShadow.opacity = 0.3;
{
  // ...
  "series": [{
    "type": "ColumnSeries",
    // ...
    "columns": {
      "filters": [{
        "type": "DropShadowFilter",
        "opacity": 0.1
      }],
      "states": {
        "hover": {
          "properties": {
            "fill": "#396478",
            "dx": -5,
            "dy": -5
          },
          "filters": [{
            "type": "DropShadowFilter",
            "dx": 6,
            "dy": 6,
           "opacity": 0.3
          }]
        }
      }
    }
  }]
}

Notice how we also shifting the column itself on hover state, only in the opposite direction than we shift the shadow, to create a nice rise effect.

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

Applying

As we mentioned before, some states are pre-created for us and are applied automatically. Some use reserved names, but require to be created before can be applied. And some are completely up to you to think of, create, and apply.

Let's explore each.

Accessing state objects

To get particular state out of the element's states dictionary, we use its getKey(name) method.

To check if the element has such state already defined, we can use hasKey(name) as well. E.g.:

let columnTemplate = ccolumnSeries.columns.template;
let hoverState;
if (columnTemplate.states.hasKey("hover")) {
  /* "hover" state already exists; use getKey() to retrieve it */
  hoverState = columnSeries.columns.template.states.getKey("hover");
}
else {
  /* "hover" state has not yet been created, create it */
  hoverState = columnSeries.columns.template.states.create("hover");
}
var columnTemplate = ccolumnSeries.columns.template;
var hoverState;
if (columnTemplate.states.hasKey("hover")) {
  /* "hover" state already exists; use getKey() to retrieve it */
  hoverState = columnSeries.columns.template.states.getKey("hover");
}
else {
  /* "hover" state has not yet been created, create it */
  hoverState = columnSeries.columns.template.states.create("hover");
}

"default" state

A "default" state is a special state which holds initial values for element's properties and filters.

Essentially, it represents all properties that were set directly on the element.

Whenever some state is applied to an element, and some properties of are being overridden, original values of those properties are saved in the "default" state.

When we want the element to go back the way before another state was applied, like element is no longer being hovered, a "default" state is applied to the element, which resets overridden values back to the way they were before.

Please note that "default" state does not reset the all element's properties to the values that were when the element first appeared. When it is applied, it resets just those values that were overridden by other states, or that were explicitly set for the "default" state.

As an example, let's take a Slice element in a Pie chart. It's fill is green by default. The "hover" state, has fill set to red. When you hover the slice, "hover" state kicks in and sets fill to red. When Slice is no longer hovered, a "default" state is applied, and resets fill back to green.

A "default" theme just reset fill - what was overridden by another state (in this case "hover"). It did not try to reset any other properties that might have changed between the Slice came into existence and was unhovered.

TIP A "default" state is also accessible using shortcut property defaultState. (which is equivalent of states.getKey("default"))

"hidden" state

Besides "default", every element has another special stated: "hidden".

This state is automatically applied to all elements when they are being hidden, e.g. by hide() method.

Internally, it has elements opacity set to zero, so that it gradually fades out to nothingness (if animations are enabled), instead of brutally disappearing out of the view.

Normally, you don't have to worry about this - it all happens transparently. However, if you'd like to apply some special properties to elements that are being hidden (e.g. maybe you want to make hidden elements just barely visible instead of completely gone), you're free to modify "hidden" state the way you want it to be.

TIP A "hidden" state is also accessible using shortcut property hiddenState. (which is equivalent of states.getKey("hidden"))

Reserved named states

Some states are not created by default, but nevertheless are being applied (if we create them manually) in certain situations.

We've already met one such friend - "hover" - which is a state applied to the elements when they are being hovered with a cursor or touched on a touch screen.

Here's the full list:

Name Applied when Related events
"active" Applied when the element is set as "active".

This could happen when a "togglable" element is toggled on, like, for example a Legend item, or clicked Pie chart slice.

"hit"
"default" Applied when when we need to reset element's properties overridden by some other state, e.g. when element is no longer hovered. "out", "blur", "up", "shown", etc.
"down" Applied when a pointer is pushing down on the element, e.g. mouse cursor is over element and button is pressed, or is being touched on a touchscreen. "down"
"hidden" Applied when element is hidden, e.g. with a hide() call. "hidden"
"hover" Applied when element has a mouse cursor over it, or is being touched on a touchscreen. "over"
"hoverActive" Applied when element which is "active" (isActive = true) has a mouse cursor over it, or is being touched on a touchscreen. "over", "toggled"

Custom states

So far we have been dealing with automatically-applied states. We just needed to create and configure them. They were applied for use automatically, on certain events.

However, we are not limited to just those states. We can create any number of custom states and apply them as we wish.

For example, we might want to apply special formatting to a chart element, say a Pie chart slice, to make some slices "disabled".

We could create a custom state "disabled" to make some slices dimmed out and unresponsive. Then reset it back by applying "default" state.

All of this using custom function, invoked by a custom button:

let disabledState = pieSeries.slices.template.states.create("disabled");
disabledState.properties.fill = am4core.color("#000");
disabledState.properties.fillOpacity = 0.1;
disabledState.properties.shiftRadius = 0;
disabledState.properties.scale = 1;
disabledState.properties.hoverable = false;
disabledState.properties.clickable = false;

function disableSlice(index) {
  let slice = pieSeries.slices.getIndex(index);
  if (slice.hoverable) {
    slice.setState("disabled");
  }
  else {
    slice.setState("default");
  }
}
var disabledState = pieSeries.slices.template.states.create("disabled");
disabledState.properties.fill = am4core.color("#000");
disabledState.properties.fillOpacity = 0.1;
disabledState.properties.shiftRadius = 0;
disabledState.properties.scale = 1;
disabledState.properties.hoverable = false;
disabledState.properties.clickable = false;

function disableSlice(index) {
  var slice = pieSeries.slices.getIndex(index);
  if (slice.hoverable) {
    slice.setState("disabled");
  }
  else {
    slice.setState("default");
  }
}

Go ahead, try it out on a live example:

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

Cascading state application

Suppose we have a bullet of type CircleBullet. That bullet has a child element - a Circle.

On an XY chart with a Cursor, all bullets in the category/date will automatically have their "hover" state applied when cursor is over that category/date.

We want our Circle to change color on hover, so we create a "hover" state for it, but since hover state is applied to the CircleBullet which is Circle's parent, we don't get the desired effect.

We somehow need to instruct our bullet to pass on the state to its children, when it gets that state applied.

This is where setStateOnChildren setting comes in.

If we set it on our CircleBullet to true, whenever CircleBullet gets any state applied, the same state is applied to all its child elements, including our Circle.

bullet.setStateOnChildren = true;
bullet.setStateOnChildren = true;
{
  // ...
  "series": [{
    // ...
    "bullets": [{
      "type": "CircleBullet",
      // ...
      "setStateOnChildren": true
    }]
  }]
}

Binding to data

As with each item in series, a state is not limited to using static values for properties. It can also bind to data for dynamic values.

For that, there's propertyFields property in each state object.

It's an object that has target element properties as keys, and in its value it holds a string that points to a key in the data.

Using this approach, we could easily make the same state use different values for each individual data item in the series.

For example, we could have data hold different fill color for each column in Column series.

We're just going to put a working example here, that does just that:

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

States and themes

In amCharts 4 themes are not just collection of color properties. They can change functionality, even create objects, including states.

This means, that some themes might modify behavior of default states, or even pre-create and pre-configure reserved themes, such as hover.

Themes & animations

Another relationship with themes is animation settings.

By default, charts are not animated, so they appear faster. This means that transitions between states will be instantaneous.

Some themes, in particularly bundled "Animated" theme, will automatically set required settings to make all transitions animated.

Related content