function makeDistroChart(dataset, xGroup, yValue) {
/*
* dataset = the csv file
* xGroup = the name of the column to group by
* yValue = the column to use as the values for the chart
*
* */
var chart = {};
var colorFunct = d3.scale.category10(); //function () {return 'lightgrey';};
function formatAsFloat(d) {
if (d % 1 !== 0) {
return d3.format(".2f")(d);
} else {
return d3.format(".0f")(d);
}
}
function logFormatNumber(d) {
var x = Math.log(d) / Math.log(10) + 1e-6;
return Math.abs(x - Math.floor(x)) < 0.6 ? formatAsFloat(d) : "";
}
chart.yFormatter = formatAsFloat;
chart.data = dataset;
//Data management
chart.xGroup = xGroup;
chart.yValue = yValue;
chart.groupObjs = {}; //The data organized by grouping and sorted as well as any metadata for the groups
chart.objs = {mainDiv: null, chartDiv: null, g: null, xAxis: null, yAxis: null};
function updateColorFunction(colorOptions) {
/*
* Takes either a list of colors, a function or an object with the mapping already in place
* */
if (typeof colorOptions == 'function') {
return colorOptions
} else if (Array.isArray(colorOptions)) {
// If an array is provided, map it to the domain
var colorMap = {}, cColor = 0;
for (var cName in chart.groupObjs) {
colorMap[cName] = colorOptions[cColor];
cColor = (cColor + 1) % colorOptions.length;
}
return function (group) {
return colorMap[group];
}
} else if (typeof colorOptions == 'object') {
// if an object is provided, assume it maps to the colors
return function (group) {
return colorOptions[group];
}
}
}
function updateGroupWidth(boxWidth) {
// Takes the boxWidth size (as percentage of possible width) and returns the actual pixel width to use
var boxSize = {left: null, right: null, middle: null};
var width = chart.xScale.rangeBand() * (boxWidth / 100);
var padding = (chart.xScale.rangeBand() - width) / 2;
boxSize.middle = chart.xScale.rangeBand() / 2;
boxSize.left = padding;
boxSize.right = boxSize.left + width;
return boxSize;
}
function tooltipHover(name, metrics) {
var tooltipString = "Group: " + name;
tooltipString += "
Max: " + formatAsFloat(metrics.max, 0.1);
tooltipString += "
Q3: " + formatAsFloat(metrics.quartile3);
tooltipString += "
Median: " + formatAsFloat(metrics.median);
tooltipString += "
Q1: " + formatAsFloat(metrics.quartile1);
tooltipString += "
Min: " + formatAsFloat(metrics.min);
return function () {
chart.objs.tooltip.transition().duration(200).style("opacity", 0.9);
chart.objs.tooltip.html(tooltipString)
};
}
function prepareData() {
/*
* Takes the dataset that is an array of objects and groups the yValues by xGroups and then sorts it
* Returns the groupObj
* */
function calcMetrics(values) {
var metrics = { //These are the original non–scaled values
max: null,
upperOuterFence: null,
upperInnerFence: null,
quartile3: null,
median: null,
mean: null,
iqr: null,
quartile1: null,
lowerInnerFence: null,
lowerOuterFence: null,
min: null
};
metrics.min = d3.min(values);
metrics.quartile1 = d3.quantile(values, 0.25);
metrics.median = d3.median(values);
metrics.mean = d3.mean(values);
metrics.quartile3 = d3.quantile(values, 0.75);
metrics.max = d3.max(values);
metrics.iqr = metrics.quartile3 - metrics.quartile1;
//The inner fences are the closest value to the IQR without going past it (assumes sorted lists)
var LIF = metrics.quartile1 - (1.5 * metrics.iqr);
var UIF = metrics.quartile3 + (1.5 * metrics.iqr);
for (var i = 0; i <= values.length; i++) {
if (values[i] < LIF) {
continue;
}
if (!metrics.lowerInnerFence && values[i] >= LIF) {
metrics.lowerInnerFence = values[i];
continue;
}
if (values[i] > UIF) {
metrics.upperInnerFence = values[i - 1];
break;
}
}
metrics.lowerOuterFence = metrics.quartile1 - (3 * metrics.iqr);
metrics.upperOuterFence = metrics.quartile3 + (3 * metrics.iqr);
if (!metrics.lowerInnerFence) {
metrics.lowerInnerFence = metrics.min;
}
if (!metrics.upperInnerFence) {
metrics.upperInnerFence = metrics.max;
}
return metrics
}
var current_x = null;
var current_y = null;
var current_row;
//Group the values
for (current_row = 0; current_row < chart.data.length; current_row++) {
current_x = chart.data[current_row][chart.xGroup];
current_y = chart.data[current_row][chart.yValue];
if (chart.groupObjs.hasOwnProperty(current_x)) {
chart.groupObjs[current_x].values.push(current_y);
} else {
chart.groupObjs[current_x] = {};
chart.groupObjs[current_x].values = [current_y];
}
}
var cName;
// Sort them
for (cName in chart.groupObjs) {
chart.groupObjs[cName].values.sort(d3.ascending);
chart.groupObjs[cName].metrics = {};
chart.groupObjs[cName].metrics = calcMetrics(chart.groupObjs[cName].values);
}
}
prepareData();
chart.update = function () {
if (!chart.objs.g) {
return false;
}
// Update chart size
chart.width = parseInt(chart.objs.chartDiv.style("width"), 10) - (chart.margin.left + chart.margin.right);
chart.height = parseInt(chart.objs.chartDiv.style("height"), 10) - (chart.margin.top + chart.margin.bottom);
chart.xScale.rangeBands([0, chart.width]);
chart.yScale.range([chart.height, 0]);
//Update axes
chart.objs.g.select('.x.axis').attr("transform", "translate(0," + chart.height + ")").call(chart.objs.xAxis)
.selectAll("text")
.attr("y", 5)
.attr("x", -5)
.attr("transform", "rotate(-45)")
.style("text-anchor", "end");
chart.objs.g.select('.x.axis .label').attr("x", chart.width / 2);
chart.objs.g.select('.y.axis').call(chart.objs.yAxis.innerTickSize(-chart.width));
chart.objs.g.select('.y.axis .label').attr("x", -chart.height / 2);
chart.objs.chartDiv.select('svg').attr("width", chart.width + (chart.margin.left + chart.margin.right)).attr("height", chart.height + (chart.margin.top + chart.margin.bottom));
return chart;
};
chart.bind = function (selector, chartOptions) {
/*
* Setup chart and connect it to the correct div
*
* Selector is the id to attach the chart to
* chartOptions = list of chart options
* scale = linear (vs log)
* chartSize
* – chart_width = 800
* – chart_height = 400
* margin = {top: 15, right: 60, bottom: 30, left: 50};
* constrainExtremes True/False, if true max is then the max of the lower fences
* axisLabels = Labels for the chart
*
*/
//Get base data
function getBaseData() {
if (chartOptions && chartOptions.margin) {
chart.margin = margin;
} else {
chart.margin = {top: 15, right: 40, bottom: 35, left: 50};
}
if (chartOptions && chartOptions.chartSize) {
chart.divWidth = chartOptions.chartSize.width;
chart.divHeight = chartOptions.chartSize.height;
} else {
chart.divWidth = 800;
chart.divHeight = 400;
}
chart.width = chart.divWidth - chart.margin.left - chart.margin.right;
chart.height = chart.divHeight - chart.margin.top - chart.margin.bottom;
if (chartOptions && chartOptions.axisLabels) {
chart.xAxisLable = chartOptions.axisLabels.xAxis;
chart.yAxisLable = chartOptions.axisLabels.yAxis;
} else {
chart.xAxisLable = xGroup;
chart.yAxisLable = yValue;
}
if (chartOptions && chartOptions.scale === 'log') {
chart.yScale = d3.scale.log();
chart.yFormatter = logFormatNumber;
} else {
chart.yScale = d3.scale.linear();
}
if (chartOptions && chartOptions.constrainExtremes === true) {
var fences = [];
for (var cName in chart.groupObjs) {
fences.push(chart.groupObjs[cName].metrics.lowerInnerFence);
fences.push(chart.groupObjs[cName].metrics.upperInnerFence);
}
chart.range = d3.extent(fences);
} else {
chart.range = d3.extent(chart.data, function (d) {
return d[chart.yValue];
});
}
// Take the options colors argument and update the colors function
if (chartOptions && chartOptions.colors) {
colorFunct = updateColorFunction(chartOptions.colors);
}
chart.yScale.range([chart.height, 0]).domain(chart.range).clamp(true);
// Get x range
chart.xScale = d3.scale.ordinal().domain(Object.keys(chart.groupObjs)).rangeBands([0, chart.width]);
//Build Axes
chart.objs.yAxis = d3.svg.axis()
.scale(chart.yScale)
.orient("left")
.tickFormat(chart.yFormatter)
.outerTickSize(0)
.innerTickSize(-chart.width + (chart.margin.right + chart.margin.left));
chart.objs.xAxis = d3.svg.axis().scale(chart.xScale).orient("bottom").tickSize(5);
}
getBaseData();
chart.objs.mainDiv = d3.select(selector)
.style("max-width", chart.divWidth + "px");
// Add all the divs to make it centered and responsive
chart.objs.mainDiv.append("div")
.attr("class", "inner-wrapper")
.style("padding-bottom", (chart.divHeight / chart.divWidth) * 100 + "%")
.append("div").attr("class", "outer-box")
.append("div").attr("class", "inner-box");
// Capture the inner div for the chart (where the chart actually is)
chart.chartSelector = selector + " .inner-box";
chart.objs.chartDiv = d3.select(chart.chartSelector);
d3.select(window).on('resize.' + chart.chartSelector, chart.update);
// Create the svg
chart.objs.g = chart.objs.chartDiv.append("svg")
.attr("class", "chart-area")
.attr("width", chart.width + (chart.margin.left + chart.margin.right))
.attr("height", chart.height + (chart.margin.top + chart.margin.bottom))
.append("g")
.attr("transform", "translate(" + chart.margin.left + "," + chart.margin.top + ")");
chart.objs.axes = chart.objs.g.append("g").attr("class", "axis");
// Show axis
chart.objs.axes.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + chart.height + ")")
.call(chart.objs.xAxis);
chart.objs.axes.append("g")
.attr("class", "y axis")
.call(chart.objs.yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", -42)
.attr("x", -chart.height / 2)
.attr("dy", ".71em")
.style("text-anchor", "middle")
.text(chart.yAxisLable);
//Add the tooltip div
chart.objs.tooltip = chart.objs.mainDiv.append('div').attr('class', 'tooltip');
// Add hover tooltip
for (var cName in chart.groupObjs) {
//Add mouseover
chart.groupObjs[cName].g = chart.objs.g.append("g").attr("class", "group");
chart.groupObjs[cName].g.on("mouseover", function () {
chart.objs.tooltip.style("display", null).style("left", (d3.event.pageX) + "px").style("top", (d3.event.pageY - 28) + "px");
}).on("mouseout", function () {
chart.objs.tooltip.style("display", "none");
}).on("mousemove", tooltipHover(cName, chart.groupObjs[cName].metrics))
}
chart.update();
return chart;
};
chart.renderViolinPlot = function (chartOptions) {
/*
* Options
* - showArea True/False (default True)
* - showLine True/False (default True)
* - resolution, number of bins
* - boxWidth (wider or not)
*/
chart.violinPlots = {};
chart.violinPlots.plots = {};
chart.violinPlots.violinOptions = chartOptions;
var vOpts = chart.violinPlots.violinOptions;
// Violin Calculations
chart.violinPlots.calculateNumBins = function (cGroup) {
var iqr;
if (chart.boxPlots) {
iqr = chart.groupObjs[cGroup].metrics.iqr
} else {
var quartile1 = d3.quantile(chart.groupObjs[cGroup].values, 0.25);
var quartile3 = d3.quantile(chart.groupObjs[cGroup].values, 0.75);
iqr = quartile3 - quartile1;
}
return Math.max(Math.round(2 * (iqr / Math.pow(chart.groupObjs[cGroup].values.length, 1 / 3))), 50)
};
function prepareViolin() {
/*
* Takes the structured data and calculates the box plot numbers
* */
var cName;
for (cName in chart.groupObjs) {
chart.groupObjs[cName].violin = {};
chart.groupObjs[cName].violin.objs = {};
chart.groupObjs[cName].violin.histogramFunct = d3.layout.histogram().frequency(1);
}
}
prepareViolin();
chart.violinPlots.change = function (updateOptions) {
/*
* Same options as on renderViolin
*/
if (updateOptions) {
for (var key in updateOptions) {
vOpts[key] = updateOptions[key]
}
}
mapObjects(true);
chart.violinPlots.update()
};
chart.violinPlots.update = function () {
var cName, cViolinPlot;
for (cName in chart.groupObjs) {
cViolinPlot = chart.groupObjs[cName].violin;
if (vOpts && vOpts.resolution) {
cViolinPlot.histogramFunct.bins(vOpts.resolution);
} else {
cViolinPlot.histogramFunct.bins(chart.violinPlots.calculateNumBins(cName));
}
cViolinPlot.histogramData = cViolinPlot.histogramFunct(chart.groupObjs[cName].values);
// Get the box size
var groupWidth = {left: null, right: null, middle: null};
if (vOpts && vOpts.violinWidth) {
groupWidth = updateGroupWidth(vOpts.violinWidth)
} else {
groupWidth = updateGroupWidth(100)
}
var leftBound = chart.xScale(cName) + groupWidth.left;
var rightBound = chart.xScale(cName) + groupWidth.right;
var width = (rightBound - leftBound) / 2;
var xV = chart.yScale.copy();
var yV = d3.scale.linear()
.range([width, 0])
.domain([0, Math.max(chart.range[1], d3.max(cViolinPlot.histogramData, function (d) {
return d.y;
}))])
.clamp(true);
var area = d3.svg.area()
.interpolate('basis')
.x(function (d) {
return xV(d.x);
})
.y0(width)
.y1(function (d) {
return yV(d.y);
});
var line = d3.svg.line()
.interpolate('basis')
.x(function (d) {
return xV(d.x);
})
.y(function (d) {
return yV(d.y);
});
if (cViolinPlot.objs.left.area) {
cViolinPlot.objs.left.area
.datum(cViolinPlot.histogramData)
.attr("d", area);
}
if (cViolinPlot.objs.left.line) {
cViolinPlot.objs.left.line
.datum(cViolinPlot.histogramData)
.attr("d", line);
}
if (cViolinPlot.objs.right.area) {
cViolinPlot.objs.right.area
.datum(cViolinPlot.histogramData)
.attr("d", area);
}
if (cViolinPlot.objs.right.line) {
cViolinPlot.objs.right.line
.datum(cViolinPlot.histogramData)
.attr("d", line);
}
cViolinPlot.objs.left.g.attr("transform", "rotate(90,0,0) translate(0,-" + leftBound + ") scale(1,-1)");
cViolinPlot.objs.right.g.attr("transform", "rotate(90,0,0) translate(0,-" + rightBound + ")");
}
};
function mapObjects(clear) {
var cName, cViolinPlot;
if (vOpts && vOpts.colors) {
chart.violinPlots.color = updateColorFunction(vOpts.colors);
} else {
chart.violinPlots.color = colorFunct
}
for (cName in chart.groupObjs) {
cViolinPlot = chart.groupObjs[cName].violin;
if (clear) {
cViolinPlot.objs.g.remove()
}
cViolinPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "violin-plot");
cViolinPlot.objs.left = {area: null, line: null, g: null};
cViolinPlot.objs.right = {area: null, line: null, g: null};
cViolinPlot.objs.left.g = cViolinPlot.objs.g.append("g");
cViolinPlot.objs.right.g = cViolinPlot.objs.g.append("g");
if (!vOpts || (vOpts && vOpts.showArea !== false)) {
cViolinPlot.objs.left.area = cViolinPlot.objs.left.g.append("path")
.attr("class", "area")
.style("fill", chart.violinPlots.color(cName));
cViolinPlot.objs.right.area = cViolinPlot.objs.right.g.append("path")
.attr("class", "area")
.style("fill", chart.violinPlots.color(cName));
}
if (!vOpts || (vOpts && vOpts.showLine !== false)) {
cViolinPlot.objs.left.line = cViolinPlot.objs.left.g.append("path")
.attr("class", "line")
.attr("fill", 'none')
.style("stroke", chart.violinPlots.color(cName));
cViolinPlot.objs.right.line = cViolinPlot.objs.right.g.append("path")
.attr("class", "line")
.attr("fill", 'none')
.style("stroke", chart.violinPlots.color(cName));
}
}
}
mapObjects();
d3.select(window).on('resize.' + chart.chartSelector + '.violinPlot', chart.violinPlots.update);
//Update the divs with the proper values
chart.violinPlots.update();
return chart.violinPlots;
};
chart.renderBoxPlot = function (chartOptions) {
chart.boxPlots = {};
chart.boxPlots.chartOptions = chartOptions;
var bOpts = chart.boxPlots.chartOptions;
/*
* options:
* showOutliers: True/False (default True) - this shouldn't affect the min/max
* showWhiskers: True/False (default True)
* whiskersRatio: (default standard=iqr*1.5), other options, minmax, (future?: std)
* showBox: True/False (default True)
* showMedian: True/False (default True)
* showMean: True/False (default False)
* outlierScatter: True/False (default False) (not fully implimented)
* boxWidth (not implimented) what percent of the bin should the box take up
*/
//Create boxPlots
for (var cName in chart.groupObjs) {
chart.groupObjs[cName].boxPlot = {};
chart.groupObjs[cName].boxPlot.objs = {};
}
function calcOutliers(obj, values, metrics) {
/*
* Create lists of the outliers for each content group
*/
var cExtremes = [];
var cOutliers = [];
var cOut, idx;
for (idx = 0; idx <= values.length; idx++) {
cOut = {value: values[idx]};
if (cOut.value < metrics.lowerInnerFence) {
if (cOut.value < metrics.lowerOuterFence) {
cExtremes.push(cOut);
} else {
cOutliers.push(cOut);
}
} else if (cOut.value > metrics.upperInnerFence) {
if (cOut.value > metrics.upperOuterFence) {
cExtremes.push(cOut);
} else {
cOutliers.push(cOut);
}
}
}
obj.outliers = cOutliers;
obj.extremes = cExtremes;
}
function calcAllOutliers() {
if (!bOpts || (bOpts && bOpts.showOutliers !== false)) {
for (var cName in chart.groupObjs) {
calcOutliers(chart.groupObjs[cName].boxPlot.objs, chart.groupObjs[cName].values, chart.groupObjs[cName].metrics);
}
}
}
calcAllOutliers();
chart.boxPlots.change = function (updateOptions) {
if (updateOptions) {
for (var key in updateOptions) {
bOpts[key] = updateOptions[key]
}
}
mapObjects(true);
chart.boxPlots.update()
};
chart.boxPlots.update = function () {
var cName, cBoxPlot;
for (cName in chart.groupObjs) {
cBoxPlot = chart.groupObjs[cName].boxPlot;
// Get the box size
var groupWidth = {left: null, right: null, middle: null};
if (bOpts && bOpts.boxWidth) {
groupWidth = updateGroupWidth(bOpts.boxWidth)
} else {
groupWidth = updateGroupWidth(30)
}
var leftBound = chart.xScale(cName) + groupWidth.left;
var rightBound = chart.xScale(cName) + groupWidth.right;
var middle = chart.xScale(cName) + groupWidth.middle;
var sMetrics = {}; //temp var for scaled (plottable) metric values
for (var attr in chart.groupObjs[cName].metrics) {
sMetrics[attr] = null;
sMetrics[attr] = chart.yScale(chart.groupObjs[cName].metrics[attr]);
}
//// Box
if (cBoxPlot.objs.box) {
cBoxPlot.objs.box
.attr("x", leftBound)
.attr('width', rightBound - leftBound)
.attr("y", sMetrics.quartile3)
.attr("rx", 1)
.attr("ry", 1)
.attr("height", -sMetrics.quartile3 + sMetrics.quartile1)
}
//// Lines
if (cBoxPlot.objs.upperWhisker) {
cBoxPlot.objs.upperWhisker.fence
.attr("x1", leftBound)
.attr("x2", rightBound)
.attr('y1', sMetrics.upperInnerFence)
.attr("y2", sMetrics.upperInnerFence);
cBoxPlot.objs.upperWhisker.line
.attr("x1", middle)
.attr("x2", middle)
.attr('y1', sMetrics.quartile3)
.attr("y2", sMetrics.upperInnerFence);
cBoxPlot.objs.lowerWhisker.fence
.attr("x1", leftBound)
.attr("x2", rightBound)
.attr('y1', sMetrics.lowerInnerFence)
.attr("y2", sMetrics.lowerInnerFence);
cBoxPlot.objs.lowerWhisker.line
.attr("x1", middle)
.attr("x2", middle)
.attr('y1', sMetrics.quartile1)
.attr("y2", sMetrics.lowerInnerFence);
}
//// Median
if (cBoxPlot.objs.median) {
cBoxPlot.objs.median.line
.attr("x1", leftBound)
.attr("x2", rightBound)
.attr('y1', sMetrics.median)
.attr("y2", sMetrics.median);
cBoxPlot.objs.median.circle
.attr("cx", middle)
.attr("cy", sMetrics.median)
}
//// Mean
if (cBoxPlot.objs.mean) {
cBoxPlot.objs.mean.line
.attr("x1", leftBound)
.attr("x2", rightBound)
.attr('y1', sMetrics.mean)
.attr("y2", sMetrics.mean);
cBoxPlot.objs.mean.circle
.attr("cx", middle)
.attr("cy", sMetrics.mean);
}
//// Outliers
var pt;
if (cBoxPlot.objs.outliers) {
for (pt in cBoxPlot.objs.outliers) {
cBoxPlot.objs.outliers[pt].point
.attr("cx", middle/*+scatter()*/)
.attr("cy", chart.yScale(cBoxPlot.objs.outliers[pt].value));
}
}
if (cBoxPlot.objs.extremes) {
for (pt in cBoxPlot.objs.extremes) {
cBoxPlot.objs.extremes[pt].point
.attr("cx", middle/*+scatter()*/)
.attr("cy", chart.yScale(cBoxPlot.objs.extremes[pt].value));
}
}
}
};
function mapObjects(clear) {
// Map everything to divs
var cName, cBoxPlot;
if (bOpts && bOpts.colors) {
chart.boxPlots.colorFunct = updateColorFunction(bOpts.colors);
} else {
chart.boxPlots.colorFunct = colorFunct
}
for (cName in chart.groupObjs) {
cBoxPlot = chart.groupObjs[cName].boxPlot;
if (clear) {
cBoxPlot.objs.g.remove()
}
cBoxPlot.objs.g = chart.groupObjs[cName].g.append("g").attr("class", "box-plot");
//Plot Box (default show)
if (!bOpts || (bOpts && bOpts.showBox !== false)) {
cBoxPlot.objs.box = cBoxPlot.objs.g.append("rect")
.attr("class", "box")
.style("fill", chart.boxPlots.colorFunct(cName));
}
//Plot Median (default show)
if (!bOpts || (bOpts && bOpts.showMedian !== false)) {
cBoxPlot.objs.median = {line: null, circle: null};
cBoxPlot.objs.median.line = cBoxPlot.objs.g.append("line")
.attr("class", "median");
cBoxPlot.objs.median.circle = cBoxPlot.objs.g.append("circle")
.attr("class", "median")
.attr('r', 3)
.style("fill", chart.boxPlots.colorFunct(cName));
}
// Plot Mean (default no plot)
if (bOpts && bOpts.showMean) {
cBoxPlot.objs.mean = {line: null, circle: null};
cBoxPlot.objs.mean.line = cBoxPlot.objs.g.append("line")
.attr("class", "mean");
cBoxPlot.objs.mean.circle = cBoxPlot.objs.g.append("circle")
.attr("class", "mean")
.attr('r', 3)
.style("fill", chart.boxPlots.colorFunct(cName));
}
//Plot Whiskers (default show)
if (!bOpts || (bOpts && bOpts.showWhiskers !== false)) {
cBoxPlot.objs.upperWhisker = {fence: null, line: null};
cBoxPlot.objs.lowerWhisker = {fence: null, line: null};
cBoxPlot.objs.upperWhisker.fence = cBoxPlot.objs.g.append("line")
.attr("class", "upper whisker")
.style("stroke", chart.boxPlots.colorFunct(cName));
cBoxPlot.objs.upperWhisker.line = cBoxPlot.objs.g.append("line")
.attr("class", "upper whisker")
.style("stroke", chart.boxPlots.colorFunct(cName));
cBoxPlot.objs.lowerWhisker.fence = cBoxPlot.objs.g.append("line")
.attr("class", "lower whisker")
.style("stroke", chart.boxPlots.colorFunct(cName));
cBoxPlot.objs.lowerWhisker.line = cBoxPlot.objs.g.append("line")
.attr("class", "lower whisker")
.style("stroke", chart.boxPlots.colorFunct(cName));
}
// Plot outliers (default show)
//var scatter = function() {
// var range = chartObj.xScale.rangeBand()/3;
// return Math.floor(Math.random() * range)-range/2;
//}
if (!bOpts || (bOpts && bOpts.showOutliers !== false)) {
if (!cBoxPlot.objs.outliers) calcAllOutliers();
var pt;
if (cBoxPlot.objs.outliers.length) {
var outDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot outliers");
for (pt in cBoxPlot.objs.outliers) {
cBoxPlot.objs.outliers[pt].point = outDiv.append("circle")
.attr("class", "outlier")
.attr('r', 2)
.style("fill", chart.boxPlots.colorFunct(cName));
}
}
if (cBoxPlot.objs.extremes.length) {
var extDiv = cBoxPlot.objs.g.append("g").attr("class", "boxplot extremes");
for (pt in cBoxPlot.objs.extremes) {
cBoxPlot.objs.extremes[pt].point = extDiv.append("circle")
.attr("class", "extreme")
.attr('r', 2)
.style("stroke", chart.boxPlots.colorFunct(cName));
}
}
}
}
}
mapObjects();
d3.select(window).on('resize.' + chart.chartSelector + '.boxPlot', chart.boxPlots.update);
//Update the divs with the proper values
chart.boxPlots.update();
return chart.boxPlots;
};
return chart;
}