Custom JavaScript visualizations in RMarkdown

I happened to stumble upon the preview release page for RStudio recently and noticed something that made me exorbitantly happy.

A preview release of RStudio v1.0.136 is now available for testing and feedback. This release includes… Support for JavaScript and CSS chunks in R Notebooks…

“Support for JavaScript and CSS chunks in R Notebooks”! As someone who loves using javascript for plotting (and secretly for manipulating) data this is massively exciting. Previously my workflow for generating an interactive graphic would be something like:

  • Download data
  • Clean data and do preliminary visualization in R
  • Export what I liked to a csv file and make a new directory with a set of .html, .js, .css files in it.
  • Load data into javascript/d3.js in the new javascript file.
  • Plot.

This workflow has served me very well, I did it probably 20 times a day when working at the New York Times and I am very fast with it. If you want to just make a stand alone visualization it’s fantastic, however, as a biostatistician who works with a lot of other biostatisticians, people tend to want to see where the data comes from.

With Javascript chunks in .Rmd files you can explicitly show the code for data gathering/cleaning etc. in a language that your collaborators can understand, along with making custom d3 charts with that data. All without ever leaving RStudio. I will show you my quick and dirty solution to doing so.

Getting data into Javascript

Instead of generating a csv file in R and then loading that into javascript we will instead send the data directly through the html to javascript. (Note: This wont work well with super large data).

Inspired by this medium post I wrote a little function that takes a dataframe and sends it to the html document in the .json format.


send_df_to_js <- function(df){
      var data = ',toJSON(df),';
    , sep="")

To illustrate how it works we will generate some random data into a dataframe.

#Generate some random x and y data to plot
n <- 300
random_data <- data_frame(x = runif(n)*10) %>% 
  mutate(y = 0.5*x^3 - 1.3*x^2 + rnorm(n, mean = 0, sd = 80),
         group = paste("group", sample(c(1,2,3), n, replace = T)))

Now we send a snippit of the dataframe into the function to see the output…

random_data %>% 
  head() %>% 
## <script>
##       var data = [{"x":8.1137,"y":45.704,"group":"group 2"},{"x":8.0455,"y":10.4571,"group":"group 1"},{"x":0.5177,"y":70.1536,"group":"group 3"},{"x":0.171,"y":51.6814,"group":"group 2"},{"x":2.2069,"y":-26.1143,"group":"group 1"},{"x":2.4624,"y":-78.9991,"group":"group 1"}];
##     </script>

Beautiful, we have our data, in json format, wrapped in a script tag. Now we can send the whole dataframe through. This time I am using the results = "asis" option in the code chunk ({r, results = "asis"}), to write the results directly to the html document and not to the output like we did above.

#Initiate data transfer protocol one

Now our data is inside our page’s javascript scope and ready to be played with!

Drawing pretty pictures

Let’s make a super simple d3 scatter plot to see this randomly generated data. All I have to do is include my desired javascript libraries, make a div element for my visualization to go into and then put my {js} block in. RMarkdown will slot the javascript into the page and we’re good to go.

In this example I did…

<script src=""></script>
<script src=""></script>

<div id = "viz"></div> 

` ` `{js}
//code goes here
` ` `

Did it work?

Why/ When

This is a bad example of a visualization for this scenario as something like plotly could do this in much less effort. If you’re doing something more complicated/ bespoke then this is a great resource to have.


If you’re interested, here’s the javascript code I included to make the above graph.

var point_vals ="#viz").append("p").text("Mouseover some data!");

//Get how wide our page is in pixels so we can draw our plot in it
var page_width = $("#did-it-work").width();
// set the dimensions and margins of the graph
var margin = 20,
    width = page_width - 2*margin,
    height = page_width*0.8 - 2*margin;
// Find max data values
var x_extent = d3.extent(data, d => d.x);
var y_extent = d3.extent(data, d => d.y);

// Set the scales 
var x = d3.scaleLinear()
  .range([0, width]);
var y = d3.scaleLinear()
  .range([height, 0]);

//Set up our SVG element
var svg ="#viz").append("svg")
    .attr("width", width + 2*margin)
    .attr("height", height + 2*margin)
          "translate(" + margin + "," + margin + ")");

var bounce_select = d3.transition()
// Add the scatterplot
    .attr("class", "dots")
    .attr("fill", d => === "group 1"? "steelblue":"orangered")
    .attr("fill-opacity", 0.3)
    .attr("r", 5)
    .attr("cx", d => x(d.x) )
    .attr("cy", d => y(d.y) )
    .on("mouseover", function(d){
       d3.selectAll(".dots").attr("r", 5) //make sure all the dots are small
        .attr("r", 10);
       point_vals.text("X:" + d.x + " Y:" + d.y) //change the title of the graph to the datapoint
// Draw the axes    
// Add the X Axis
    .attr("transform", "translate(0," + height + ")")

// Add the Y Axis

Nick Strayer image
Nick Strayer

Randomly walking my way though a career in statistics

comments powered by Disqus