diff --git a/awx/ui/static/js/app.js b/awx/ui/static/js/app.js index c1cf9dd844..a777d252c3 100644 --- a/awx/ui/static/js/app.js +++ b/awx/ui/static/js/app.js @@ -400,12 +400,12 @@ angular.module('Tower', [ templateUrl: urlPrefix + 'partials/home.html', controller: 'Home', resolve: { - graphData: function($q, jobStatusGraphData, hostCountGraphData) { - return $q.all({ - jobStatus: jobStatusGraphData.get("month", "all"), - hostCounts: hostCountGraphData.get() - }); - } + graphData: function($q, jobStatusGraphData, hostCountGraphData) { + return $q.all({ + jobStatus: jobStatusGraphData.get("month", "all"), + hostCounts: hostCountGraphData.get() + }); + } } }). diff --git a/awx/ui/static/js/controllers/Home.js b/awx/ui/static/js/controllers/Home.js index ab7da2e3d3..8ecb0fcb81 100644 --- a/awx/ui/static/js/controllers/Home.js +++ b/awx/ui/static/js/controllers/Home.js @@ -30,7 +30,7 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, ClearScope('home'); - var buttons, html, e, borderStyles, jobs_scope, schedule_scope; + var buttons, html, e, borderStyles; // Add buttons to the top of the Home page. We're using lib/ansible/generator_helpers.js-> Buttons() // to build buttons dynamically and insure all styling and icons match the rest of the application. @@ -111,13 +111,13 @@ function Home($scope, $compile, $routeParams, $rootScope, $location, $log, Wait, Wait('start'); Rest.setUrl(GetBasePath('dashboard')); Rest.get() - .success(function (data) { - $scope.dashboardData = data; - $scope.$emit('dashboardReady', data); - }) - .error(function (data, status) { - ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); - }); + .success(function (data) { + $scope.dashboardData = data; + $scope.$emit('dashboardReady', data); + }) + .error(function (data, status) { + ProcessErrors($scope, data, status, null, { hdr: 'Error!', msg: 'Failed to get dashboard: ' + status }); + }); }; $scope.refresh(); diff --git a/awx/ui/static/js/directives/auto-size-module.js b/awx/ui/static/js/directives/auto-size-module.js index eee8e6b3e8..2df92c53fd 100644 --- a/awx/ui/static/js/directives/auto-size-module.js +++ b/awx/ui/static/js/directives/auto-size-module.js @@ -1,52 +1,52 @@ angular.module('DashboardGraphs') - .directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) { +.directive('autoSizeModule', ['$window', '$timeout', function($window, $timeout) { // Adjusts the size of the module so that all modules // fit into a single a page; assumes there are 2 rows // of modules, with the available height being offset // by the navbar & the count summaries module - return function(scope, element, attr) { + return function(scope, element) { - // We need to trigger a resize on the first call - // to this when the view things load; but we don't want - // to trigger a global window resize for everything that - // has an auto resize, since they'll all pick it up with - // a single call - var triggerResize = - _.throttle(function() { - $($window).resize(); + // We need to trigger a resize on the first call + // to this when the view things load; but we don't want + // to trigger a global window resize for everything that + // has an auto resize, since they'll all pick it up with + // a single call + var triggerResize = + _.throttle(function() { + $($window).resize(); }, 1000); - function adjustSizeInitially() { - adjustSize(); - triggerResize(); - } + function adjustSizeInitially() { + adjustSize(); + triggerResize(); + } - function adjustSize() { - var winHeight = $($window).height(), - available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; - element.height(available_height/2); - } + function adjustSize() { + var winHeight = $($window).height(), + available_height = winHeight - $('#main-menu-container .navbar').outerHeight() - $('#count-container').outerHeight() - 120; + element.height(available_height/2); + } - $($window).resize(adjustSize); + $($window).resize(adjustSize); - element.on('$destroy', function() { - $($window).off('resize', adjustSize); - }); + element.on('$destroy', function() { + $($window).off('resize', adjustSize); + }); - // Wait a second or until dashboardReady triggers, - // whichever comes first. The timeout handles cases - // where dashboardReady never fires. + // Wait a second or until dashboardReady triggers, + // whichever comes first. The timeout handles cases + // where dashboardReady never fires. - var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500); + var dashboardReadyTimeout = $timeout(adjustSizeInitially, 500); - // This makes sure count-container div is loaded - // by controllers/Home.js before we use it - // to determine the available window height - scope.$on('dashboardReady', function() { - $timeout.cancel(dashboardReadyTimeout); - adjustSizeInitially(); - }); + // This makes sure count-container div is loaded + // by controllers/Home.js before we use it + // to determine the available window height + scope.$on('dashboardReady', function() { + $timeout.cancel(dashboardReadyTimeout); + adjustSizeInitially(); + }); }; diff --git a/awx/ui/static/js/directives/host-count-graph.js b/awx/ui/static/js/directives/host-count-graph.js index 4e50836557..bffb5f8c76 100644 --- a/awx/ui/static/js/directives/host-count-graph.js +++ b/awx/ui/static/js/directives/host-count-graph.js @@ -1,116 +1,126 @@ angular.module('DashboardGraphs'). - directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { + directive('hostCountGraph', ['GetBasePath', 'Rest', 'adjustGraphSize', '$window', function(getBasePath, Rest, adjustGraphSize, $window) { - return { - restrict: 'E', - templateUrl: '/static/partials/host_count_graph.html', - link: link - }; + return { + restrict: 'E', + templateUrl: '/static/partials/host_count_graph.html', + link: link + }; - function link(scope, element, attr) { - var url, license, license_graph; + function link(scope, element, attr) { + var license_graph; - scope.$watch(attr.data, function(data) { - if(!data) return; - createGraph(data.hosts, data.license); - }); + scope.$watch(attr.data, function(data) { - function onResize() { - if(!license_graph) return; - adjustGraphSize(license_graph, element); - } + if(!data) { + return; + } - angular.element($window).on('resize', function(e) { - if(!license_graph) return; - adjustGraphSize(license_graph, element); - }); + createGraph(data.hosts, data.license); + }); - element.on('$destroy', function() { - angular.element($window).off('resize', onResize); - }); + function onResize() { + if(!license_graph) { + return; + } - - function createGraph(data, license) { - //url = getBasePath('dashboard')+'graphs/'; - var graphData = [ - { - "key" : "Hosts" , - "color" : "#1778c3", - "values": data.hosts - }, - { - "key" : "License" , - "color" : "#171717", - "values": data.hosts - } - ]; - - graphData.map(function(series) { - if(series.key==="Hosts"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); + adjustGraphSize(license_graph, element); } - if(series.key==="License"){ - series.values = series.values.map(function(d) { - return { - x: d[0], - y: license - }; - }); + + angular.element($window).on('resize', function() { + + if(!license_graph) { + return; + } + + adjustGraphSize(license_graph, element); + }); + + element.on('$destroy', function() { + angular.element($window).off('resize', onResize); + }); + + + + function createGraph(data, license) { + //url = getBasePath('dashboard')+'graphs/'; + var graphData = [ + { "key" : "Hosts" , + "color" : "#1778c3", + "values": data.hosts + }, + { "key" : "License" , + "color" : "#171717", + "values": data.hosts + } + ]; + + graphData.map(function(series) { + if(series.key==="Hosts"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + } + if(series.key==="License"){ + series.values = series.values.map(function(d) { + return { + x: d[0], + y: license + }; + }); + + } + return series; + + }); + + var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, + height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, + license_graph = nv.models.lineChart() + .margin({top: 15, right: 75, bottom: 40, left: 85}) + .x(function(d,i) { return i ;}) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .transitionDuration(350) //how fast do you want the lines to transition? + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true) //Show the x-axis + ; + + license_graph.xAxis + .axisLabel("Time") + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; + }); + + license_graph.yAxis //Chart y-axis settings + .axisLabel('Hosts') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData).transition() + .attr('width', width) + .attr('height', height) + .duration(500) + .call(license_graph) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + + scope.$emit('WidgetLoaded'); + + adjustGraphSize(license_graph, element); + + return license_graph; } - return series; - - }); - - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.6; //nv.utils.windowSize().height/5, - license_graph = nv.models.lineChart() - .margin({top: 15, right: 75, bottom: 40, left: 85}) - .x(function(d,i) { return i ;}) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .transitionDuration(350) //how fast do you want the lines to transition? - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true) //Show the x-axis - ; - - license_graph.xAxis - .axisLabel("Time") - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format('%m/%d')(new Date(Number(dx+'000'))) : ''; - }); - - license_graph.yAxis //Chart y-axis settings - .axisLabel('Hosts') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData).transition() - .attr('width', width) - .attr('height', height) - .duration(500) - .call(license_graph) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - - scope.$emit('WidgetLoaded'); - - adjustGraphSize(license_graph, element); - - return license_graph; - } - } }]); diff --git a/awx/ui/static/js/directives/host-status-graph.js b/awx/ui/static/js/directives/host-status-graph.js index e5d5accccf..11b0dbf227 100644 --- a/awx/ui/static/js/directives/host-status-graph.js +++ b/awx/ui/static/js/directives/host-status-graph.js @@ -1,106 +1,102 @@ angular.module('DashboardGraphs') .directive('hostStatusGraph', ['$compile', '$window', function ($compile, $window) { - return { - restrict: 'E', - link: link, - templateUrl: '/static/partials/host_status_graph.html' - }; + return { + restrict: 'E', + link: link, + templateUrl: '/static/partials/host_status_graph.html' + }; - function link(scope, element, attr) { - var html, canvas, context, winHeight, available_height, host_pie_chart; + function link(scope, element, attr) { + var host_pie_chart; - scope.$watch(attr.data, function(data) { - if (data && data.hosts) { - createGraph(data); - } - }); + scope.$watch(attr.data, function(data) { + if (data && data.hosts) { + createGraph(data); + } + }); - function adjustGraphSize() { + function adjustGraphSize() { - if (angular.isUndefined(host_pie_chart)) { - return; - } + if (angular.isUndefined(host_pie_chart)) { + return; + } - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = host_pie_chart.margin(); + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = host_pie_chart.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; + var newHeight = parentHeight - toolbarHeight - margins.bottom; - $(container).height(newHeight); + $(container).height(newHeight); - host_pie_chart.update(); - } - - angular.element($window).on('resize', adjustGraphSize); - - element.on('$destroy', function() { - angular.element($window).off('resize', adjustGraphSize); - }); - - function createGraph(data) { - if(data.hosts.total+data.hosts.failed>0){ - data = [ - { - "label": "Successful", - "color": "#00aa00", - "value" : data.hosts.total - } , - { - "label": "Failed", - "color" : "#aa0000", - "value" : data.hosts.failed + host_pie_chart.update(); } - ]; - var width = $('.graph-container').width(), // nv.utils.windowSize().width/3, - height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - host_pie_chart = nv.models.pieChart() - .margin({top: 5, right: 75, bottom: 25, left: 85}) - .x(function(d) { return d.label; }) - .y(function(d) { return d.value; }) - .showLabels(true) - .labelThreshold(0.01) - .tooltipContent(function(x, y) { - return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; - }) - .color(['#00aa00', '#aa0000']); + angular.element($window).on('resize', adjustGraphSize); - host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); - - d3.select(element.find('svg')[0]) - .datum(data) - .transition().duration(350) - .call(host_pie_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" + element.on('$destroy', function() { + angular.element($window).off('resize', adjustGraphSize); }); - adjustGraphSize(); - return host_pie_chart; - } - else{ - // This should go in a template or something - // but I'm at the end of a card and need to get this done. - // We definitely need to refactor this, I'm letting - // good enough be good enough for right now. - var notFoundContainer = $('
'); - notFoundContainer.css({ - 'text-align': 'center', - 'width': '100%', - 'padding-top': '2em' - }); + function createGraph(data) { + if(data.hosts.total+data.hosts.failed>0){ + data = [ + { "label": "Successful", + "color": "#00aa00", + "value" : data.hosts.total + } , + { "label": "Failed", + "color" : "#aa0000", + "value" : data.hosts.failed + } + ]; - notFoundContainer.text('No host data'); + host_pie_chart = nv.models.pieChart() + .margin({top: 5, right: 75, bottom: 25, left: 85}) + .x(function(d) { return d.label; }) + .y(function(d) { return d.value; }) + .showLabels(true) + .labelThreshold(0.01) + .tooltipContent(function(x, y) { + return ''+x+''+ '

' + Math.floor(y.replace(',','')) + ' Hosts ' + '

'; + }) + .color(['#00aa00', '#aa0000']); - element.find('svg').replaceWith(notFoundContainer); - } + host_pie_chart.pie.pieLabelsOutside(true).labelType("percent"); + d3.select(element.find('svg')[0]) + .datum(data) + .transition().duration(350) + .call(host_pie_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + adjustGraphSize(); + return host_pie_chart; + } + else{ + // This should go in a template or something + // but I'm at the end of a card and need to get this done. + // We definitely need to refactor this, I'm letting + // good enough be good enough for right now. + var notFoundContainer = $('
'); + notFoundContainer.css({ + 'text-align': 'center', + 'width': '100%', + 'padding-top': '2em' + }); + + notFoundContainer.text('No host data'); + + element.find('svg').replaceWith(notFoundContainer); + } + + } } - } - }]); + }]); diff --git a/awx/ui/static/js/directives/job-status-graph.js b/awx/ui/static/js/directives/job-status-graph.js index 1d5f6f3827..3cf6696bf2 100644 --- a/awx/ui/static/js/directives/job-status-graph.js +++ b/awx/ui/static/js/directives/job-status-graph.js @@ -1,129 +1,120 @@ angular.module('DashboardGraphs') .directive('jobStatusGraph', ['$rootScope', '$compile', '$location' , '$window', 'Wait', 'adjustGraphSize', 'jobStatusGraphData', - function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize, jobStatusGraphData) { + function ($rootScope, $compile , $location, $window, Wait, adjustGraphSize) { return { - restrict: 'E', - templateUrl: '/static/partials/job_status_graph.html', - link: link + restrict: 'E', + templateUrl: '/static/partials/job_status_graph.html', + link: link }; - function link(scope, element, attr) { - var html; - var url; - var job_status_chart = nv.models.lineChart(); - var cleanup = angular.noop; + function link(scope, element, attr) { + var job_type, job_status_chart = nv.models.lineChart(); - scope.period="month"; - scope.jobType="all"; + scope.period="month"; + scope.jobType="all"; - var data; - scope.$watch(attr.data, function(value) { - if (value) { - createGraph(value, scope.period, scope.jobType); - } - }); + scope.$watch(attr.data, function(value) { + if (value) { + createGraph(value, scope.period, scope.jobType); + } + }); - function createGraph(data, period, jobtype){ + function createGraph(data, period, jobtype){ - scope.period = period; - scope.jobType = jobtype; + scope.period = period; + scope.jobType = jobtype; - var timeFormat, graphData = [ - { - "color": "#00aa00", - "key": "Successful", - "values": data.jobs.successful - }, - { - "key" : "Failed" , - "color" : "#aa0000", - "values": data.jobs.failed + var timeFormat, graphData = [ + { "color": "#00aa00", + "key": "Successful", + "values": data.jobs.successful + }, + { "key" : "Failed" , + "color" : "#aa0000", + "values": data.jobs.failed + } + ]; + + if(period==="day") { + timeFormat="%H:%M"; + } + else { + timeFormat = '%m/%d'; + } + graphData.map(function(series) { + series.values = series.values.map(function(d) { + return { + x: d[0], + y: d[1] + }; + }); + return series; + }); + + job_status_chart + .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. + .x(function(d,i) { return i; }) + .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! + .showLegend(true) //Show the legend, allowing users to turn on/off line series. + .showYAxis(true) //Show the y-axis + .showXAxis(true); //Show the x-axis + + + job_status_chart.xAxis + .axisLabel("Time")//.showMaxMin(true) + .tickFormat(function(d) { + var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; + return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; + }); + + job_status_chart.yAxis //Chart y-axis settings + .axisLabel('Jobs') + .tickFormat(d3.format('.f')); + + d3.select(element.find('svg')[0]) + .datum(graphData) + .call(job_status_chart) + .style({ + "font-family": 'Open Sans', + "font-style": "normal", + "font-weight":400, + "src": "url(/static/fonts/OpenSans-Regular.ttf)" + }); + + // when the Period drop down filter is used, create a new graph based on the + d3.selectAll(element.find(".n")) + .on("click", function() { + period = this.getAttribute("id"); + $('#period-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + //On click, update with new data + d3.selectAll(element.find(".m")) + .on("click", function() { + job_type = this.getAttribute("id"); + $('#type-dropdown').replaceWith(""+this.text+"\n"); + + createGraph(data, period, job_type); + }); + + adjustGraphSize(job_status_chart, element); } - ]; - if(period==="day") { - timeFormat="%H:%M"; + function onResize() { + adjustGraphSize(job_status_chart, element); } - else { - timeFormat = '%m/%d'; + + angular.element($window).on('resize', onResize); + + element.on('$destroy', function() { + angular.element($window).off('resize', onResize); + }); + + if (scope.removeGraphDataReady) { + scope.removeGraphDataReady(); } - graphData.map(function(series) { - series.values = series.values.map(function(d) { - return { - x: d[0], - y: d[1] - }; - }); - return series; - }); - job_status_chart - .margin({top: 5, right: 75, bottom: 40, left: 85}) //Adjust chart margins to give the x-axis some breathing room. - .x(function(d,i) { return i; }) - .useInteractiveGuideline(true) //We want nice looking tooltips and a guideline! - .showLegend(true) //Show the legend, allowing users to turn on/off line series. - .showYAxis(true) //Show the y-axis - .showXAxis(true); //Show the x-axis - - - var width = $('.graph-container').width(); // nv.utils.windowSize().width/3, - var height = $('.graph-container').height()*0.7; //nv.utils.windowSize().height/5, - - job_status_chart.xAxis - .axisLabel("Time")//.showMaxMin(true) - .tickFormat(function(d) { - var dx = graphData[0].values[d] && graphData[0].values[d].x || 0; - return dx ? d3.time.format(timeFormat)(new Date(Number(dx+'000'))) : ''; - }); - - job_status_chart.yAxis //Chart y-axis settings - .axisLabel('Jobs') - .tickFormat(d3.format('.f')); - - d3.select(element.find('svg')[0]) - .datum(graphData) - .call(job_status_chart) - .style({ - "font-family": 'Open Sans', - "font-style": "normal", - "font-weight":400, - "src": "url(/static/fonts/OpenSans-Regular.ttf)" - }); - - // when the Period drop down filter is used, create a new graph based on the - d3.selectAll(element.find(".n")) - .on("click", function() { - period = this.getAttribute("id"); - $('#period-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(data, period, job_type); - }); - - //On click, update with new data - d3.selectAll(element.find(".m")) - .on("click", function() { - job_type = this.getAttribute("id"); - $('#type-dropdown').replaceWith(""+this.text+"\n"); - - createGraph(data, period, job_type); - }); - - adjustGraphSize(job_status_chart, element); } - - function onResize() { - adjustGraphSize(job_status_chart, element); - } - - angular.element($window).on('resize', onResize); - - element.on('$destroy', function() { - angular.element($window).off('resize', onResize); - }); - - if (scope.removeGraphDataReady) { - scope.removeGraphDataReady(); - } - - } }]); diff --git a/awx/ui/static/js/services/adjust-graph-size.js b/awx/ui/static/js/services/adjust-graph-size.js index ed973bbb23..e447f94b38 100644 --- a/awx/ui/static/js/services/adjust-graph-size.js +++ b/awx/ui/static/js/services/adjust-graph-size.js @@ -1,90 +1,90 @@ angular.module('DashboardGraphs'). - factory('adjustGraphSize', function() { + factory('adjustGraphSize', function() { - // Adjusts the size of graphs based on the current height - // of the outer parent (see auto-size-module directive). - // - // Since the graph's svg element is set to width & height of 100%, - // it will automatically size itself when the size of its container - // changes. Since boxes in HTML automatically fill the width of their - // parent, we don't have to change the container's width. However, - // since the makers HTML never heard of vertical rhythm, - // we have to manually set a new height on the container. - // - // ## Calculating the container's new height - // - // newHeight is the height we assign to the graph's immediate parent. - // This is calculated as the height of the graph-container (the - // outer parent), offset by the height of the toolbar row - // (the contains the title and/or any filters) and the - // bottom margin. - // - // ## Responsive Graph Stuff - // - // Letting the svg element automatically scale only solves part of - // the responsive graph problem. d3 draws graphs as paths, with static - // positioning of all elements. Therefore, we need to tell the graph how - // to adjust itself so that it can resize properly. - // - // ### Resizing the axes - // - // First we get the width & height of the chart after it has been modified - // by setting the height on its parent (see Calculating the New Container's - // Height above). Note that we need to offset the width/height by the margins - // to make sure we keep all the spacing intact. - // - // Next, we update the range for x & y to take the new width & height into - // account. d3 uses this range to map domain values (the actual data) onto - // pixels. - // - // After that we adjust the number of ticks on the axes. This makes sure we - // will never have overlapping ticks. If that does become a problem, try - // changing the divisor in the calculations to a different number until you - // find something that helps. For example, (width / 75) should make the x - // axis only ever display 1 tick per every 75 pixels. - // - // ### Redrawing the line - // - // Since this is a line graph, now that we've changed the range & ticks, - // we need to instruct d3 to repaint (redraw) the actual lines representing - // the data. We do this by setting the "d" attribute of the path element - // that represents the line to the line function on the chart model. This - // function triggers the mapping of domain to range, and plots the chart. - // Calling chartModel.update() at the end instructs nv to process our changes. - // + // Adjusts the size of graphs based on the current height + // of the outer parent (see auto-size-module directive). + // + // Since the graph's svg element is set to width & height of 100%, + // it will automatically size itself when the size of its container + // changes. Since boxes in HTML automatically fill the width of their + // parent, we don't have to change the container's width. However, + // since the makers HTML never heard of vertical rhythm, + // we have to manually set a new height on the container. + // + // ## Calculating the container's new height + // + // newHeight is the height we assign to the graph's immediate parent. + // This is calculated as the height of the graph-container (the + // outer parent), offset by the height of the toolbar row + // (the contains the title and/or any filters) and the + // bottom margin. + // + // ## Responsive Graph Stuff + // + // Letting the svg element automatically scale only solves part of + // the responsive graph problem. d3 draws graphs as paths, with static + // positioning of all elements. Therefore, we need to tell the graph how + // to adjust itself so that it can resize properly. + // + // ### Resizing the axes + // + // First we get the width & height of the chart after it has been modified + // by setting the height on its parent (see Calculating the New Container's + // Height above). Note that we need to offset the width/height by the margins + // to make sure we keep all the spacing intact. + // + // Next, we update the range for x & y to take the new width & height into + // account. d3 uses this range to map domain values (the actual data) onto + // pixels. + // + // After that we adjust the number of ticks on the axes. This makes sure we + // will never have overlapping ticks. If that does become a problem, try + // changing the divisor in the calculations to a different number until you + // find something that helps. For example, (width / 75) should make the x + // axis only ever display 1 tick per every 75 pixels. + // + // ### Redrawing the line + // + // Since this is a line graph, now that we've changed the range & ticks, + // we need to instruct d3 to repaint (redraw) the actual lines representing + // the data. We do this by setting the "d" attribute of the path element + // that represents the line to the line function on the chart model. This + // function triggers the mapping of domain to range, and plots the chart. + // Calling chartModel.update() at the end instructs nv to process our changes. + // return function adjustGraphSize(chartModel, element) { - var parentHeight = element.parent().parent().height(); - var toolbarHeight = element.find('.toolbar').height(); - var container = element.find('svg').parent(); - var margins = chartModel.margin(); + var parentHeight = element.parent().parent().height(); + var toolbarHeight = element.find('.toolbar').height(); + var container = element.find('svg').parent(); + var margins = chartModel.margin(); - var newHeight = parentHeight - toolbarHeight - margins.bottom; + var newHeight = parentHeight - toolbarHeight - margins.bottom; - $(container).height(newHeight); + $(container).height(newHeight); - var graph = d3.select(element.find('svg')[0]); - var width = parseInt(graph.style('width')) - margins.left - margins.right; - var height = parseInt(graph.style('height')) - margins.top - margins.bottom; + var graph = d3.select(element.find('svg')[0]); + var width = parseInt(graph.style('width')) - margins.left - margins.right; + var height = parseInt(graph.style('height')) - margins.top - margins.bottom; - chartModel.xRange([0, width]); - chartModel.yRange([height, 0]); + chartModel.xRange([0, width]); + chartModel.yRange([height, 0]); - chartModel.xAxis.ticks(Math.max(width / 75, 2)); - chartModel.yAxis.ticks(Math.max(height / 50, 2)); + chartModel.xAxis.ticks(Math.max(width / 75, 2)); + chartModel.yAxis.ticks(Math.max(height / 50, 2)); - if (height < 160) { - graph.select('.y.nv-axis').select('.domain').style('display', 'none'); - graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); - } + if (height < 160) { + graph.select('.y.nv-axis').select('.domain').style('display', 'none'); + graph.select('.y.nv-axis').select('.domain').style('display', 'initial'); + } - graph.select('.x.nv-axis') + graph.select('.x.nv-axis') .attr('transform', 'translate(0, ' + height + ')') .call(chartModel.xAxis); - graph.selectAll('.line') - .attr('d', chartModel.lines) + graph.selectAll('.line') + .attr('d', chartModel.lines); - chartModel.update(); - } + chartModel.update(); + }; }); diff --git a/awx/ui/static/js/services/host-count-graph-data.js b/awx/ui/static/js/services/host-count-graph-data.js index e81fd0fe14..4bc1f817c3 100644 --- a/awx/ui/static/js/services/host-count-graph-data.js +++ b/awx/ui/static/js/services/host-count-graph-data.js @@ -1,47 +1,47 @@ angular.module('DataServices') - .service('hostCountGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$q", - HostCountGraphData]); +.service('hostCountGraphData', + ["Rest", + "GetBasePath", + "ProcessErrors", + "$q", + HostCountGraphData]); function HostCountGraphData(Rest, getBasePath, processErrors, $q) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } - - function getLicenseData() { - url = getBasePath('config'); - Rest.setUrl(url); - return Rest.get() - .then(function (data){ - license = data.data.license_info.instance_count; - return license; - }) - } - - function getHostData() { - url = getBasePath('dashboard')+'graphs/inventory/'; - Rest.setUrl(url); - return pluck('data', Rest.get()); - } - - return { - get: function() { - return $q.all({ - license: getLicenseData(), - hosts: getHostData() - }).catch(function (response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - processErrors(null, response.data, response.status, null, { hdr: 'Error!', - msg: errorMessage - }); - return response; + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; }); } - }; + + function getLicenseData() { + var url = getBasePath('config'); + Rest.setUrl(url); + return Rest.get() + .then(function (data){ + var license = data.data.license_info.instance_count; + return license; + }); + } + + function getHostData() { + var url = getBasePath('dashboard')+'graphs/inventory/'; + Rest.setUrl(url); + return pluck('data', Rest.get()); + } + + return { + get: function() { + return $q.all({ + license: getLicenseData(), + hosts: getHostData() + }).catch(function (response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + processErrors(null, response.data, response.status, null, { hdr: 'Error!', + msg: errorMessage + }); + return response; + }); + } + }; } diff --git a/awx/ui/static/js/services/job-status-graph-data.js b/awx/ui/static/js/services/job-status-graph-data.js index b29c81456d..242368fc0f 100644 --- a/awx/ui/static/js/services/job-status-graph-data.js +++ b/awx/ui/static/js/services/job-status-graph-data.js @@ -1,62 +1,59 @@ angular.module('DataServices') - .service('jobStatusGraphData', - ["Rest", - "GetBasePath", - "ProcessErrors", - "$rootScope", - "$q", - JobStatusGraphData]); +.service('jobStatusGraphData', + ["Rest", + "GetBasePath", + "ProcessErrors", + "$rootScope", + JobStatusGraphData]); -function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope, $q) { - var callbacks = {}; - var currentCallbackId = 0; +function JobStatusGraphData(Rest, getBasePath, processErrors, $rootScope) { - function pluck(property, promise) { - return promise.then(function(value) { - return value[property]; - }); - } + function pluck(property, promise) { + return promise.then(function(value) { + return value[property]; + }); + } - function getData(period, jobType) { - var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; - Rest.setUrl(url); - var result = Rest.get() - .catch(function(response) { - var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; + function getData(period, jobType) { + var url = getBasePath('dashboard')+'graphs/jobs/?period='+period+'&job_type='+jobType; + Rest.setUrl(url); + var result = Rest.get() + .catch(function(response) { + var errorMessage = 'Failed to get: ' + response.url + ' GET returned: ' + response.status; - processErrors(null, - response.data, - response.status, - null, { - hdr: 'Error!', - msg: errorMessage - }); - return response; - }); + processErrors(null, + response.data, + response.status, + null, { + hdr: 'Error!', + msg: errorMessage + }); + return response; + }); - return pluck('data', result); - } + return pluck('data', result); + } - return { - destroyWatcher: angular.noop, - setupWatcher: function(period, jobType) { - this.destroyWatcher = - $rootScope.$on('JobStatusChange', function() { - getData(period, jobType).then(function(result) { - $rootScope. - $broadcast('DataReceived:JobStatusGraph', - result); - return result; - }); - }); - }, - get: function(period, jobType) { + return { + destroyWatcher: angular.noop, + setupWatcher: function(period, jobType) { + this.destroyWatcher = + $rootScope.$on('JobStatusChange', function() { + getData(period, jobType).then(function(result) { + $rootScope. + $broadcast('DataReceived:JobStatusGraph', + result); + return result; + }); + }); + }, + get: function(period, jobType) { - this.destroyWatcher(); - this.setupWatcher(period, jobType); + this.destroyWatcher(); + this.setupWatcher(period, jobType); - return getData(period, jobType); + return getData(period, jobType); - } - }; + } + }; }