Source: AppExploreController.js

/**
 * Exposes an AngularJS controller to retrieve the data for the exploratory
 * visualization and manage the interactive state of the visualization.
 * @module AppExplainController
 * @exports AppExplainController
 */
define(["AppConstants"], function(AppConstants) {

  /**
   * The controller retrieving the data for the exploratory visualization and
   * governing the interaction on this visualization.
   * @constructor
   */
  AppExploreController = function($scope, $http, $location) {
    // memorize $http and $location service
    this.$http = $http;
    this.$location = $location;
    // currently, we have no scenarios loaded and not scenarios selected
    this.scenarios = [];
    this.selectedScenario = undefined;
    this.dataDir = AppConstants.DATA_DIR;
    this.scenariosFile = AppConstants.SCENARIOS_FILE;
    // react everytime the selected scenarios, metrics or stories changed
    $scope.$watch("appExplore.selectedScenario",
        this.selectedScenarioChangedHandler());
    $scope.$watch("appExplore.selectedMetrics",
        this.selectedMetricsChangedHandler(), true); // needs deep comparison
    $scope.$watch("appExplore.selectedStory",
        this.selectedStoryChangedHandler());
    // react every time the URL search string changed
    $scope.$on("$locationChangeSuccess", this.processUrlHandler());
    // reset highlights: currently nothing is highlighted
    this.resetHighlights();
    // load scenarios
    this.loadScenarios(this.dataDir + "/" + this.scenariosFile);
  };

  /**
   * Factory to create the directive; used in the angular.directive call.
   * @static
   */
  AppExploreController.factory = function($scope, $http, $location) {
    return new AppExploreController($scope, $http, $location);
  };

  /**
   * Loads the scenario file containing all the selectable scenarios from a URL
   * supplied to the function.
   * @param scenarioUrl the URL to load the scenarios file from
   */
  AppExploreController.prototype.loadScenarios = function(scenarioUrl) {
    this.$http.get(scenarioUrl).success(this.scenariosLoadedHandler());
  };

  /**
   * Loads a metrics file containing the metrics data from a URL
   * supplied to the function.
   * @param metricsUrl the URL to load the metrics file from
   */
  AppExploreController.prototype.loadMetrics = function(metricsUrl) {
    this.resetHighlights();
    this.$http.get(metricsUrl).success(this.metricsLoadedHandler());
  };

  /**
   * Loads a stories file containing the stories from a URL
   * supplied to the function.
   * @param storiesUrl the URL to load the stories file from
   */
  AppExploreController.prototype.loadStories = function(storiesUrl) {
    this.$http.get(storiesUrl).success(this.storiesLoadedHandler());
  };

  /**
   * Resets all "highlights" back to normal, that means, no part of the
   * visualization will be highlighted/marked/zoomed-in etc. after calling this
   * method.
   */
  AppExploreController.prototype.resetHighlights = function() {
    if (!this.highlights) this.highlights = {};
    this.highlights.seriesId = undefined;
    this.highlights.metric = undefined;
    this.highlights.x = undefined;
    this.highlights.xRange = [undefined, undefined];
    this.highlights.looseness = 0;
    this.highlights.seriesesValues = {};
  };

  /**
   * Helper function to select a number of metrics from the loaded metrics
   * by its 'id' property. Can be used, e.g., if a storybox needs to display
   * certain metric graphs based on the story's 'metrics' property.
   * @param metricsIds an array of metric ids
   */
  AppExploreController.prototype.selectMetricsById = function(metricsIds) {
    this.selectedMetrics = [];
    for (i in this.metrics) {
      if (metricsIds.indexOf(this.metrics[i].id) != -1)
        this.selectedMetrics.push(this.metrics[i])
    }
  };

  /**
   * Returns a callback to be invoked with the JSON data as parameter after a
   * metrics JSON file is loaded.
   */
  AppExploreController.prototype.metricsLoadedHandler = function() {
    var self = this;
    return function(data) {
      self.metrics = data;
      // load stories, if stories are associated with this scenario
      if (self.selectedScenario.storiesFile)
        self.loadStories(self.dataDir + "/" +
            self.selectedScenario.storiesFile);
    };
  };

  /**
   * Returns a callback to be invoked with the JSON data as parameter after a
   * stories JSON file is loaded.
   */
  AppExploreController.prototype.storiesLoadedHandler = function() {
    var self = this;
    return function(data) {
      self.stories = data;
      if (data.length > 0) {
        self.selectedStory = data[0];
        self.highlights.looseness = 0;
      }
      self.processUrlStoryId();
    };
  };

  /**
   * Returns a callback to be invoked with the JSON data as parameter after the
   * scenarios JSON file is loaded.
   */
  AppExploreController.prototype.scenariosLoadedHandler = function() {
    var self = this;
    return function(data) {
      self.scenarios = data;
      self.processUrlScenarioId();
    };
  };

  /**
   * Returns a callback that should be invoked every time the user changes the
   * selected scenario. The callback updates the URL's search string and loads
   * all metrics associated with the scenario.
   */
  AppExploreController.prototype.selectedScenarioChangedHandler = function() {
    var self = this;
    return function() {
      // reset metrics and stories
      self.metrics = undefined;
      self.stories = undefined;
      self.selectedMetrics = [];
      self.serieses = [];
      // reset highlights
      self.resetHighlights();
      // if we actually do have a scenario selected
      if (self.selectedScenario !== undefined) {
        // ... update the URL's search string
        self.$location.search("scenarioId", self.selectedScenario.id);
        // load metrics
        self.loadMetrics(self.dataDir + "/" + self.selectedScenario.dataFile);
      }
    };
  };

  /**
   * Returns a callback that should be invoked every time the user changes the
   * selected story. The callback updates the URL's search string to contain
   * a reference to that story.
   */
  AppExploreController.prototype.selectedStoryChangedHandler = function() {
    var self = this;
    return function() {
      if (self.selectedStory !== undefined)
        self.$location.search("storyId", self.selectedStory.id);
    }
  };

  /**
   * Returns a callback that should be invoked every time the user changes the
   * selected metrics. The callback updates an internal array "serieses"
   * that contains all the serieses of all the metrics.
   */
  AppExploreController.prototype.selectedMetricsChangedHandler = function() {
    var self = this;
    return function() {
      self.serieses = [];
      for (i in self.selectedMetrics)
        for (j in self.selectedMetrics[i].serieses)
          self.serieses.push(self.selectedMetrics[i].serieses[j])
    }
  };

  /**
   * Returns a callback that should be invoked every time the URL's search
   * string changes. The callback processes the URL-parameters 'scenarioId' and
   * 'storyId'.
   * @see module:AppExploreController~AppExploreController#processUrlScenarioId
   * @see module:AppExploreController~AppExploreController#processUrlStoryId
   */
  AppExploreController.prototype.processUrlHandler = function() {
    var self = this;
    return function($event, $args) {
      self.processUrlScenarioId();
      self.processUrlStoryId();
    }
  };

  /**
   * Updates the selected scenario based on the URL's search parameter
   * 'scenarioId'.
   */
  AppExploreController.prototype.processUrlScenarioId = function() {
    var scenarioId = this.$location.search().scenarioId,
        scenario = undefined;
    for (i in this.scenarios)
      if (this.scenarios[i].id == scenarioId)
        scenario = this.scenarios[i];
    this.selectedScenario = scenario;
  };

  /**
   * Updates the selected story based on the URL's search parameter 'storyId'.
   */
  AppExploreController.prototype.processUrlStoryId = function() {
    var storyId = this.$location.search().storyId,
        story = undefined;
    if (this.stories && this.stories.length > 0) {
      for (i in this.stories)
        if (this.stories[i].id == storyId)
          story = this.stories[i];
      if (story === undefined && this.selectedStory === undefined)
        this.selectedStory = this.stories[0];
    }
  };

  return AppExploreController;

});