Skip to content

Instantly share code, notes, and snippets.

@mattbrehmer
Last active January 18, 2018 01:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mattbrehmer/8be29724bdd7a63ff41d to your computer and use it in GitHub Desktop.
Save mattbrehmer/8be29724bdd7a63ff41d to your computer and use it in GitHub Desktop.
Box Plot w/ Brushing

A boxplot containing monthly values x 10 years, with brushing by year to highlight individual values (points) from that year, as well as brushing by value (point) to highlight both corresponding year and other values (points) from that year.

//initialize the dimensions
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 400 - margin.left - margin.right,
height = 100 - margin.top - margin.bottom,
padding = 20
midline = (height - padding) / 2;
//initialize the x scale
var xScale = d3.scale.linear()
.range([padding, width - padding]);
//initialize the x axis
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom");
var parseDate = d3.time.format("%b %Y").parse;
//initialize the time scale
var timeScale = d3.scale.linear()
.range([padding / 2, width - padding / 2]);
//initialize boxplot statistics
var data = [],
outliers = [],
minVal = Infinity,
lowerWhisker = Infinity,
q1Val = Infinity,
medianVal = 0,
q3Val = -Infinity,
iqr = 0,
upperWhisker = -Infinity,
maxVal = -Infinity;
// initialize time stats
var yearArr = [],
dates = []
selectedYear = 0;
d3.csv("data.csv", type, function(error,csv) { // load the data
data = csv.map(function(d) {
return d.value;
});
data = data.sort(d3.ascending);
//calculate the boxplot statistics
minVal = data[0],
q1Val = d3.quantile(data, .25),
medianVal = d3.quantile(data, .5),
q3Val = d3.quantile(data, .75),
iqr = q3Val - q1Val,
maxVal = data[data.length - 1];
var index = 0;
//search for the lower whisker, the mininmum value within q1Val - 1.5*iqr
while (index < data.length && lowerWhisker == Infinity) {
if (data[index] >= (q1Val - 1.5*iqr))
lowerWhisker = data[index];
else
outliers.push(data[index]);
index++;
}
index = data.length-1; // reset index to end of array
//search for the upper whisker, the maximum value within q1Val + 1.5*iqr
while (index >= 0 && upperWhisker == -Infinity) {
if (data[index] <= (q3Val + 1.5*iqr))
upperWhisker = data[index];
else
outliers.push(data[index]);
index--;
}
dates = csv.map(function(d) {
return d.date;
});
for (i = 0; i < dates.length; i++) {
// get year
var year = parseDate(dates[i]).getFullYear();
if (!(yearArr.indexOf(year) > -1))
yearArr.push(year);
}
yearArr = yearArr.sort(d3.ascending);
//map the domain to the x scale +10%
xScale.domain([0,maxVal*1.10]);
//map the time scale
timeScale.domain([yearArr[0],yearArr[yearArr.length-1]+1]);
var datesvg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//time intervals as rect+label texts
intervals = datesvg.append("g")
.selectAll("g")
.data(yearArr)
.enter();
//add linked highlighting to points on interval mouseover
intervalCell = intervals.append("g")
.attr("class", "intervalCell")
.on("mouseover", function(d) {
d3.selectAll(".year"+d)
.attr("class", "selected");
})
.on("mouseout", function(d) {
d3.selectAll(".selected")
.attr("class", "year"+d);
});
intervalCell.append("rect")
.attr("x", function(d){
return timeScale(d)
})
.attr("id", function(d){
return "interval" + d;
})
.attr("y", 0)
.attr("width", (width - padding) / yearArr.length - 2)
.attr("height", height - padding);
// text labels for interval cells
intervalCell.append("text")
.attr("x", function(d){
return timeScale(d) + 5
})
.attr("width", (width - padding) / yearArr.length - 8)
.attr("height", 10)
.attr("y", midline + 2.5)
.attr("font-family", "sans-serif")
.attr("font-size", "10px")
.attr("class", "intervalLabel")
.text(function(d) { return d});
var svg = d3.select("body")
.append("svg")
.attr("width", width)
.attr("height", height);
//append the axis
svg.append("g")
.attr("class", "axis")
.attr("transform", "translate(0, " + (height - padding) + ")")
.call(xAxis);
//draw verical line for lowerWhisker
svg.append("line")
.attr("x1", xScale(lowerWhisker))
.attr("x2", xScale(lowerWhisker))
.attr("y1", midline - 15)
.attr("y2", midline + 15);
//draw vertical line for upperWhisker
svg.append("line")
.attr("x1", xScale(upperWhisker))
.attr("x2", xScale(upperWhisker))
.attr("y1", midline - 15)
.attr("y2", midline + 15);
//draw horizontal line from lowerWhisker to upperWhisker
svg.append("line")
.attr("x1", xScale(lowerWhisker))
.attr("x2", xScale(upperWhisker))
.attr("y1", midline)
.attr("y2", midline);
//draw rect for iqr
svg.append("rect")
.attr("x", xScale(q1Val))
.attr("y", padding-5)
.attr("width", xScale(iqr) - padding)
.attr("height", 30);
//draw vertical line at median
svg.append("line")
.attr("x1", xScale(medianVal))
.attr("x2", xScale(medianVal))
.attr("y1", midline - 15)
.attr("y2", midline + 15);
//draw data as points
svg.append("g")
.selectAll("circle")
.data(csv)
.enter()
.append("circle")
//double point size for outliers
.attr("r", function(d) {
if (d.value < lowerWhisker || d.value > upperWhisker)
return 4;
else
return 2;
})
.attr("class", function(d) {
return "year" + parseDate(d.date).getFullYear();
})
.attr("cy", function(d) { // jitter the points to reduce overplotting
return random_jitter();
})
.attr("cx", function(d) {
return xScale(d.value);
})
//add linked highlighting to interval on point mouseover
.on("mouseover", function(d) {
d3.selectAll(".year"+parseDate(d.date).getFullYear())
.attr("class", "selected");
d3.select("#interval"+parseDate(d.date).getFullYear())
.attr("class", "selectedInterval");
})
.on("mouseout", function(d) {
d3.selectAll(".selected")
.attr("class", "year"+parseDate(d.date).getFullYear());
d3.selectAll(".selectedInterval")
.attr("class", "none");
})
//add tooltip on hover
.append("title")
.text(function(d) {
return "Date: " + d.date + "; value: " + d.value;
});
});
//add random jitter to points around midline
function random_jitter() {
if (Math.round(Math.random() * 1) == 0)
var seed = -5;
else
var seed = 5;
return midline + Math.floor((Math.random() * seed) + 1);
}
function type(d) {
d.value = +d.value; // coerce to number
d.date = d.date;
return d;
}
date value
Jan 2000 64.56
Feb 2000 68.87
Mar 2000 67
Apr 2000 55.19
May 2000 48.31
Jun 2000 36.31
Jul 2000 30.12
Aug 2000 41.5
Sep 2000 38.44
Oct 2000 36.62
Nov 2000 24.69
Dec 2000 15.56
Jan 2001 17.31
Feb 2001 10.19
Mar 2001 10.23
Apr 2001 15.78
May 2001 16.69
Jun 2001 14.15
Jul 2001 12.49
Aug 2001 8.94
Sep 2001 5.97
Oct 2001 6.98
Nov 2001 11.32
Dec 2001 10.82
Jan 2002 14.19
Feb 2002 14.1
Mar 2002 14.3
Apr 2002 16.69
May 2002 18.23
Jun 2002 16.25
Jul 2002 14.45
Aug 2002 14.94
Sep 2002 15.93
Oct 2002 19.36
Nov 2002 23.35
Dec 2002 18.89
Jan 2003 21.85
Feb 2003 22.01
Mar 2003 26.03
Apr 2003 28.69
May 2003 35.89
Jun 2003 36.32
Jul 2003 41.64
Aug 2003 46.32
Sep 2003 48.43
Oct 2003 54.43
Nov 2003 53.97
Dec 2003 52.62
Jan 2004 50.4
Feb 2004 43.01
Mar 2004 43.28
Apr 2004 43.6
May 2004 48.5
Jun 2004 54.4
Jul 2004 38.92
Aug 2004 38.14
Sep 2004 40.86
Oct 2004 34.13
Nov 2004 39.68
Dec 2004 44.29
Jan 2005 43.22
Feb 2005 35.18
Mar 2005 34.27
Apr 2005 32.36
May 2005 35.51
Jun 2005 33.09
Jul 2005 45.15
Aug 2005 42.7
Sep 2005 45.3
Oct 2005 39.86
Nov 2005 48.46
Dec 2005 47.15
Jan 2006 44.82
Feb 2006 37.44
Mar 2006 36.53
Apr 2006 35.21
May 2006 34.61
Jun 2006 38.68
Jul 2006 26.89
Aug 2006 30.83
Sep 2006 32.12
Oct 2006 38.09
Nov 2006 40.34
Dec 2006 39.46
Jan 2007 37.67
Feb 2007 39.14
Mar 2007 39.79
Apr 2007 61.33
May 2007 69.14
Jun 2007 68.41
Jul 2007 78.54
Aug 2007 79.91
Sep 2007 93.15
Oct 2007 89.15
Nov 2007 90.56
Dec 2007 92.64
Jan 2008 77.7
Feb 2008 64.47
Mar 2008 71.3
Apr 2008 78.63
May 2008 81.62
Jun 2008 73.33
Jul 2008 76.34
Aug 2008 80.81
Sep 2008 72.76
Oct 2008 57.24
Nov 2008 42.7
Dec 2008 51.28
Jan 2009 58.82
Feb 2009 64.79
Mar 2009 73.44
Apr 2009 80.52
May 2009 77.99
Jun 2009 83.66
Jul 2009 85.76
Aug 2009 81.19
Sep 2009 93.36
Oct 2009 118.81
Nov 2009 135.91
Dec 2009 134.52
Jan 2010 125.41
Feb 2010 118.4
Mar 2010 128.82
<!DOCTYPE html>
<link rel = "stylesheet" type="text/css" href="style.css" />
<style type="text/css"></style>
<html>
<body>
<script type="text/javascript" src="http://d3js.org/d3.v3.min.js"></script>
<script type="text/javascript" src="brushboxplot.js"></script>
</body>
</html>
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
line {
fill: #fff;
stroke: #000;
stroke-width: 1px;
shape-rendering: crispEdges;
}
rect {
fill: #fff;
stroke: #000;
stroke-width: 1px;
shape-rendering: crispEdges;
}
circle {
opacity: 0.2;
stroke: none;
shape-rendering: crispEdges;
}
circle:hover {
stroke: red;
fill: red;
opacity: 1;
stroke-width: 2px;
}
.selected {
stroke: red;
fill: red;
opacity: 1;
stroke-width: 2px;
}
.axis path {
fill: none;
stroke: black;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.axis line {
fill: none;
stroke: black;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.axis text {
font-size: 10px;
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
.selectedInterval {
stroke: red;
fill: #E8E8E8;
}
.intervalCell:hover rect {
stroke: red;
fill: #EEEEEE;
}
.intervalCell:hover text {
fill: red;
}
.intervalLabel {
-moz-user-select: -moz-none;
-khtml-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
user-select: none;
cursor: default;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment