|
(function() { |
|
|
|
d3.box = function() { |
|
|
|
var width = 1, |
|
height = 1, |
|
domain = null, |
|
padding = 10, |
|
midline = 1 |
|
value = Number; |
|
|
|
function box(g) { |
|
g.each(function(d, i){ |
|
|
|
//initialize boxplot statistics |
|
var data = d.map(function(d) { |
|
return d.close; |
|
}); |
|
|
|
var outliers = [], |
|
minVal = Infinity, |
|
lowerWhisker = Infinity, |
|
q1Val = Infinity, |
|
medianVal = 0, |
|
q3Val = -Infinity, |
|
iqr = 0, |
|
upperWhisker = -Infinity, |
|
maxVal = -Infinity; |
|
|
|
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]; |
|
// lowerWhisker = d3.max([minVal, q1Val - iqr]) |
|
// upperWhisker = d3.min([maxVal, q3Val + iqr]); |
|
|
|
//initialize the x scale |
|
var xScale = d3.scale.linear() |
|
.range([padding, width - padding]) |
|
.domain(domain && domain.call(this, d, i) || [min, max]); |
|
|
|
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--; |
|
} |
|
|
|
midline = height / 2; |
|
|
|
var g = d3.select(this); |
|
|
|
//draw verical line for lowerWhisker |
|
g.append("line") |
|
.attr("class", "whisker") |
|
.attr("x1", xScale(lowerWhisker)) |
|
.attr("x2", xScale(lowerWhisker)) |
|
.attr("stroke", "black") |
|
.attr("y1", padding) |
|
.attr("y2", height - padding); |
|
|
|
//draw vertical line for upperWhisker |
|
g.append("line") |
|
.attr("class", "whisker") |
|
.attr("x1", xScale(upperWhisker)) |
|
.attr("x2", xScale(upperWhisker)) |
|
.attr("stroke", "black") |
|
.attr("y1", padding) |
|
.attr("y2", height - padding); |
|
|
|
//draw horizontal line from lowerWhisker to upperWhisker |
|
g.append("line") |
|
.attr("class", "whisker") |
|
.attr("x1", xScale(lowerWhisker)) |
|
.attr("x2", xScale(upperWhisker)) |
|
.attr("stroke", "black") |
|
.attr("y1", midline) |
|
.attr("y2", midline); |
|
|
|
//draw rect for iqr |
|
g.append("rect") |
|
.attr("class", "box") |
|
.attr("stroke", "black") |
|
.attr("fill", "white") |
|
.attr("x", xScale(q1Val)) |
|
.attr("y", padding) |
|
.attr("width", xScale(q3Val) - xScale(q1Val) - padding) |
|
.attr("height", height - 2 * padding) |
|
.append("title") |
|
.text(function() { |
|
return "count: " + addCommas(data.length) + "\n" + |
|
"minVal: " + addCommas(minVal) + "\n" + |
|
"lowerWhisker: " + addCommas(lowerWhisker) + "\n" + |
|
"q1Val: " + addCommas(q1Val) + "\n" + |
|
"medianVal: " + addCommas(medianVal) + "\n" + |
|
"q3Val: " + addCommas(q3Val) + "\n" + |
|
"upperWhisker: " + addCommas(upperWhisker) + "\n" + |
|
"maxVal: " + addCommas(maxVal) + "\n" + |
|
"# outliers: " + addCommas(outliers.length); |
|
}); |
|
|
|
//draw vertical line at median |
|
g.append("line") |
|
.attr("class", "median") |
|
.attr("stroke", "black") |
|
.attr("x1", xScale(medianVal)) |
|
.attr("x2", xScale(medianVal)) |
|
.attr("y1", padding) |
|
.attr("y2", height - padding); |
|
|
|
//draw data as points |
|
g.append("g") |
|
.attr("class", "outliers") |
|
.selectAll("circle") |
|
.data(d) |
|
.enter() |
|
.append("circle") |
|
.filter(function(d) { |
|
return d.close > upperWhisker || d.close < lowerWhisker; |
|
}) |
|
.attr("r", 2.5) |
|
.attr("class", "point") |
|
.attr("cy", function(d) { |
|
return random_jitter(); |
|
}) |
|
.attr("cx", function(d) { |
|
return xScale(d.close); |
|
}) |
|
.append("title") |
|
.text(function(d) { |
|
return "Date: " + d.date + "\nValue: " + addCommas(d.close); |
|
}); |
|
|
|
}); |
|
|
|
} |
|
|
|
function addCommas(nStr) { |
|
nStr += ''; |
|
x = nStr.split('.'); |
|
x1 = x[0]; |
|
x2 = x.length > 1 ? '.' + x[1] : ''; |
|
var rgx = /(\d+)(\d{3})/; |
|
while (rgx.test(x1)) { |
|
x1 = x1.replace(rgx, '$1' + ',' + '$2'); |
|
} |
|
return x1 + x2; |
|
} |
|
|
|
function random_jitter() { |
|
if (Math.round(Math.random() * 1) == 0) |
|
var seed = - (height / 3 - padding); |
|
else |
|
var seed = height / 3 - padding; |
|
return midline + Math.floor((Math.random() * seed) + 1); |
|
} |
|
|
|
box.width = function(x) { |
|
if (!arguments.length) return width; |
|
width = x; |
|
return box; |
|
}; |
|
|
|
box.height = function(x) { |
|
if (!arguments.length) return height; |
|
height = x; |
|
return box; |
|
}; |
|
|
|
box.value = function(x) { |
|
if (!arguments.length) return value; |
|
value = x; |
|
return box; |
|
}; |
|
|
|
box.domain = function(x) { |
|
if (!arguments.length) return domain; |
|
domain = x == null ? x : d3.functor(x); |
|
return box; |
|
}; |
|
|
|
return box; |
|
} |
|
|
|
})(); |