Boxplot with individual data points

This post follows the previous basic boxplot. It shows how to display individual data points using jitter. It's a good practice: basic boxplots hide features like the sample size and the exact distribution. More boxplot examples in the dedicated section.

Boxplot section


  • All the hard part is described in the previous multigroup boxplot

  • Here, the trick is just to add a layer of points by appending circle.

  • To avoid circles overlap, jittering is used. Basically, every point is shiffted on the X axis randomly, using Math.random()
<!DOCTYPE html>
<meta charset="utf-8">

<!-- Load d3.js -->
<script src=""></script>

<!-- Create a div where the graph will take place -->
<div id="my_dataviz"></div>


// set the dimensions and margins of the graph
var margin = {top: 10, right: 30, bottom: 30, left: 40},
    width = 460 - margin.left - margin.right,
    height = 400 - - margin.bottom;

// append the svg object to the body of the page
var svg ="#my_dataviz")
    .attr("width", width + margin.left + margin.right)
    .attr("height", height + + margin.bottom)
          "translate(" + margin.left + "," + + ")");

// Read the data and compute summary statistics for each specie
d3.csv("", function(data) {

  // Compute quartiles, median, inter quantile range min and max --> these info are then used to draw the box.
  var sumstat = d3.nest() // nest function allows to group the calculation per level of a factor
    .key(function(d) { return d.Species;})
    .rollup(function(d) {
      q1 = d3.quantile( { return g.Sepal_Length;}).sort(d3.ascending),.25)
      median = d3.quantile( { return g.Sepal_Length;}).sort(d3.ascending),.5)
      q3 = d3.quantile( { return g.Sepal_Length;}).sort(d3.ascending),.75)
      interQuantileRange = q3 - q1
      min = q1 - 1.5 * interQuantileRange
      max = q3 + 1.5 * interQuantileRange
      return({q1: q1, median: median, q3: q3, interQuantileRange: interQuantileRange, min: min, max: max})

  // Show the X scale
  var x = d3.scaleBand()
    .range([ 0, width ])
    .domain(["setosa", "versicolor", "virginica"])
    .attr("transform", "translate(0," + height + ")")

  // Show the Y scale
  var y = d3.scaleLinear()
    .range([height, 0])

  // Show the main vertical line
      .attr("x1", function(d){return(x(d.key))})
      .attr("x2", function(d){return(x(d.key))})
      .attr("y1", function(d){return(y(d.value.min))})
      .attr("y2", function(d){return(y(d.value.max))})
      .attr("stroke", "black")
      .style("width", 40)

  // rectangle for the main box
  var boxWidth = 100
        .attr("x", function(d){return(x(d.key)-boxWidth/2)})
        .attr("y", function(d){return(y(d.value.q3))})
        .attr("height", function(d){return(y(d.value.q1)-y(d.value.q3))})
        .attr("width", boxWidth )
        .attr("stroke", "black")
        .style("fill", "#69b3a2")

  // Show the median
      .attr("x1", function(d){return(x(d.key)-boxWidth/2) })
      .attr("x2", function(d){return(x(d.key)+boxWidth/2) })
      .attr("y1", function(d){return(y(d.value.median))})
      .attr("y2", function(d){return(y(d.value.median))})
      .attr("stroke", "black")
      .style("width", 80)

// Add individual points with jitter
var jitterWidth = 50
    .attr("cx", function(d){return(x(d.Species) - jitterWidth/2 + Math.random()*jitterWidth )})
    .attr("cy", function(d){return(y(d.Sepal_Length))})
    .attr("r", 4)
    .style("fill", "white")
    .attr("stroke", "black")



Related blocks →

  • Simple Box Plot Example in d3.js v4.0 - link

  • D3.js Boxplot with Axes and Labels - link