/** * Exposes the AngularJS code of the <app-metric> directive which * displays a exploratory graph. Uses a derivative of the AppChartController * controller/module to place an AppChart object in the element reserved by the * directive. * @module AppMetricDirective * @exports AppMetricDirective */ define(["AppHelper", "AppChartController", "AppConstants"], function(AppHelper, AppChartController, AppConstants) { /** * Sets up a controller to glue together the generic AppChart chart drawing * component with the <app-metric> AngularJS directive & scope. Enhances * the AppChartController by some functionality like linking together the x- * axis of various graphs, populating selected values to other directives * listening to seriesesValuesChanged events etc. * @constructor * @extends module:AppChartController~AppChartController * @param $scope the AngularJS scope of the directive where the chart should * be placed. * @param $element the DOM element of the directive * @param ctrlName the name of the controller in the angularjs scope */ AppMetricController = function($scope, $element, ctrlName) { // call super AppChartController.call(this, $scope, $element, ctrlName); // add handlers for listening for selection of different metrics, zooming/ // panning together on the x-axis and change of the "looseness" parameter. n = ctrlName; $scope.$watch(n + ".highlights.metricId", this.highlightsUpdatedHandler()); $scope.$watch(n + ".highlights.metricId", this.seriesesValuesUpdatedHandler()); $scope.$watch(n + ".highlights.x", this.seriesesValuesUpdatedHandler()); $scope.$watch(n + ".highlights.xRange", this.xRangeUpdatedHandler()); $scope.$watch(n + ".highlights.looseness", this.loosenessUpdatedHandler()); }; AppMetricController.prototype = Object.create(AppChartController.prototype); /** * Returns the factory function of the controller. The factory is used in * the directives .controller property. You have to specify some "options" * to the factory. The factory callback returned will accept exactly two * parameters: $scope and $element. * @static * @param options - see AppChartController.getFactory */ AppMetricController.getFactory = function(ctrlName) { return function($scope, $element) { return new AppMetricController($scope, $element, ctrlName); }; }; /** * This function must be called when the directive using this controller links * the directive to a DOM element. It takes care of actually drawing the * chart into the DOM element. * @param chartOptions the chart options to create the AppChart with * @param dataTransformer the data transform function to apply to the data * before the chart is drawn */ AppMetricController.prototype.link = function(dataTransformer, chartOptions) { // call super AppChartController.prototype.link.call(this, dataTransformer, chartOptions); // add handlers for mouse enter / exit to be able to react to mouse // hovering over one of the many metric graphs this.chart.attachMouseEnterHandler(this.metricHighlightedHandler()); this.chart.attachMouseLeaveHandler(this.metricUnhighlightedHandler()); }; /** * Returns a callback that can be invoked if the highlights object supplied * to the directive / controller is changed. Will delegate the highlight * change to the included AppChart object. */ AppMetricController.prototype.highlightsUpdatedHandler = function() { // extends the super handler but adds addtitional functionality of // highlighting / unhighlighting a complete metrics chart. var superHandler = AppChartController.prototype.highlightsUpdatedHandler.call(this); var self = this; return function() { superHandler(); if (self.chart) { var highlightThisChart = (self.data.id == self.highlights.metricId); self.chart.highlight({ thisChart: highlightThisChart }); } } }; /** * Returns a callback that can be invoked if a datapoint is selected. Will * update the highlights.seriesesValues object accordingly. */ AppMetricController.prototype.seriesesValuesUpdatedHandler = function() { var self = this; return function() { // did we highlight anything at all in any graph? anyPointHighlighted = self.highlights.x; // have we highlighted this graph or are we in another graph? thisMetricHighlighted = (self.highlights.metricId === self.data.id); if (!anyPointHighlighted) // we did not highlight anything at all - remove all highlighted points self.highlights.seriesesValues = {}; else { // find the point corresponding to (highlights.x, highlights.y) var serieses = self.data.serieses, values = self.chart.getNearest( self.highlights.x, self.highlights.y), currentValues = self.highlights.seriesesValues; newValues = {}; // create a "newValues" seriesesValues array with the content of the // old elements for (seriesId in currentValues) newValues[seriesId] = currentValues[seriesId]; // but overwrite all the values we obtained from this graph for (seriesId in values) newValues[seriesId] = values[seriesId]; // save this array of values into the highlights.seriesesValues object // this might actually happen multiple times until all metric graphs // had the chance to update their data points. self.highlights.seriesesValues = newValues; } }; }; /** * Returns a callback that should be invoked any time the x/y extent changes * because the user zooms or pans in the graph. Updates the highlights object * (xRange property) and the "looseness" property accordingly. */ AppMetricController.prototype.zoomedPannedHandler = function() { var superHandler = AppChartController.prototype.zoomedPannedHandler.call(this); var self = this; return function(minX, maxX, minY, maxY) { superHandler(minX, maxX, minY, maxY); self.$scope.$apply(function() { if (self.highlights.looseness < 3) self.highlights.looseness = 3; }); }; }; /** * Returns a callback that should be invoked any time a metric graph as a * whole is highlighted (mouseover'ed). Updates the highlights.metricId object * accordingly. */ AppMetricController.prototype.metricHighlightedHandler = function() { var self = this; return function() { self.$scope.$apply(function() { self.highlights.metricId = self.data.id; if (self.highlights.looseness < 1) self.highlights.looseness = 1; }); }; }; /** * Returns a callback that should be invoked any time a metric graph as a * whole is unhighlighted (mouseout'ed). Updates the highlights.metricId * object accordingly. */ AppMetricController.prototype.metricUnhighlightedHandler = function() { var self = this; return function() { self.$scope.$apply(function() { self.highlights.metricId = undefined; if (self.highlights.looseness < 1) self.highlights.looseness = 1; }); }; }; /** * The <app-metric> directive. Supports the following attributes: <ul> * <li>highlights '=' - the highlights object representing the internal * visualization status</li> * <li>metric '=' - the data object to visualize</li> * </ul> * @constructor */ AppMetricDirective = function() { this.require = ["^^appMetrics"]; this.restrict = "E"; this.templateUrl = "templates/AppMetric.html"; this.scope = true; this.bindToController = { data: "=metric", highlights: "=" }; this.controllerAs = "appMetric"; // create the controller based on the AppMetricController class this.controller = ["$scope", "$element", AppMetricController.getFactory(this.controllerAs)]; }; /** * Factory to create the directive; used as the angularjs.directive call. * @static */ AppMetricDirective.factory = function() { return new AppMetricDirective(); }; /** * The "link" function to be invoked when the directive is attached to an * element. Will take care that the controller will draw the chart using the * AppChart methods. */ AppMetricDirective.prototype.link = function( $scope, $element, $attrs, $controllers, $transclude ) { dataTransformer = AppHelper.getSeriesDataTransformer("time", "data"); chartOptions = { xlabel: "time", xunit: "s", ylabel: $scope.appMetric.data.label, yunit: $scope.appMetric.data.unit, colorMap: $scope.appMetric.data.dataColorMap, graphType: "line", baseDir: AppConstants.DATA_DIR }; $scope.appMetric.link(dataTransformer, chartOptions); }; return AppMetricDirective; });