Animated D3.js scatter plot in R

By Data Tricks, 5 July 2020

Following my first foray into D3.js in R, this tutorial explains how to create an animated scatter plot in D3.js using the r2d3 package. The plot includes an on-load animation and mouseover effects.

Step 1: create data and send to r2d3

First we’ll create an R script which creates the data and sends it to the r2d3 function. Save the following code into an R file.

library(r2d3)
setwd("~/R projects/D3.js") #Set this to your working directory

set.seed(250)
data <- data.frame(x = rnorm(100, mean = 50, sd = 15),
                   add = rnorm(100, mean = 50, sd = 15),
                   size = rep(3,100))
data$y <- data$x + data$add

r2d3(data=data,
     script = "Scatter chart.js",
     options = list(margin = 50,
               barPadding = 0.1,
               colour = "rgba(255,0,0,1)",
               hovercolour = "rgba(50,50,50,1)",
               xLabel = "x label",
               yLabel = "y label",
               xmin = 0,
               xmax = max(data$x),
               ymin = 0,
               ymax = max(data$y),
               chartTitle = "Chart Title"
     )
)

Step 2: Create the js code

Create a blank file called Scatter chart.js and save it in your working directory. This is where we’ll put all the d3.js code.

First we can set some initial values such as margin and width, and get the minimum and maximum values of x and y from the options already specified in the R code. We can then use these to create the axes. Add the following code to your Scatter chart.js file.

//Set some initial values
var margin = options.margin,
    width = width-(2*margin), height = height-(2*margin),
    xmax = options.xmax,
    xmin = options.xmin,
    ymax = options.ymax,
    ymin = options.ymin;

//Create the axes
x = d3.scaleLinear()
    .range([margin, margin+width])
    .domain([xmin, xmax]);
y = d3.scaleLinear()
    .range([height, 0])
    .domain([ymin, ymax]);

//Append axes
svg.append("g")
  .attr("transform", "translate(" + 0 + "," + (margin+y(0)) + ")")
  .call(d3.axisBottom(x));
svg.append("g")
  .attr("transform", "translate(" + x(0) + ", " + margin + ")")
  .call(d3.axisLeft(y));

Save your js file and go back to your R script and run the whole code. You should get something like this:

We can then add axis labels and a chart title as follows:

//Axes labels
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); 

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

We can now start creating the chart by adding the markers, or dots, together with the on-load animation. You’ll notice below that when we first create the dots, we set the location as 0,0 ie. the intercept of the x and y axes. Later in the on-load animation, we set the true location of the dots with a smooth transition. By adding a delay to each dot’s animation, this creates the effect of all the dots ‘spraying’ out from 0,0.

//Create the chart
svg.selectAll("dot")
  .data(data)
  .enter()
  .append("circle")
  .attr("cx", function (d) { return x(0); } )
  .attr("cy", function (d) { return y(0)+margin; } )
  .attr("r", 2)
  .style("fill", options.colour);

//On-load transition for circles
svg.selectAll('circle')
  .transition()
  .delay(function(d,i){return (i*30);}) .duration(function(d,i){return (2000+(i*2));})
  .ease(d3.easeBack)
  .attr("cx", function (d) { return x(d.x); } )
  .attr("cy", function (d) { return y(d.y)+margin; } )
  .attr("r", function (d) { return d.size; });

Tip: in the example above we’ve set the transition easing to be easeBack. There are plenty of transition easings available. Check out the complete list at Bl.ocks.

Our chart is now beginning to take shape! The final step is to create a tooltip which is displayed when the user hovers their mouse over each marker.

//Create a tooltip
var Tooltip = d3.select('#htmlwidget_container')
  .append('div')
  .attr("class", "tooltip")
  .style('transform', 'scale(0,0)')
  .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");

// Three function that change the tooltip when user hovers, moves and leaves each dot
var mouseover = function(d) {
Tooltip
  .style('transition', '0.4s transform cubic-bezier(0.5,0.8,0,1.7)')
  .style('transform', 'scale(1,1)')
  .style('opacity', 1)
  .style('box-shadow', '5px 5px 5px rgba(0,0,0,0.2)');
d3.select(this)
  .style('transition', '0.4s all cubic-bezier(0.5,0.8,0,1.7)')
  .style('fill', options.hovercolour)
  .attr("r", function (d) { return d.size + 10; });
};

var mousemove = function(d) {
Tooltip
  .html('x: ' + d.x + '
' + 'y: ' + d.y)
  .style("left", (d3.mouse(this)[0]+30) + "px")
  .style("top", (d3.mouse(this)[1]+30) + "px");
};

var mouseleave = function(d) {
Tooltip
  .style("opacity", 0)
  .style('transform', 'scale(0,0)');
d3.select(this)
  .style('transition', '0.4s all cubic-bezier(0.5,0.8,0,1.7)')
  .style('fill', options.colour)
  .attr("r", function (d) { return d.size; });
};

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

You should now have a chart which look like this:

I hope you found this tutorial useful. As always please share your thoughts or questions in the comments below.

Tags: , ,

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.