From 0614839905113448980a36f2f3ea11edb79a3bfc Mon Sep 17 00:00:00 2001 From: Tom Gallacher Date: Wed, 4 Jan 2017 16:45:31 +0000 Subject: [PATCH] Spike should use new chartjs box plot module --- .../chartjs-whiskers/client/chart/base.js | 2 +- .../client/element.whisker.js | 221 -------------- .../chartjs-whiskers/client/whisker.js | 276 ------------------ .../chartjs-whiskers/package.json | 1 + .../graphs-matrix/chartjs-whiskers/yarn.lock | 23 ++ 5 files changed, 25 insertions(+), 498 deletions(-) delete mode 100644 spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js delete mode 100644 spikes/graphs-matrix/chartjs-whiskers/client/whisker.js diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js b/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js index 09bd940f..936ca094 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js +++ b/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js @@ -1,7 +1,7 @@ const buildArray = require('build-array'); const Chart = require('chart.js'); const React = require('react'); -const whisker = require('../whisker'); +const whisker = require('chartjs-chart-box-plot'); whisker(Chart); module.exports = React.createClass({ diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js b/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js deleted file mode 100644 index 75ff1d09..00000000 --- a/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js +++ /dev/null @@ -1,221 +0,0 @@ -'use strict'; - -module.exports = function(Chart) { - - var globalOpts = Chart.defaults.global; - - globalOpts.elements.rectangle = { - backgroundColor: globalOpts.defaultColor, - borderWidth: 0, - borderColor: globalOpts.defaultColor, - borderSkipped: 'bottom' - }; - - function isVertical(bar) { - return bar._view.width !== undefined; - } - - /** - * Helper function to get the bounds of the bar regardless of the orientation - * @private - * @param bar {Chart.Element.Rectangle} the bar - * @return {Bounds} bounds of the bar - */ - function getBarBounds(bar) { - var vm = bar._view; - var x1, x2, y1, y2; - - if (isVertical(bar)) { - // vertical - var halfWidth = vm.width / 2; - x1 = vm.x - halfWidth; - x2 = vm.x + halfWidth; - y1 = Math.min(vm.y, vm.base); - y2 = Math.max(vm.y, vm.base); - } else { - // horizontal bar - var halfHeight = vm.height / 2; - x1 = Math.min(vm.x, vm.base); - x2 = Math.max(vm.x, vm.base); - y1 = vm.y - halfHeight; - y2 = vm.y + halfHeight; - } - - return { - left: x1, - top: y1, - right: x2, - bottom: y2 - }; - } - - Chart.elements.Whisker = Chart.Element.extend({ - draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; - - var halfWidth = vm.width / 2, - leftX = vm.x - halfWidth, - rightX = vm.x + halfWidth, - top = vm.base - (vm.base - vm.y), - halfStroke = vm.borderWidth / 2; - - // Canvas doesn't allow us to stroke inside the width so we can - // adjust the sizes to fit if we're setting a stroke on the line - if (vm.borderWidth) { - leftX += halfStroke; - rightX -= halfStroke; - top += halfStroke; - } - - ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; - - // Corner points, from bottom-left to bottom-right clockwise - // | 1 2 | - // | 0 3 | - var corners = [ - [leftX, vm.base], - [leftX, top], - [rightX, top], - [rightX, vm.base] - ]; - - // Find first (starting) corner with fallback to 'bottom' - var borders = ['bottom', 'left', 'top', 'right']; - var startCorner = borders.indexOf(vm.borderSkipped, 0); - if (startCorner === -1) { - startCorner = 0; - } - - function cornerAt(index) { - return corners[(startCorner + index) % 4]; - } - - // Draw rectangle from 'startCorner' - var corner = cornerAt(0); - ctx.moveTo(corner[0], corner[1]); - - for (var i = 1; i < 4; i++) { - corner = cornerAt(i); - ctx.lineTo(corner[0], corner[1]); - } - - ctx.fill(); - if (vm.borderWidth) { - ctx.stroke(); - } - ctx.closePath(); - - // Median line - ctx.beginPath(); - - ctx.moveTo(leftX, vm.median); - ctx.lineTo(rightX, vm.median); - ctx.lineWidth = 2; - - // set line color - ctx.strokeStyle = 'rgb(54, 74, 205)'; - ctx.stroke(); - ctx.closePath(); - - // Top Whisker - // if (smaller than 5px then do not draw) - if (vm.median - vm.maxV > 10) { - ctx.beginPath(); - ctx.moveTo((rightX - leftX) / 2 + leftX, vm.median - 1); - ctx.lineTo((rightX - leftX) / 2 + leftX, vm.maxV); - ctx.lineWidth = 2; - ctx.strokeStyle = 'rgb(245, 93, 93)'; - ctx.stroke(); - ctx.closePath(); - ctx.beginPath(); - ctx.arc((rightX - leftX) / 2 + leftX, vm.maxV, 3, 0, 2 * Math.PI); - ctx.fillStyle = 'rgb(245, 93, 93)'; - ctx.fill(); - } - - // Bottom Whisker - // if (smaller than 5px then do not draw) - if (vm.minV - vm.median > 10) { - ctx.beginPath(); - ctx.moveTo((rightX - leftX) / 2 + leftX, vm.median + 1); - ctx.lineTo((rightX - leftX) / 2 + leftX, vm.minV); - ctx.lineWidth = 2; - ctx.strokeStyle = 'rgb(245, 93, 93)'; - ctx.stroke(); - ctx.closePath(); - ctx.beginPath(); - ctx.arc((rightX - leftX) / 2 + leftX, vm.minV, 3, 0, 2 * Math.PI); - ctx.fillStyle = 'rgb(245, 93, 93)'; - ctx.fill(); - } - }, - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - inRange: function(mouseX, mouseY) { - var inRange = false; - - if (this._view) { - var bounds = getBarBounds(this); - inRange = mouseX >= bounds.left && mouseX <= bounds.right && mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; - }, - inLabelRange: function(mouseX, mouseY) { - var me = this; - if (!me._view) { - return false; - } - - var inRange = false; - var bounds = getBarBounds(me); - - if (isVertical(me)) { - inRange = mouseX >= bounds.left && mouseX <= bounds.right; - } else { - inRange = mouseY >= bounds.top && mouseY <= bounds.bottom; - } - - return inRange; - }, - inXRange: function(mouseX) { - var bounds = getBarBounds(this); - return mouseX >= bounds.left && mouseX <= bounds.right; - }, - inYRange: function(mouseY) { - var bounds = getBarBounds(this); - return mouseY >= bounds.top && mouseY <= bounds.bottom; - }, - getCenterPoint: function() { - var vm = this._view; - var x, y; - if (isVertical(this)) { - x = vm.x; - y = (vm.y + vm.base) / 2; - } else { - x = (vm.x + vm.base) / 2; - y = vm.y; - } - - return {x: x, y: y}; - }, - getArea: function() { - var vm = this._view; - return vm.width * Math.abs(vm.y - vm.base); - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y - }; - } - }); - -}; diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/whisker.js b/spikes/graphs-matrix/chartjs-whiskers/client/whisker.js deleted file mode 100644 index 6e4b0c56..00000000 --- a/spikes/graphs-matrix/chartjs-whiskers/client/whisker.js +++ /dev/null @@ -1,276 +0,0 @@ -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); - } - - }); -}; diff --git a/spikes/graphs-matrix/chartjs-whiskers/package.json b/spikes/graphs-matrix/chartjs-whiskers/package.json index f16bbbb5..643a5131 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/package.json +++ b/spikes/graphs-matrix/chartjs-whiskers/package.json @@ -18,6 +18,7 @@ "babel-runtime": "^6.11.6", "build-array": "^1.0.0", "chart.js": "^2.3.0", + "chartjs-chart-box-plot": "^1.0.0-9", "classnames": "^2.2.5", "component-emitter": "^1.2.1", "cpu-percent": "^2.0.1", diff --git a/spikes/graphs-matrix/chartjs-whiskers/yarn.lock b/spikes/graphs-matrix/chartjs-whiskers/yarn.lock index 47298b29..b8163327 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/yarn.lock +++ b/spikes/graphs-matrix/chartjs-whiskers/yarn.lock @@ -947,6 +947,13 @@ chart.js@^2.3.0: chartjs-color "^2.0.0" moment "^2.10.6" +chartjs-chart-box-plot@^1.0.0-9: + version "1.0.0-9" + resolved "https://registry.yarnpkg.com/chartjs-chart-box-plot/-/chartjs-chart-box-plot-1.0.0-9.tgz#bd392c689301e71b13f602818629f5a0965eaddb" + dependencies: + react "^15.4.1" + react-dom "^15.4.1" + chartjs-color-string@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/chartjs-color-string/-/chartjs-color-string-0.4.0.tgz#57748d4530ae28d8db0a5492182ba06dfdf2f468" @@ -3785,6 +3792,14 @@ react-dom@^15.3.2: loose-envify "^1.1.0" object-assign "^4.1.0" +react-dom@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-15.4.1.tgz#d54c913261aaedb17adc20410d029dcc18a1344a" + dependencies: + fbjs "^0.8.1" + loose-envify "^1.1.0" + object-assign "^4.1.0" + react-hot-loader@^3.0.0-beta.6: version "3.0.0-beta.6" resolved "https://registry.yarnpkg.com/react-hot-loader/-/react-hot-loader-3.0.0-beta.6.tgz#463fac0bfc8b63a8385258af20c91636abce75f4" @@ -3837,6 +3852,14 @@ react@^15.3.2: loose-envify "^1.1.0" object-assign "^4.1.0" +react@^15.4.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/react/-/react-15.4.1.tgz#498e918602677a3983cd0fd206dfe700389a0dd6" + dependencies: + fbjs "^0.8.4" + loose-envify "^1.1.0" + object-assign "^4.1.0" + readable-stream@^1.0.27-1, readable-stream@^1.1.13: version "1.1.14" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9"