Make your own Custom Bar Chart Race with D3.js
By: Ömer Karataş
Tags: Bar Chart Race, Stop Resume Animation, Bar Chart, JavaScript, D3js

Draw Function

In the beginning of this function, you will see dataSetDescendingOrder , it is just for order the dataSet in descending order. But you can also sort your data before giving to draw function. In the following line, the current date is just set in year format, which is shown in lower-right side of the chart.

xAxisScale.domain([0, dataSetDescendingOrder[0].value]);

yAxisScale.domain(dataSetDescendingOrder.map(({ name }) => name)); xAxisContainer.transition(transition).call(

d3

.axisTop(xAxisScale)

.ticks(ticksInXAxis)

.tickSize(-innerHeight)

); yAxisContainer

.transition(transition)

.call(d3.axisLeft(yAxisScale).tickSize(0));

We left the scale functions uncompleted for both axis, did you remember? Here is our missing parts for them. We define domain for both axis depending on our current dataSet, i.e. dataSetDescendingOrder . For x-axis, has a continuous range, we define the domain by setting minimum and maximum values on the dataSet. While creating random data, I used 0(zero) for min value, and the first element of dataSet array will be maximum, due to descending order. Depending on your data, you need to calculate these range values by yourself. For y-axis, has categorical values, we define the domain as array of name s.

We have the domains for the scale functions. So what? What is the point here, you might ask. Well, these utility functions help us to create the mapping. We will use these in two different points. First, to create axis elements, i.e. xAxisContainer and yAxisContainer . They will create and append required elements to DOM. In above, as you can see, there are axisTop and axisLeft methods to create axis related SVG elements. For details, look here. tickSize(-innerHeight) is a trick to show gridlines for the tick values of x-axis. Also check the css rules for both axises, because some texts and lines are invisible. For the second place scale functions used, to find mapping value, check in later section for that.

Did you notice the transition function at above code lines. Maybe you asked yourself, is it something useful? Well, this special function has the responsible of making animation. Wow, we now have the ability of showing something with animations. But how this function create animation for us? The answer is lying on its name. It will transit elements from their current state to their desired state. Knowing the current and desired states, d3 apply changes between two states by interpolating these values. Some primitive values like Strings or Numbers can be interpolated by built-in interpolators, additionally you can create your own custom interpolators. In later parts of this section there is an example of the custom interpolators.

So, we have done with axis functions. Let’s continue with columns and theirs titles and values.

The General Update Pattern

In the next lines, you will see a very common pattern used in D3.js, the update pattern. But, at first, we need to bind our data to the html elements.

const barGroups = chartContainer

.select(".columns")

.selectAll("g.column-container")

.data(dataSetDescendingOrder, ({ name }) => name);

We select all g elements having class name column-container . Hmmm, but there seems a problem here, since we don’t have any of that element rendered in the DOM yet. We add them neither in the html file explicitly nor in the javascript file dynamically. So, this is an empty selection so far. But we use another D3.js method, i.e. data . This special method binds our data, array in our case, to the selected elements. The second parameter of data function gets a key function. It decides that how to differentiate each elements in data. If you are familiar with one of the popular front end frameworks, it is similar to key operator. I just used name field on my data, if you have any unique field in your data, such as id, you can use that.

The data function returns a selection that represents update selection. Now, you can use enter and exit selections.

Enter Selection

You have successfully bound the data. Now, you can add elements to the DOM, depending on your data.

const barGroupsEnter = barGroups

.enter()

.append("g")

.attr("class", "column-container")

.attr("transform", `translate(0,${innerHeight})`);

When you call enter function, you actually create elements in the DOM, and you will set up initial settings for the first time appearance. Whenever a new data, i.e. having a unique key value, is added by using data function, the settings for the element, functions come after the enter , will run.

In our case, we add column-container elements to the DOM by using append function. We set its initial position as the most bottom line of the chart, translate(0,${innerHeight}) . And, in the next lines, we append actual SVG elements that form column, i.e. rectangle, its title and its value inside column container . The column rectangle has 0 width initially to show growing animation, and has a constant height calculated by using step function of our axis scale. For column title and its value, their x and y attributes are calculated also by using step function. As a final step, we set their innerText values by using text method. Column title will remain the same, but we set column value as 0 to able to use tween function, which will be explained in later section.

Update Selection

By using enter selections, we have already given initial values to our data. Now we will set their updated values, the real values that we want to create a transition, i.e. animation.

const barUpdate = barGroupsEnter.merge(barGroups);

D3 has merge method to connect enter sections with update sections. It will just give us an update selection. By using barUpdate , we will use to populate updated values.

In next lines of the code, we set our elements’ actual data(attributes or innerText). Moreover, we use transition functions here to make an animation. From the values given in enter selection to update selection, D3.js will make a transition/animation in given duration time(see Render function section). D3.js has able to calculate interpolated values itself. However, you can also define your own custom interpolator function.

.tween("text", function({ value }) {

const interpolateStartValue =

elapsedTime === chartSettings.duration

? this.currentValue || 0

: +this.innerHTML; const interpolate = d3.interpolate(interpolateStartValue, value);

this.currentValue = value; return function(t) {

d3.select(this).text(Math.ceil(interpolate(t)));

};

});

It’s nothing but tween function to create our custom interpolations. It takes an attribute name as first argument, and a function as second argument. In second argument you can define your own interpolation function and return it. interpolate method takes two argument, starting value and final value. And using these arguments it can populate corresponding value at given eased time. Under the hood, the return function of tween will be repeatedly called during the transition phase and using the time has been passed, t , you can evaluate next value by calling interpolate(t) . This is an example of how you can create a custom interpolator.

Note: Whenever a new data comes, first enter selection will be run for its corresponding element and then update selection will be run. However, when a new updated data comes, the update selection will be run only, not enter selection.

Exit Selection

By using enter and update selection, we append the elements to the DOM and are able to set their values by using the corresponding data or even create a transition. But, when some data points are being removed then we need to decide how to these data points should be removed from DOM. In this point, exit selection takes the stage.

const bodyExit = barGroups.exit(); bodyExit

.transition(transition)

.attr("transform", `translate(0,${innerHeight})`)

.remove();

In exit selection, we decide that what will happened to the elements right before exiting from the data, mostly their removals from the DOM. For example, for column-container elements, we set their transform attributes with transition function and then we will remove the element. By giving this transition, our columns will go to the bottom line first by decreasing their rectangle width and value. And when they reach to bottom-most position then they will disappear and will be removed from DOM.