Animated D3.js bar chart in R

By Data Tricks, 11 June 2020

In this tutorial we will create an animated D3.js bar chart in R using the r2d3 package. This is a follow up and improvement of the bar chart created in this tutorial.

Step 1: Create practice data and send to r2d3

We will use the same R script as we used in the previous tutorial so please read this if you’d like an fuller explanation of this code.

library(r2d3)
setwd("~/R projects/D3.js")

data <- data.frame(year=c(2011:2016), value=c(0.45, 0.47, 0.52, 0.7, 0.59, 0.47))

r2d3(data=data,
    script = "Custom D3.js",
    options = list(margin = 50,
        barPadding = 0.1,
        colour = "rgba(255,0,0,1)",
        xLabel = "Year",
        yLabel = "Value",
        chartTitle = "Chart Title"
    )
)

Once you’ve created this code, save it as Bar_chart.R.

Step 2: Create the js code

Our js file will be based largely on the js code in the previous tutorial with two important differences – we are going to add some css transitions to animate the bars when the chart is first loaded, and add a tooltip when the user hovers over each bar.

The code is largely based on the js file in our previous tutorial so we are first going to set some initial values, create the x and y axes, chart title and create the basic bar chart with ‘rect’ elements.

//Set some initial values
var margin = options.margin,
    width = width-(2*margin),
    height = height-(2*margin),
    barPadding = options.barPadding*(width/data.length),
    barWidth = (width-(data.length*barPadding))/data.length,
    xmax = d3.max(data, function(d) { return d.year; }),
    xmin = d3.min(data, function(d) { return d.year; }),
    ymax = d3.max(data, function(d) { return d.value; });

//Create the x axis
var x = d3.scaleBand()
    .domain(data.map(function(d) { return d.year; }))
    .range([margin, margin+width]);
svg.append("g")
    .attr("transform", "translate(" + 0 + "," + (height+margin) + ")")
    .call(d3.axisBottom(x));
svg.append("text")
    .attr("transform", "translate(" + (width/2) + " ," + (height+2*margin) + ")")
    .attr("dx", "1em")
    .style("text-anchor", "middle")
    .style("font-family", "Tahoma, Geneva, sans-serif")
    .style("font-size", "12pt")
    .text(options.xLabel);

//Create the y axis
    var y = d3.scaleLinear()
    .range([height, 0])
    .domain([0, ymax]);
svg.append("g")
    .attr("transform", "translate(" + margin + ", " + margin + ")")
    .call(d3.axisLeft(y));
svg.append("text")
    .attr("transform", "translate(" + 0 + " ," + ((height+2*margin)/2) + ") rotate(-90)")
    .attr("dy", "1em")
    .style("text-anchor", "middle")
    .style("font-family", "Tahoma, Geneva, sans-serif")
    .style("font-size", "12pt")
    .text(options.yLabel);

//Create the chart title
svg.append("text")
    .attr("x", (width / 2))
    .attr("y", (margin/2))
    .attr("text-anchor", "middle")
    .attr("dx", "1em")
    .style("font-size", "16pt")
    .style("font-family", "Tahoma, Geneva, sans-serif")
    .text(options.chartTitle);

//Create the chart
svg.selectAll('rect')
    .data(data)
    .enter()
    .append('rect')
    .attr('width', barWidth)
    .attr('x', function(d, i) { return (margin+((i+0.5)*barPadding)+(i*barWidth)); })
    .attr('y', height + margin)
    .attr('fill', options.colour);

Save this file as Custom D3.js in the same folder as your R script and run your R script. You should get a chart that looks like this:

You’ll see that there are no bars yet. This is not a problem, and is because we have created the ‘rect’ elements, set the width and x, y coordinates but we haven’t yet set the height of the bars, so they currently have a height of 0px. This is because we want to add a css transition so that the bars appear from the x axis and smoothly animate to their correct height.

Before we get to the animation, we need to understand an important point with how SVG ‘rect’ elements are created. The x and y coordinates relate to the top left corner of each bar. Thus, if we simply change the height of each bar from 0 to 100%, the bar will actually animate from the x axis downwards. However, for this bar chart we want to bars to animate from the x axis upwards.

The solution we have chosen is to transition the height of each bar from 0 to 100% at the same time as transitioning the y coordinate from the x axis to x axis minus the height of the bar. If both these animations run at exactly the same time and duration, it will appear as if the bar is rising from the x axis.

Add transitions when the chart is first loaded

Add the following text to your Custom D3.js script.

//Transition animation on load
svg.selectAll('rect')
    .transition()
    .delay(function(d,i){return (i*100);})
    .duration(function(d,i){return (1000+(i*200));})
    .attr('height', function(d) { return d.value/ymax * height; })
    .attr('y', function(d) { return (height+margin-(d.value/ymax * height)); });

To initiate a css transition we use .transition() followed by .delay(). Within the brackets of .delay() should be a value in milliseconds. In our example, we have used a function to define the delay so that each bar is animated with 100ms intervals.

Once the transition has been initiated, we can then change the attributes of whatever we want to animate, in this case the height and y coordinate as explained above.

Add the above code to your Custom D3.js script and you should get a chart that looks like this:

When the chart first loads, the bars should smoothly transition from the x axis upwards, with a short delay between each one. Click on “Reload” above to see this in action.

Add animated tooltips

Next we are going to add a tooltip to each bar which appears when the use hovers over the bar, and follows the mouse cursor across the screen.

First we’ll add the tooltip and style it:

//Create a tooltip
var Tooltip = d3.select('#htmlwidget_container')
    .append('div')
    .attr("class", "tooltip")
    .style('position', 'absolute')
    .style('background-color', 'rgba(255,255,255,0.8)')
    .style('border-radius', '5px')
    .style('padding', '5px')
    .style('opacity', 0)
    .style("font-family", "Tahoma, Geneva, sans-serif")
    .style("font-size", "12pt");

Then we’ll create the mouseover effects. We’ll need 3 of these – one for when the user moves the mouse into the bar, another to make the tooltip follow the mouse cursor, and the last one to make the tooltip disappear when the user moves the mouse away from the bar.

//Mouseover effects for tooltip
var mouseover = function(d) {
    Tooltip
        .style('opacity', 1)
        .style('box-shadow', '5px 5px 5px rgba(0,0,0,0.2)');
    d3.select(this)
        .attr('fill', 'rgba(100,0,0,1)');
};
var mousemove = function(d) {
    Tooltip
        .html('Year ' + d.year + ': ' + d.value)
        .style("left", (d3.mouse(this)[0]+30) + "px")
        .style("top", (d3.mouse(this)[1]+30) + "px");
};
var mouseleave = function(d) {
    Tooltip
        .style("opacity", 0);
    d3.select(this)
        .attr('fill', options.colour);
};

Finally, we need to ensure these three functions are called:

svg.selectAll('rect')
    .on("mouseover", mouseover)
    .on("mousemove", mousemove)
    .on("mouseleave", mouseleave);

Add all of this code to your Custom D3.js scripts and you should get a chart like this:

And we’re done! You’ll notice that we also change the colour of the bar in the mouseover function using d3.select(this) and defining the fill colour. Remember if you’re doing this, you’ll need to set the fill colour back what it was originally in the mouseleave function, otherwise the hover colour will remain permanently.

I hope you find this tutorial useful. As always, comments and question are welcome using the form below and I’ll endeavour to reply to all queries.

Tags: , ,

One thought on “Animated D3.js bar chart in R”

Leave a Reply

Your email address will not be published. Required fields are marked *

Please note that your first comment on this site will be moderated, after which you will be able to comment freely.