Built with blockbuilder.org
forked from rjurney's block: Simple Box Plot Example in d3.js 4.0
license: apache-2.0 |
Built with blockbuilder.org
forked from rjurney's block: Simple Box Plot Example in d3.js 4.0
<!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> |