/** * A box plot is a chart that depicts numerical data via their quartile ranges. * * Examples: * - {@link http://dc-js.github.io/dc.js/examples/boxplot-basic.html Boxplot Basic example} * - {@link http://dc-js.github.io/dc.js/examples/boxplot-enhanced.html Boxplot Enhanced example} * - {@link http://dc-js.github.io/dc.js/examples/boxplot-render-data.html Boxplot Render Data example} * - {@link http://dc-js.github.io/dc.js/examples/boxplot-time.html Boxplot time example} * @class boxPlot * @memberof dc * @mixes dc.coordinateGridMixin * @example * // create a box plot under #chart-container1 element using the default global chart group * var boxPlot1 = dc.boxPlot('#chart-container1'); * // create a box plot under #chart-container2 element using chart group A * var boxPlot2 = dc.boxPlot('#chart-container2', 'chartGroupA'); * @param {String|node|d3.selection} parent - Any valid * {@link https://github.com/d3/d3-selection/blob/master/README.md#select d3 single selector} specifying * a dom block element such as a div; or a dom element or d3 selection. * @param {String} [chartGroup] - The name of the chart group this chart instance should be placed in. * Interaction with a chart will only trigger events and redraws within the chart's group. * @returns {dc.boxPlot} */ dc.boxPlot = function (parent, chartGroup) { var _chart = dc.coordinateGridMixin({}); // Returns a function to compute the interquartile range. function DEFAULT_WHISKERS_IQR (k) { return function (d) { var q1 = d.quartiles[0], q3 = d.quartiles[2], iqr = (q3 - q1) * k, i = -1, j = d.length; do { ++i; } while (d[i] < q1 - iqr); do { --j; } while (d[j] > q3 + iqr); return [i, j]; }; } var _whiskerIqrFactor = 1.5; var _whiskersIqr = DEFAULT_WHISKERS_IQR; var _whiskers = _whiskersIqr(_whiskerIqrFactor); var _box = d3.box(); var _tickFormat = null; var _renderDataPoints = false; var _dataOpacity = 0.3; var _dataWidthPortion = 0.8; var _showOutliers = true; var _boldOutlier = false; // Used in yAxisMin and yAxisMax to add padding in pixel coordinates // so the min and max data points/whiskers are within the chart var _yRangePadding = 8; var _boxWidth = function (innerChartWidth, xUnits) { if (_chart.isOrdinal()) { return _chart.x().bandwidth(); } else { return innerChartWidth / (1 + _chart.boxPadding()) / xUnits; } }; // default to ordinal _chart.x(d3.scaleBand()); _chart.xUnits(dc.units.ordinal); // valueAccessor should return an array of values that can be coerced into numbers // or if data is overloaded for a static array of arrays, it should be `Number`. // Empty arrays are not included. _chart.data(function (group) { return group.all().map(function (d) { d.map = function (accessor) { return accessor.call(d, d); }; return d; }).filter(function (d) { var values = _chart.valueAccessor()(d); return values.length !== 0; }); }); /** * Get or set the spacing between boxes as a fraction of box size. Valid values are within 0-1. * See the {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleBand d3 docs} * for a visual description of how the padding is applied. * @method boxPadding * @memberof dc.boxPlot * @instance * @see {@link https://github.com/d3/d3-scale/blob/master/README.md#scaleBand d3.scaleBand} * @param {Number} [padding=0.8] * @returns {Number|dc.boxPlot} */ _chart.boxPadding = _chart._rangeBandPadding; _chart.boxPadding(0.8); /** * Get or set the outer padding on an ordinal box chart. This setting has no effect on non-ordinal charts * or on charts with a custom {@link dc.boxPlot#boxWidth .boxWidth}. Will pad the width by * `padding * barWidth` on each side of the chart. * @method outerPadding * @memberof dc.boxPlot * @instance * @param {Number} [padding=0.5] * @returns {Number|dc.boxPlot} */ _chart.outerPadding = _chart._outerRangeBandPadding; _chart.outerPadding(0.5); /** * Get or set the numerical width of the boxplot box. The width may also be a function taking as * parameters the chart width excluding the right and left margins, as well as the number of x * units. * @example * // Using numerical parameter * chart.boxWidth(10); * // Using function * chart.boxWidth((innerChartWidth, xUnits) { ... }); * @method boxWidth * @memberof dc.boxPlot * @instance * @param {Number|Function} [boxWidth=0.5] * @returns {Number|Function|dc.boxPlot} */ _chart.boxWidth = function (boxWidth) { if (!arguments.length) { return _boxWidth; } _boxWidth = typeof boxWidth === 'function' ? boxWidth : dc.utils.constant(boxWidth); return _chart; }; var boxTransform = function (d, i) { var xOffset = _chart.x()(_chart.keyAccessor()(d, i)); return 'translate(' + xOffset + ', 0)'; }; _chart._preprocessData = function () { if (_chart.elasticX()) { _chart.x().domain([]); } }; _chart.plotData = function () { var _calculatedBoxWidth = _boxWidth(_chart.effectiveWidth(), _chart.xUnitCount()); _box.whiskers(_whiskers) .width(_calculatedBoxWidth) .height(_chart.effectiveHeight()) .value(_chart.valueAccessor()) .domain(_chart.y().domain()) .duration(_chart.transitionDuration()) .tickFormat(_tickFormat) .renderDataPoints(_renderDataPoints) .dataOpacity(_dataOpacity) .dataWidthPortion(_dataWidthPortion) .renderTitle(_chart.renderTitle()) .showOutliers(_showOutliers) .boldOutlier(_boldOutlier); var boxesG = _chart.chartBodyG().selectAll('g.box').data(_chart.data(), _chart.keyAccessor()); var boxesGEnterUpdate = renderBoxes(boxesG); updateBoxes(boxesGEnterUpdate); removeBoxes(boxesG); _chart.fadeDeselectedArea(_chart.filter()); }; function renderBoxes (boxesG) { var boxesGEnter = boxesG.enter().append('g'); boxesGEnter .attr('class', 'box') .attr('transform', boxTransform) .call(_box) .on('click', function (d) { _chart.filter(_chart.keyAccessor()(d)); _chart.redrawGroup(); }); return boxesGEnter.merge(boxesG); } function updateBoxes (boxesG) { dc.transition(boxesG, _chart.transitionDuration(), _chart.transitionDelay()) .attr('transform', boxTransform) .call(_box) .each(function (d) { var color = _chart.getColor(d, 0); d3.select(this).select('rect.box').attr('fill', color); d3.select(this).selectAll('circle.data').attr('fill', color); }); } function removeBoxes (boxesG) { boxesG.exit().remove().call(_box); } function minDataValue () { return d3.min(_chart.data(), function (e) { return d3.min(_chart.valueAccessor()(e)); }); } function maxDataValue () { return d3.max(_chart.data(), function (e) { return d3.max(_chart.valueAccessor()(e)); }); } function yAxisRangeRatio () { return ((maxDataValue() - minDataValue()) / _chart.effectiveHeight()); } _chart.fadeDeselectedArea = function (brushSelection) { if (_chart.hasFilter()) { if (_chart.isOrdinal()) { _chart.g().selectAll('g.box').each(function (d) { if (_chart.isSelectedNode(d)) { _chart.highlightSelected(this); } else { _chart.fadeDeselected(this); } }); } else { if (!(_chart.brushOn() || _chart.parentBrushOn())) { return; } var start = brushSelection[0]; var end = brushSelection[1]; var keyAccessor = _chart.keyAccessor(); _chart.g().selectAll('g.box').each(function (d) { var key = keyAccessor(d); if (key < start || key >= end) { _chart.fadeDeselected(this); } else { _chart.highlightSelected(this); } }); } } else { _chart.g().selectAll('g.box').each(function () { _chart.resetHighlight(this); }); } }; _chart.isSelectedNode = function (d) { return _chart.hasFilter(_chart.keyAccessor()(d)); }; _chart.yAxisMin = function () { var padding = _yRangePadding * yAxisRangeRatio(); return dc.utils.subtract(minDataValue() - padding, _chart.yAxisPadding()); }; _chart.yAxisMax = function () { var padding = _yRangePadding * yAxisRangeRatio(); return dc.utils.add(maxDataValue() + padding, _chart.yAxisPadding()); }; /** * Get or set the numerical format of the boxplot median, whiskers and quartile labels. Defaults * to integer formatting. * @example * // format ticks to 2 decimal places * chart.tickFormat(d3.format('.2f')); * @method tickFormat * @memberof dc.boxPlot * @instance * @param {Function} [tickFormat] * @returns {Number|Function|dc.boxPlot} */ _chart.tickFormat = function (tickFormat) { if (!arguments.length) { return _tickFormat; } _tickFormat = tickFormat; return _chart; }; /** * Get or set the amount of padding to add, in pixel coordinates, to the top and * bottom of the chart to accommodate box/whisker labels. * @example * // allow more space for a bigger whisker font * chart.yRangePadding(12); * @method yRangePadding * @memberof dc.boxPlot * @instance * @param {Function} [yRangePadding = 8] * @returns {Number|Function|dc.boxPlot} */ _chart.yRangePadding = function (yRangePadding) { if (!arguments.length) { return _yRangePadding; } _yRangePadding = yRangePadding; return _chart; }; /** * Get or set whether individual data points will be rendered. * @example * // Enable rendering of individual data points * chart.renderDataPoints(true); * @method renderDataPoints * @memberof dc.boxPlot * @instance * @param {Boolean} [show=false] * @returns {Boolean|dc.boxPlot} */ _chart.renderDataPoints = function (show) { if (!arguments.length) { return _renderDataPoints; } _renderDataPoints = show; return _chart; }; /** * Get or set the opacity when rendering data. * @example * // If individual data points are rendered increase the opacity. * chart.dataOpacity(0.7); * @method dataOpacity * @memberof dc.boxPlot * @instance * @param {Number} [opacity=0.3] * @returns {Number|dc.boxPlot} */ _chart.dataOpacity = function (opacity) { if (!arguments.length) { return _dataOpacity; } _dataOpacity = opacity; return _chart; }; /** * Get or set the portion of the width of the box to show data points. * @example * // If individual data points are rendered increase the data box. * chart.dataWidthPortion(0.9); * @method dataWidthPortion * @memberof dc.boxPlot * @instance * @param {Number} [percentage=0.8] * @returns {Number|dc.boxPlot} */ _chart.dataWidthPortion = function (percentage) { if (!arguments.length) { return _dataWidthPortion; } _dataWidthPortion = percentage; return _chart; }; /** * Get or set whether outliers will be rendered. * @example * // Disable rendering of outliers * chart.showOutliers(false); * @method showOutliers * @memberof dc.boxPlot * @instance * @param {Boolean} [show=true] * @returns {Boolean|dc.boxPlot} */ _chart.showOutliers = function (show) { if (!arguments.length) { return _showOutliers; } _showOutliers = show; return _chart; }; /** * Get or set whether outliers will be drawn bold. * @example * // If outliers are rendered display as bold * chart.boldOutlier(true); * @method boldOutlier * @memberof dc.boxPlot * @instance * @param {Boolean} [show=false] * @returns {Boolean|dc.boxPlot} */ _chart.boldOutlier = function (show) { if (!arguments.length) { return _boldOutlier; } _boldOutlier = show; return _chart; }; return _chart.anchor(parent, chartGroup); };