lib/providers/metrics-provider.js
function aggregateMetrics(currentTime, metricData) {
    var aggregateKey;

    /**
     * Place aggregate data into the specified slot.  If the current zoom
     * level matches the aggregate level, the data is emitted to keep the
     * display in sync.
     *
     * @param {Number} index
     * The desired slot for the aggregate.
     *
     * @param {Object} data
     * The aggregate data.
     *
     * @returns {void}
     */
    var setAggregateData =
      function setAggregateData(index, data) {
        this._aggregation[aggregateKey].data[index] = data;

        // if this view (current or not) is scrolled, adjust it
        if (this._aggregation[aggregateKey].scrollOffset) {
          this._aggregation[aggregateKey].scrollOffset--;
        }

        // emit to keep the display in sync
        if (this.isCurrentZoom(aggregateKey)) {
          if (this.isScrolled()) {
            this.emit("refreshMetrics");
          } else {
            this.emit("metrics", data);
          }
        }
      }.bind(this);

    /**
     * Given the current time time index, add any missing logical
     * time slots to have a complete picture of data.
     *
     * @param {Number} currentTimeIndex
     * The time index currently being processed.
     *
     * @returns {void}
     */
    var addMissingTimeSlots =
      function addMissingTimeSlots(currentTimeIndex) {
        var aggregateIndex = this._aggregation[aggregateKey].data.length;
        while (aggregateIndex < currentTimeIndex) {
          setAggregateData(aggregateIndex++, this.emptyAverage);
        }
      }.bind(this);

    /**
     * After having detected a new sampling, aggregate the relevant data points
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @param {Number} startIndex
     * The starting index to derive an average.
     *
     * @param {Number} endIndex
     * The ending index to derive an average.
     *
     * @returns {void}
     */
    var getAveragedAggregate =
      function getAveragedAggregate(rows, startIndex, endIndex) {
        var averagedAggregate = getInitializedAverage(metricData);

        // this is the number of elements we will aggregate
        var aggregateCount = endIndex - startIndex + 1;

        // you can compute an average of a set of numbers two ways
        // first, you can add all the numbers together and then divide by the count
        // second, you call divide each number by the count and add the quotients
        // the first method is more accurate, however you can overflow an accumulator
        // and result with NaN
        // the second method is employed here to ensure no overflows

        for (var dataKey in metricData) {
          for (var dataMetricKey in metricData[dataKey]) {
            for (var rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) {
              averagedAggregate[dataKey][dataMetricKey] +=
                rows[rowIndex][dataKey][dataMetricKey] / aggregateCount;
            }

            // after the average is done, truncate the averages to one decimal point
            averagedAggregate[dataKey][dataMetricKey] =
              +averagedAggregate[dataKey][dataMetricKey].toFixed(1);
          }
        }

        return averagedAggregate;
      };

    /**
     * Process one row of metric data into aggregate.
     *
     * @param {Number} rowIndex
     * The index of the row being processed.
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @returns {void}
     */
    var processRow = function processRow(rowIndex, rows) {
      var averagedAggregate;
      var lastTimeIndex = this._aggregation[aggregateKey].lastTimeIndex;

      // get the time index of the aggregate
      var currentTimeIndex =
        convertElapsedTimeToTimeIndex(currentTime, this._startTime, +aggregateKey);

      // when the time index changes, average the data for the time band
      if (currentTimeIndex !== lastTimeIndex) {
        // except for the first one
        if (lastTimeIndex !== undefined) {
          // add in any missing logical time slots
          addMissingTimeSlots.call(this, lastTimeIndex);

          // get the average across the discovered time band
          averagedAggregate = getAveragedAggregate(
            rows,
            this._aggregation[aggregateKey].lastAggregateIndex,
            rowIndex - 1
          );

          // place the average
          setAggregateData(lastTimeIndex, averagedAggregate);

          // now we know where the next aggregate begins
          this._aggregation[aggregateKey].lastAggregateIndex = rowIndex;
        }

        // level-break
        this._aggregation[aggregateKey].lastTimeIndex = currentTimeIndex;
      }
    }.bind(this);

    // iterate over the configured aggregation time buckets
    for (aggregateKey in this._aggregation) {
      // iterate through metrics, beginning where we left off
      processRow(this._lastAggregationIndex, this._metrics);
    }

    // remember where we will begin again
    this._lastAggregationIndex++;
  }
function aggregateMetrics(currentTime, metricData) {
    var aggregateKey;

    /**
     * Place aggregate data into the specified slot.  If the current zoom
     * level matches the aggregate level, the data is emitted to keep the
     * display in sync.
     *
     * @param {Number} index
     * The desired slot for the aggregate.
     *
     * @param {Object} data
     * The aggregate data.
     *
     * @returns {void}
     */
    var setAggregateData =
      function setAggregateData(index, data) {
        this._aggregation[aggregateKey].data[index] = data;

        // if this view (current or not) is scrolled, adjust it
        if (this._aggregation[aggregateKey].scrollOffset) {
          this._aggregation[aggregateKey].scrollOffset--;
        }

        // emit to keep the display in sync
        if (this.isCurrentZoom(aggregateKey)) {
          if (this.isScrolled()) {
            this.emit("refreshMetrics");
          } else {
            this.emit("metrics", data);
          }
        }
      }.bind(this);

    /**
     * Given the current time time index, add any missing logical
     * time slots to have a complete picture of data.
     *
     * @param {Number} currentTimeIndex
     * The time index currently being processed.
     *
     * @returns {void}
     */
    var addMissingTimeSlots =
      function addMissingTimeSlots(currentTimeIndex) {
        var aggregateIndex = this._aggregation[aggregateKey].data.length;
        while (aggregateIndex < currentTimeIndex) {
          setAggregateData(aggregateIndex++, this.emptyAverage);
        }
      }.bind(this);

    /**
     * After having detected a new sampling, aggregate the relevant data points
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @param {Number} startIndex
     * The starting index to derive an average.
     *
     * @param {Number} endIndex
     * The ending index to derive an average.
     *
     * @returns {void}
     */
    var getAveragedAggregate =
      function getAveragedAggregate(rows, startIndex, endIndex) {
        var averagedAggregate = getInitializedAverage(metricData);

        // this is the number of elements we will aggregate
        var aggregateCount = endIndex - startIndex + 1;

        // you can compute an average of a set of numbers two ways
        // first, you can add all the numbers together and then divide by the count
        // second, you call divide each number by the count and add the quotients
        // the first method is more accurate, however you can overflow an accumulator
        // and result with NaN
        // the second method is employed here to ensure no overflows

        for (var dataKey in metricData) {
          for (var dataMetricKey in metricData[dataKey]) {
            for (var rowIndex = startIndex; rowIndex <= endIndex; rowIndex++) {
              averagedAggregate[dataKey][dataMetricKey] +=
                rows[rowIndex][dataKey][dataMetricKey] / aggregateCount;
            }

            // after the average is done, truncate the averages to one decimal point
            averagedAggregate[dataKey][dataMetricKey] =
              +averagedAggregate[dataKey][dataMetricKey].toFixed(1);
          }
        }

        return averagedAggregate;
      };

    /**
     * Process one row of metric data into aggregate.
     *
     * @param {Number} rowIndex
     * The index of the row being processed.
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @returns {void}
     */
    var processRow = function processRow(rowIndex, rows) {
      var averagedAggregate;
      var lastTimeIndex = this._aggregation[aggregateKey].lastTimeIndex;

      // get the time index of the aggregate
      var currentTimeIndex =
        convertElapsedTimeToTimeIndex(currentTime, this._startTime, +aggregateKey);

      // when the time index changes, average the data for the time band
      if (currentTimeIndex !== lastTimeIndex) {
        // except for the first one
        if (lastTimeIndex !== undefined) {
          // add in any missing logical time slots
          addMissingTimeSlots.call(this, lastTimeIndex);

          // get the average across the discovered time band
          averagedAggregate = getAveragedAggregate(
            rows,
            this._aggregation[aggregateKey].lastAggregateIndex,
            rowIndex - 1
          );

          // place the average
          setAggregateData(lastTimeIndex, averagedAggregate);

          // now we know where the next aggregate begins
          this._aggregation[aggregateKey].lastAggregateIndex = rowIndex;
        }

        // level-break
        this._aggregation[aggregateKey].lastTimeIndex = currentTimeIndex;
      }
    }.bind(this);

    // iterate over the configured aggregation time buckets
    for (aggregateKey in this._aggregation) {
      // iterate through metrics, beginning where we left off
      processRow(this._lastAggregationIndex, this._metrics);
    }

    // remember where we will begin again
    this._lastAggregationIndex++;
  }
function aggregateMetrics ( currentTime , metricData ) { var aggregateKey ; /**
     * Place aggregate data into the specified slot.  If the current zoom
     * level matches the aggregate level, the data is emitted to keep the
     * display in sync.
     *
     * @param {Number} index
     * The desired slot for the aggregate.
     *
     * @param {Object} data
     * The aggregate data.
     *
     * @returns {void}
     */ var setAggregateData = function setAggregateData ( index , data ) { this . _aggregation [ aggregateKey ] . data [ index ] = data ; // if this view (current or not) is scrolled, adjust it if ( this . _aggregation [ aggregateKey ] . scrollOffset ) { this . _aggregation [ aggregateKey ] . scrollOffset -- ; } // emit to keep the display in sync if ( this . isCurrentZoom ( aggregateKey ) ) { if ( this . isScrolled ( ) ) { this . emit ( "refreshMetrics" ) ; } else { this . emit ( "metrics" , data ) ; } } } . bind ( this ) ; /**
     * Given the current time time index, add any missing logical
     * time slots to have a complete picture of data.
     *
     * @param {Number} currentTimeIndex
     * The time index currently being processed.
     *
     * @returns {void}
     */ var addMissingTimeSlots = function addMissingTimeSlots ( currentTimeIndex ) { var aggregateIndex = this . _aggregation [ aggregateKey ] . data . length ; while ( aggregateIndex < currentTimeIndex ) { setAggregateData ( aggregateIndex ++ , this . emptyAverage ) ; } } . bind ( this ) ; /**
     * After having detected a new sampling, aggregate the relevant data points
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @param {Number} startIndex
     * The starting index to derive an average.
     *
     * @param {Number} endIndex
     * The ending index to derive an average.
     *
     * @returns {void}
     */ var getAveragedAggregate = function getAveragedAggregate ( rows , startIndex , endIndex ) { var averagedAggregate = getInitializedAverage ( metricData ) ; // this is the number of elements we will aggregate var aggregateCount = endIndex - startIndex + 1 ; // you can compute an average of a set of numbers two ways // first, you can add all the numbers together and then divide by the count // second, you call divide each number by the count and add the quotients // the first method is more accurate, however you can overflow an accumulator // and result with NaN // the second method is employed here to ensure no overflows for ( var dataKey in metricData ) { for ( var dataMetricKey in metricData [ dataKey ] ) { for ( var rowIndex = startIndex ; rowIndex <= endIndex ; rowIndex ++ ) { averagedAggregate [ dataKey ] [ dataMetricKey ] += rows [ rowIndex ] [ dataKey ] [ dataMetricKey ] / aggregateCount ; } // after the average is done, truncate the averages to one decimal point averagedAggregate [ dataKey ] [ dataMetricKey ] = + averagedAggregate [ dataKey ] [ dataMetricKey ] . toFixed ( 1 ) ; } } return averagedAggregate ; } ; /**
     * Process one row of metric data into aggregate.
     *
     * @param {Number} rowIndex
     * The index of the row being processed.
     *
     * @param {Object[]} rows
     * The array reference.
     *
     * @returns {void}
     */ var processRow = function processRow ( rowIndex , rows ) { var averagedAggregate ; var lastTimeIndex = this . _aggregation [ aggregateKey ] . lastTimeIndex ; // get the time index of the aggregate var currentTimeIndex = convertElapsedTimeToTimeIndex ( currentTime , this . _startTime , + aggregateKey ) ; // when the time index changes, average the data for the time band if ( currentTimeIndex !== lastTimeIndex ) { // except for the first one if ( lastTimeIndex !== undefined ) { // add in any missing logical time slots addMissingTimeSlots . call ( this , lastTimeIndex ) ; // get the average across the discovered time band averagedAggregate = getAveragedAggregate ( rows , this . _aggregation [ aggregateKey ] . lastAggregateIndex , rowIndex - 1 ) ; // place the average setAggregateData ( lastTimeIndex , averagedAggregate ) ; // now we know where the next aggregate begins this . _aggregation [ aggregateKey ] . lastAggregateIndex = rowIndex ; } // level-break this . _aggregation [ aggregateKey ] . lastTimeIndex = currentTimeIndex ; } } . bind ( this ) ; // iterate over the configured aggregation time buckets for ( aggregateKey in this . _aggregation ) { // iterate through metrics, beginning where we left off processRow ( this . _lastAggregationIndex , this . _metrics ) ; } // remember where we will begin again this . _lastAggregationIndex ++ ; }
Perform event-driven aggregation at all configured units of time.

@param {Number} currentTime
The current time of the aggregation.

@param {Object} metricData
The metric data template received.

@this MetricsProvider

@returns {void}
Perform event - driven aggregation at all configured units of time .