diff --git a/app/js/controllers/grouped-runs.js b/app/js/controllers/grouped-runs.js index 9bc30671..0224de1c 100644 --- a/app/js/controllers/grouped-runs.js +++ b/app/js/controllers/grouped-runs.js @@ -114,10 +114,9 @@ function GroupedRunsController( }); }); - vm.chartData = [ - { key: 'Passes', values: passEntries, color: 'blue' }, - { key: 'Failures', values: failEntries, color: 'red' } - ]; + vm.passes = passEntries; + vm.failures = failEntries; + vm.failRates = failRateEntries; vm.chartDataRate = [ { key: '% Failures', values: failRateEntries } diff --git a/app/js/controllers/home.js b/app/js/controllers/home.js index 7c557783..3139c51d 100644 --- a/app/js/controllers/home.js +++ b/app/js/controllers/home.js @@ -50,11 +50,9 @@ function HomeController( var dateStats = projectService.getStatsByDate(projects); var entries = getChartEntries(dateStats, blanks); - vm.chartData = [ - { key: 'Passes', values: entries.passes, color: 'blue' }, - { key: 'Failures', values: entries.failures, color: 'red' } - ]; - vm.chartDataRate = [{ key: '% Failures', values: entries.failRate }]; + vm.passes = entries.passes; + vm.failures = entries.failures; + vm.failRate = entries.failRate; vm.projects = projects .sort(byFailRateDesc) .map(function(project) { return generateHorizontalBarData(project); }); diff --git a/app/js/controllers/job.js b/app/js/controllers/job.js index 3ffddd94..2975bd8e 100644 --- a/app/js/controllers/job.js +++ b/app/js/controllers/job.js @@ -140,15 +140,10 @@ function JobController( }); } - vm.chartData = [ - { key: 'Passes', values: passEntries, color: 'blue' }, - { key: 'Failures', values: failEntries, color: 'red' }, - { key: 'Skips', values: skipEntries, color: 'violet' } - ]; - - vm.chartDataRate = [ - { key: '% Failures', values: failRateEntries } - ]; + vm.passes = passEntries; + vm.failures = failEntries; + vm.skips = skipEntries; + vm.failRates = failRateEntries; vm.tests = Object.keys(tests).map(function(test) { return tests[test]; diff --git a/app/js/directives/chart-canvas-line.js b/app/js/directives/chart-canvas-line.js new file mode 100644 index 00000000..b19fc41a --- /dev/null +++ b/app/js/directives/chart-canvas-line.js @@ -0,0 +1,163 @@ +'use strict'; + +var directivesModule = require('./_index.js'); + +/** + * @ngInject + */ +function chartCanvasLine() { + var link = function(scope, el, attrs, ctrl) { + var base = ctrl.createCanvas(ctrl.width, ctrl.height, false); + var baseDirty = false; + + var overlay = ctrl.createCanvas(ctrl.width, ctrl.height, false); + var overlayDirty = false; + + var stroke = scope.stroke || 'black'; + var lineWidth = scope.lineWidth || 1; + + var dataset = null; + var screenX = null; + var screenY = null; + var dataX = null; + var dataY = null; + + var nearest = null; + + function updateAxes() { + dataset = ctrl.datasets[scope.dataset]; + if (!dataset) { + return; + } + + var axes = scope.axes.split(/[\s,]+/).map(function(name) { + return ctrl.axes[name]; + }); + screenX = axes.find(function(a) { return a.orient === 'horizontal'; }); + screenY = axes.find(function(a) { return a.orient === 'vertical'; }); + + dataX = ctrl.data(dataset.name, screenX.name); + dataY = ctrl.data(dataset.name, screenY.name); + } + + function renderBase() { + var dataset = ctrl.datasets[scope.dataset]; + if (!dataset) { + return; + } + + var ctx = base.ctx; + ctx.strokeStyle = stroke; + ctx.lineWidth = lineWidth * base.ratio; + + ctx.beginPath(); + ctx.moveTo(screenX.scale(dataX[0]), screenY.scale(dataY[0])); + for (var i = 1; i < dataX.length; i++) { + ctx.lineTo(screenX.scale(dataX[i]), screenY.scale(dataY[i])); + } + + ctx.stroke(); + + baseDirty = false; + } + + function renderOverlay() { + var ctx = overlay.ctx; + ctx.clearRect(0, 0, overlay.canvas.width, overlay.canvas.height); + + if (nearest) { + ctx.fillStyle = 'rgba(50, 50, 50, 0.15)'; + ctx.strokeStyle = 'rgba(100, 100, 100, 0.75)'; + ctx.lineWidth = 1; + + ctx.beginPath(); + ctx.arc( + screenX.scale(screenX.mapper(nearest)), + screenY.scale(screenY.mapper(nearest)), + 5 * overlay.ratio, + 0, Math.PI * 2 + ); + + ctx.fill(); + ctx.stroke(); + } + + overlayDirty = false; + } + + function handleUpdate() { + updateAxes(); + + baseDirty = true; + overlayDirty = true; + } + + scope.$on('render', function(event, canvas) { + if (baseDirty) { + renderBase(); + } + + canvas.ctx.drawImage(base.canvas, 0, 0); + }); + + scope.$on('renderOverlay', function(event, canvas) { + if (overlayDirty) { + renderOverlay(); + } + + canvas.ctx.drawImage(overlay.canvas, 0, 0); + }); + + scope.$on('update', handleUpdate); + + scope.$on('resize', function(event, width, height) { + base.resize(width, height); + overlay.resize(width, height); + }); + + scope.$on('mousemove', function(event, p) { + if (!dataset) { + return; + } + + nearest = ctrl.nearestPoint(p, dataset, screenX, screenY, 10 * base.ratio); + overlayDirty = true; + ctrl.render(); + + if (nearest) { + ctrl.tooltips.set(dataset.name, { + points: [nearest], + style: stroke + }); + } else { + ctrl.tooltips.delete(dataset.name); + } + }); + + scope.$on('mouseout', function() { + if (!dataset) { + return; + } + + nearest = null; + overlayDirty = true; + ctrl.render(); + + ctrl.tooltips.delete(dataset.name); + }); + }; + + return { + restrict: 'E', + require: '^chart', + link: link, + scope: { + dataset: '@', + axes: '@', + lineWidth: '=', + stroke: '@' + } + }; +} + +directivesModule.directive('chartCanvasLine', chartCanvasLine); diff --git a/app/js/directives/chart-line.js b/app/js/directives/chart-line.js deleted file mode 100644 index 648d65d8..00000000 --- a/app/js/directives/chart-line.js +++ /dev/null @@ -1,73 +0,0 @@ -'use strict'; - -var directivesModule = require('./_index.js'); - -var d3 = require('d3'); -var nv = require('nvd3'); - -/** - * @ngInject - */ -function chartLine() { - var link = function(scope, el, attrs) { - scope.$on('loading-started', function() { - el.css({'display' : 'none'}); - }); - - scope.$on('loading-complete', function() { - el.css({'display' : 'block'}); - }); - - var chart = null; - - var svg = d3.select(el[0]).append('svg') - .attr('width', attrs.width) - .attr('height', attrs.height); - - var update = function(data) { - if (typeof data === 'undefined') { - return; - } - - chart = nv.models.lineChart() - .margin({ left: 50, right: 50 }) - .useInteractiveGuideline(true); - - chart.tooltip.gravity('s').chartContainer(el[0]); - - chart.xAxis.tickFormat(function(d) { return d3.time.format('%m/%d %H:%M')(new Date(d)); }); - - if (attrs.forceY) { - chart.forceY(angular.fromJson(attrs.forceY)); - } - if (attrs.tickFormatX) { - chart.yAxis.tickFormat(d3.format(attrs.tickFormatX)); - } - if (attrs.tickFormatY) { - chart.yAxis.tickFormat(d3.format(attrs.tickFormatY)); - } - - svg.datum(data).call(chart); - }; - - scope.$on('windowResize', function() { - if (chart !== null) { - chart.update(); - } - }); - - scope.$watch('data', update); - }; - - return { - restrict: 'EA', - scope: { - 'data': '=', - 'width': '@', - 'height': '@' - }, - link: link - }; -} - -directivesModule.directive('chartLine', chartLine); diff --git a/app/views/grouped-runs.html b/app/views/grouped-runs.html index 26665a2a..a060fe77 100644 --- a/app/views/grouped-runs.html +++ b/app/views/grouped-runs.html @@ -31,8 +31,32 @@