Skip to content

Instantly share code, notes, and snippets.

@rjurney
Created June 18, 2017 03:17

Revisions

  1. rjurney created this gist Jun 18, 2017.
    1 change: 1 addition & 0 deletions .block
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    license: mit
    1 change: 1 addition & 0 deletions README.md
    Original file line number Diff line number Diff line change
    @@ -0,0 +1 @@
    Built with [blockbuilder.org](http://blockbuilder.org)
    217 changes: 217 additions & 0 deletions index.html
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,217 @@
    <!DOCTYPE html>
    <head>
    <meta charset="utf-8">
    <script src="https://d3js.org/d3.v4.min.js"></script>
    <style>
    body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
    </style>
    </head>

    <body>
    <script>

    var width = 900;
    var height = 400;
    var barWidth = 30;

    var margin = {top: 20, right: 10, bottom: 20, left: 10};

    var width = width - margin.left - margin.right,
    height = height - margin.top - margin.bottom;

    var totalWidth = width + margin.left + margin.right;
    var totalheight = height + margin.top + margin.bottom;

    // Generate five 100 count, normal distributions with random means
    var groupCounts = {};
    var globalCounts = [];
    var meanGenerator = d3.randomUniform(10);
    for(i=0; i<7; i++) {
    var randomMean = meanGenerator();
    var generator = d3.randomNormal(randomMean);
    var key = i.toString();
    groupCounts[key] = [];

    for(j=0; j<100; j++) {
    var entry = generator();
    groupCounts[key].push(entry);
    globalCounts.push(entry);
    }
    }

    // Sort group counts so quantile methods work
    for(var key in groupCounts) {
    var groupCount = groupCounts[key];
    groupCounts[key] = groupCount.sort(sortNumber);
    }

    // Setup a color scale for filling each box
    var colorScale = d3.scaleOrdinal(d3.schemeCategory20)
    .domain(Object.keys(groupCounts));

    // Prepare the data for the box plots
    var boxPlotData = [];
    for (var [key, groupCount] of Object.entries(groupCounts)) {

    var record = {};
    var localMin = d3.min(groupCount);
    var localMax = d3.max(groupCount);

    record["key"] = key;
    record["counts"] = groupCount;
    record["quartile"] = boxQuartiles(groupCount);
    record["whiskers"] = [localMin, localMax];
    record["color"] = colorScale(key);

    boxPlotData.push(record);
    }

    // Compute an ordinal xScale for the keys in boxPlotData
    var xScale = d3.scalePoint()
    .domain(Object.keys(groupCounts))
    .rangeRound([0, width])
    .padding([0.5]);

    // Compute a global y scale based on the global counts
    var min = d3.min(globalCounts);
    var max = d3.max(globalCounts);
    var yScale = d3.scaleLinear()
    .domain([min, max])
    .range([0, height]);

    // Setup the svg and group we will draw the box plot in
    var svg = d3.select("body").append("svg")
    .attr("width", totalWidth)
    .attr("height", totalheight)
    .append("g")
    .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    // Move the left axis over 25 pixels, and the top axis over 35 pixels
    var axisG = svg.append("g").attr("transform", "translate(25,0)");
    var axisTopG = svg.append("g").attr("transform", "translate(35,0)");

    // Setup the group the box plot elements will render in
    var g = svg.append("g")
    .attr("transform", "translate(20,5)");

    // Draw the box plot vertical lines
    var verticalLines = g.selectAll(".verticalLines")
    .data(boxPlotData)
    .enter()
    .append("line")
    .attr("x1", function(datum) {
    return xScale(datum.key) + barWidth/2;
    }
    )
    .attr("y1", function(datum) {
    var whisker = datum.whiskers[0];
    return yScale(whisker);
    }
    )
    .attr("x2", function(datum) {
    return xScale(datum.key) + barWidth/2;
    }
    )
    .attr("y2", function(datum) {
    var whisker = datum.whiskers[1];
    return yScale(whisker);
    }
    )
    .attr("stroke", "#000")
    .attr("stroke-width", 1)
    .attr("fill", "none");

    // Draw the boxes of the box plot, filled in white and on top of vertical lines
    var rects = g.selectAll("rect")
    .data(boxPlotData)
    .enter()
    .append("rect")
    .attr("width", barWidth)
    .attr("height", function(datum) {
    var quartiles = datum.quartile;
    var height = yScale(quartiles[2]) - yScale(quartiles[0]);
    return height;
    }
    )
    .attr("x", function(datum) {
    return xScale(datum.key);
    }
    )
    .attr("y", function(datum) {
    return yScale(datum.quartile[0]);
    }
    )
    .attr("fill", function(datum) {
    return datum.color;
    }
    )
    .attr("stroke", "#000")
    .attr("stroke-width", 1);

    // Now render all the horizontal lines at once - the whiskers and the median
    var horizontalLineConfigs = [
    // Top whisker
    {
    x1: function(datum) { return xScale(datum.key) },
    y1: function(datum) { return yScale(datum.whiskers[0]) },
    x2: function(datum) { return xScale(datum.key) + barWidth },
    y2: function(datum) { return yScale(datum.whiskers[0]) }
    },
    // Median line
    {
    x1: function(datum) { return xScale(datum.key) },
    y1: function(datum) { return yScale(datum.quartile[1]) },
    x2: function(datum) { return xScale(datum.key) + barWidth },
    y2: function(datum) { return yScale(datum.quartile[1]) }
    },
    // Bottom whisker
    {
    x1: function(datum) { return xScale(datum.key) },
    y1: function(datum) { return yScale(datum.whiskers[1]) },
    x2: function(datum) { return xScale(datum.key) + barWidth },
    y2: function(datum) { return yScale(datum.whiskers[1]) }
    }
    ];

    for(var i=0; i < horizontalLineConfigs.length; i++) {
    var lineConfig = horizontalLineConfigs[i];

    // Draw the whiskers at the min for this series
    var horizontalLine = g.selectAll(".whiskers")
    .data(boxPlotData)
    .enter()
    .append("line")
    .attr("x1", lineConfig.x1)
    .attr("y1", lineConfig.y1)
    .attr("x2", lineConfig.x2)
    .attr("y2", lineConfig.y2)
    .attr("stroke", "#000")
    .attr("stroke-width", 1)
    .attr("fill", "none");
    }

    // Setup a scale on the left
    var axisLeft = d3.axisLeft(yScale);
    axisG.append("g")
    .call(axisLeft);

    // Setup a series axis on the top
    var axisTop = d3.axisTop(xScale);
    axisTopG.append("g")
    .call(axisTop);

    function boxQuartiles(d) {
    return [
    d3.quantile(d, .25),
    d3.quantile(d, .5),
    d3.quantile(d, .75)
    ];
    }

    // Perform a numeric sort on an array
    function sortNumber(a,b) {
    return a - b;
    }

    </script>
    </body>