From fe99517cb38ee817f22a261724193085868d91e9 Mon Sep 17 00:00:00 2001 From: Tom Gallacher Date: Mon, 21 Nov 2016 11:12:22 +0000 Subject: [PATCH] bar and whisker: adding min and max --- .../chartjs-whiskers/client/actions.js | 2 +- .../chartjs-whiskers/client/chart/base.js | 2 + .../chartjs-whiskers/client/chart/cpu.js | 4 +- .../client/element.whisker.js | 221 ++++++++++++++++++ .../chartjs-whiskers/client/whisker.js | 50 +++- .../graphs-matrix/chartjs-whiskers/local.txt | 11 + .../chartjs-whiskers/server/metric.js | 5 +- 7 files changed, 289 insertions(+), 6 deletions(-) create mode 100644 spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js create mode 100644 spikes/graphs-matrix/chartjs-whiskers/local.txt diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/actions.js b/spikes/graphs-matrix/chartjs-whiskers/client/actions.js index 1e0eb776..b0c18201 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/client/actions.js +++ b/spikes/graphs-matrix/chartjs-whiskers/client/actions.js @@ -8,7 +8,7 @@ const actions = { disk: [] }); - const newData = ['cpu'].reduce((sum, key) => { + const newData = ['cpu', 'mem'].reduce((sum, key) => { const item = { ...action.payload.stats[key], when: action.payload.when diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js b/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js index 19b1325f..af16873f 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js +++ b/spikes/graphs-matrix/chartjs-whiskers/client/chart/base.js @@ -32,6 +32,8 @@ module.exports = React.createClass({ options: { scales: { xAxes: [{ + barPercentage: 1.0, + categoryPercentage: 1.0, }], yAxes: [{ ticks: { diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/chart/cpu.js b/spikes/graphs-matrix/chartjs-whiskers/client/chart/cpu.js index 42f5e8d0..684d5608 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/client/chart/cpu.js +++ b/spikes/graphs-matrix/chartjs-whiskers/client/chart/cpu.js @@ -5,7 +5,8 @@ const React = require('react'); const colors = { user: 'rgb(255, 99, 132)', sys: 'rgb(255, 159, 64)', - perc: 'rgba(54, 74, 205, 0.2)' + perc: 'rgba(54, 74, 205, 0.2)', + alt: 'rgba(245, 93, 93, 0.2)' }; module.exports = ({ @@ -16,6 +17,7 @@ module.exports = ({ return { label: key, backgroundColor: colors[key], + altBackgroundColor: colors['alt'], data: buildArray(windowSize).map((v, i) => ((data[i] || {})[key] || { firstQuartile: 0, thirdQuartile: 0, median: 0, max: 0, min: 0 })) }; }); diff --git a/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js b/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js new file mode 100644 index 00000000..75ff1d09 --- /dev/null +++ b/spikes/graphs-matrix/chartjs-whiskers/client/element.whisker.js @@ -0,0 +1,221 @@ +'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 index 3c977e0b..3cedf016 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/client/whisker.js +++ b/spikes/graphs-matrix/chartjs-whiskers/client/whisker.js @@ -1,4 +1,8 @@ +const whiskerElement = require('./element.whisker'); + module.exports = function(Chart) { + whiskerElement(Chart); + var helpers = Chart.helpers; Chart.defaults.whisker = { @@ -27,7 +31,7 @@ module.exports = function(Chart) { Chart.controllers.whisker = Chart.DatasetController.extend({ - dataElementType: Chart.Element.extend(Chart.elements.Rectangle.prototype), + dataElementType: Chart.elements.Whisker, initialize: function(chart, datasetIndex) { Chart.DatasetController.prototype.initialize.call(this, chart, datasetIndex); @@ -81,9 +85,12 @@ module.exports = function(Chart) { 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(dataset.backgroundColor, index, rectangleElementOptions.backgroundColor), + 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) @@ -92,6 +99,45 @@ module.exports = function(Chart) { rectangle.pivot(); }, + stddev: function(datasetIndex, index) { + var me = this; + var obj = me.getDataset().data[index]; + var value = Number(obj.stddev); + + console.log('stddev >>', value); + 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(); diff --git a/spikes/graphs-matrix/chartjs-whiskers/local.txt b/spikes/graphs-matrix/chartjs-whiskers/local.txt new file mode 100644 index 00000000..198e31d4 --- /dev/null +++ b/spikes/graphs-matrix/chartjs-whiskers/local.txt @@ -0,0 +1,11 @@ +OldCpuSpeed=2712 +NewCpuSpeedCount=0 +NewCpuSpeed=0 +RollingAverage=1000 +RollingAverageIsFromV27=1 +ComputerGUID=7fe43060180dc17e1be44afd3c056d72 +CPUHours=1 +RollingStartTime=0 +Affinity=100 +ThreadsPerTest=4 +Pid=0 diff --git a/spikes/graphs-matrix/chartjs-whiskers/server/metric.js b/spikes/graphs-matrix/chartjs-whiskers/server/metric.js index 977b42e7..2e1f226e 100644 --- a/spikes/graphs-matrix/chartjs-whiskers/server/metric.js +++ b/spikes/graphs-matrix/chartjs-whiskers/server/metric.js @@ -15,7 +15,7 @@ const getCPU = (fn) => { }; const getPerc = (fn) => { - async.timesSeries(10, (n, next) => { + async.timesSeries(20, (n, next) => { osutils.cpuUsage((p) => { const percentage = p * 100; next(null, percentage); @@ -27,7 +27,8 @@ const getPerc = (fn) => { median: statistics.median(sample), thirdQuartile: statistics.quantile(sample, 0.75), max: statistics.max(sample), - min: statistics.min(sample) + min: statistics.min(sample), + stddev: statistics.sampleStandardDeviation(sample) } }); });