const whiskerElement = require('./element.whisker'); module.exports = function(Chart) { whiskerElement(Chart); var helpers = Chart.helpers; Chart.defaults.whisker = { hover: { mode: 'label' }, scales: { xAxes: [{ type: 'category', // Specific to Bar Controller categoryPercentage: 0.8, barPercentage: 0.9, // grid line settings gridLines: { offsetGridLines: true } }], yAxes: [{ type: 'linear' }] } }; Chart.controllers.whisker = Chart.DatasetController.extend({ dataElementType: Chart.elements.Whisker, initialize: function(chart, datasetIndex) { Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); // Use this to indicate that this is a bar dataset. this.getMeta().bar = true; }, // Get the number of datasets that display bars. We use this to correctly calculate the bar width getBarCount: function() { var me = this; var barCount = 0; helpers.each(me.chart.data.datasets, function(dataset, datasetIndex) { var meta = me.chart.getDatasetMeta(datasetIndex); if (meta.bar && me.chart.isDatasetVisible(datasetIndex)) { ++barCount; } }, me); return barCount; }, update: function(reset) { var me = this; helpers.each(me.getMeta().data, function(rectangle, index) { me.updateElement(rectangle, index, reset); }, me); }, updateElement: function(rectangle, index, reset) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var yScale = me.getScaleForId(meta.yAxisID); var scaleBase = yScale.getBasePixel(); var rectangleElementOptions = me.chart.options.elements.rectangle; var custom = rectangle.custom || {}; var dataset = me.getDataset(); rectangle._xScale = xScale; rectangle._yScale = yScale; rectangle._datasetIndex = me.index; rectangle._index = index; var ruler = me.getRuler(index); rectangle._model = { x: me.calculateBarX(index, me.index, ruler), y: reset ? scaleBase : me.boxTopValue(index, me.index), // Tooltip label: me.chart.data.labels[index], datasetLabel: dataset.label, // Appearance median: reset ? scaleBase : me.medianValue(me.index, index), maxV: reset ? scaleBase : me.maxValue(me.index, index), minV: reset ? scaleBase : me.minValue(me.index, index), base: reset ? scaleBase : me.boxBottomValue(me.index, index), width: me.calculateBarWidth(ruler), backgroundColor: custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(me.stddev(me.index, index) > 3 ? dataset.altBackgroundColor : dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), borderSkipped: custom.borderSkipped ? custom.borderSkipped : rectangleElementOptions.borderSkipped, borderColor: custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor), borderWidth: custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth) }; rectangle.pivot(); }, stddev: function(datasetIndex, index) { var me = this; var obj = me.getDataset().data[index]; var value = Number(obj.stddev); return value; }, minValue: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var obj = me.getDataset().data[index]; var value = Number(obj.min); return yScale.getPixelForValue(value); }, maxValue: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var obj = me.getDataset().data[index]; var value = Number(obj.max); return yScale.getPixelForValue(value); }, medianValue: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var obj = me.getDataset().data[index]; var value = Number(obj.median); return yScale.getPixelForValue(value); }, boxBottomValue: function(datasetIndex, index) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var obj = me.getDataset().data[index]; var value = Number(obj.firstQuartile); return yScale.getPixelForValue(value); }, boxTopValue: function(index, datasetIndex) { var me = this; var meta = me.getMeta(); var yScale = me.getScaleForId(meta.yAxisID); var obj = me.getDataset().data[index]; var value = Number(obj.thirdQuartile); return yScale.getPixelForValue(value); }, getRuler: function(index) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var datasetCount = me.getBarCount(); var tickWidth; if (xScale.options.type === 'category') { tickWidth = xScale.getPixelForTick(index + 1) - xScale.getPixelForTick(index); } else { // Average width tickWidth = xScale.width / xScale.ticks.length; } var categoryWidth = tickWidth * xScale.options.categoryPercentage; var categorySpacing = (tickWidth - (tickWidth * xScale.options.categoryPercentage)) / 2; var fullBarWidth = categoryWidth / datasetCount; if (xScale.ticks.length !== me.chart.data.labels.length) { var perc = xScale.ticks.length / me.chart.data.labels.length; fullBarWidth = fullBarWidth * perc; } var barWidth = fullBarWidth * xScale.options.barPercentage; var barSpacing = fullBarWidth - (fullBarWidth * xScale.options.barPercentage); return { datasetCount: datasetCount, tickWidth: tickWidth, categoryWidth: categoryWidth, categorySpacing: categorySpacing, fullBarWidth: fullBarWidth, barWidth: barWidth, barSpacing: barSpacing }; }, calculateBarWidth: function(ruler) { var xScale = this.getScaleForId(this.getMeta().xAxisID); if (xScale.options.barThickness) { return xScale.options.barThickness; } return ruler.barWidth; }, // Get bar index from the given dataset index accounting for the fact that not all bars are visible getBarIndex: function(datasetIndex) { var barIndex = 0; var meta; var j; for (j = 0; j < datasetIndex; ++j) { meta = this.chart.getDatasetMeta(j); if (meta.bar && this.chart.isDatasetVisible(j)) { ++barIndex; } } return barIndex; }, calculateBarX: function(index, datasetIndex, ruler) { var me = this; var meta = me.getMeta(); var xScale = me.getScaleForId(meta.xAxisID); var barIndex = me.getBarIndex(datasetIndex); var leftTick = xScale.getPixelForValue(null, index, datasetIndex, me.chart.isCombo); leftTick -= me.chart.isCombo ? (ruler.tickWidth / 2) : 0; return leftTick + (ruler.barWidth / 2) + ruler.categorySpacing + (ruler.barWidth * barIndex) + (ruler.barSpacing / 2) + (ruler.barSpacing * barIndex); }, draw: function(ease) { var me = this; var easingDecimal = ease || 1; var metaData = me.getMeta().data; var dataset = me.getDataset(); var i, len; for (i = 0, len = metaData.length; i < len; ++i) { var d = dataset.data[i]; if (d !== null && d !== undefined && typeof d === 'object' && !isNaN(d.median)) { metaData[i].transition(easingDecimal).draw(); } } }, setHoverStyle: function(rectangle) { var dataset = this.chart.data.datasets[rectangle._datasetIndex]; var index = rectangle._index; var custom = rectangle.custom || {}; var model = rectangle._model; model.backgroundColor = custom.hoverBackgroundColor ? custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.getHoverColor(model.backgroundColor)); model.borderColor = custom.hoverBorderColor ? custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.getHoverColor(model.borderColor)); model.borderWidth = custom.hoverBorderWidth ? custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, model.borderWidth); }, removeHoverStyle: function(rectangle) { var dataset = this.chart.data.datasets[rectangle._datasetIndex]; var index = rectangle._index; var custom = rectangle.custom || {}; var model = rectangle._model; var rectangleElementOptions = this.chart.options.elements.rectangle; model.backgroundColor = custom.backgroundColor ? custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor); model.borderColor = custom.borderColor ? custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, rectangleElementOptions.borderColor); model.borderWidth = custom.borderWidth ? custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangleElementOptions.borderWidth); } }); };