Source: style/stylequantification.js

goog.provide('M.style.quantification');

/**
 * @namespace M.style.quantification
 */
(function() {

  /** This function returns a jenks quantification function
   * @function
   * @public
   * @param {number} n_classes_param - Number of classes
   * @return {function}
   * @api stable
   */

  M.style.quantification.JENKS = function(n_classes_param) {
    n_classes_param = n_classes_param || M.style.quantification.DEFAULT_CLASES_JENKS;

    return function jenks(data, n_classes = n_classes_param) {
      let uniqueData = M.style.quantification.uniqueArray_(data);
      n_classes = uniqueData.length <= n_classes ? uniqueData.length - 1 : n_classes;
      // sort data in numerical order, since this is expected
      // by the matrices function
      data.sort(function(a, b) {
        return a - b;
      });

      // get our basic matrices
      let matrices = M.style.quantification.getMatrices_(data, n_classes);
      // we only need lower class limits here
      let lower_class_limits = matrices.lower_class_limits;

      // extract n_classes out of the computed matrices
      let breaks = M.style.quantification.jenksBreaks_(data, lower_class_limits, n_classes);
      // No cogemos el minimo
      let break_points = breaks.slice(1, breaks.length);
      return break_points;
    };
  };

  /** This function returns a quantile quantification function
   * @function
   * @public
   * @param {number} n_classes_param - Number of classes
   * @return {function}
   * @api stable
   */

  M.style.quantification.QUANTILE = function(n_classes_param) {
    n_classes_param = n_classes_param || M.style.quantification.DEFAULT_CLASES_QUANTILE;
    return function quantile(data, n_classes = n_classes_param) {
      let uniqueData = M.style.quantification.uniqueArray_(data);
      n_classes = uniqueData.length <= n_classes ? uniqueData.length - 1 : n_classes;
      let numData = data.length;
      data.sort((a, b) => a - b);
      let [min, max] = [data[0], data[numData - 1]];

      // Calculamos el salto para calcular los puntos de ruptura
      // Esto será (valor minimo + valor máximo) / número de clases
      let step = (min + max) / n_classes;
      let breaks = [];

      // Calculamos los puntos de ruptura multiplicando por el valor
      // del salto desde i = 1, 2, .. numero de clases - 1
      for (var i = 1; i < n_classes; i++) {
        let break_point = step * i;
        breaks.push(break_point);
      }
      breaks.push(max);
      return breaks;
    };
  };

  /**
   *  Compute the matrices required for Jenks breaks. These matrices
   * can be used for any classing of data with `classes <= n_classes`
   * @function
   * @private
   * @api stable
   *
   */
  M.style.quantification.getMatrices_ = function(data, n_classes) {

    // in the original implementation, these matrices are referred to
    // as `LC` and `OP`
    //
    // * lower_class_limits (LC): optimal lower class limits
    // * variance_combinations (OP): optimal variance combinations for all classes
    let lower_class_limits = [];
    let variance_combinations = [];
    // the variance, as computed at each step in the calculation
    let variance = 0;

    // Initialize and fill each matrix with zeroes
    for (let i = 0; i < data.length + 1; i++) {
      let tmp1 = [];
      let tmp2 = [];
      for (let j = 0; j < n_classes + 1; j++) {
        tmp1.push(0);
        tmp2.push(0);
      }
      lower_class_limits.push(tmp1);
      variance_combinations.push(tmp2);
    }

    for (let i = 1; i < n_classes + 1; i++) {
      lower_class_limits[1][i] = 1;
      variance_combinations[1][i] = 0;
      // in the original implementation, 9999999 is used but
      // since Javascript has `Infinity`, we use that.
      for (let j = 2; j < data.length + 1; j++) {
        variance_combinations[j][i] = Infinity;
      }
    }

    for (let l = 2; l < data.length + 1; l++) {

      // `SZ` originally. this is the sum of the values seen thus
      // far when calculating variance.
      let sum = 0;
      let sum_squares = 0;
      let w = 0;
      let i4 = 0;

      // in several instances, you could say `Math.pow(x, 2)`
      // instead of `x * x`, but this is slower in some browsers
      // introduces an unnecessary concept.
      for (let m = 1; m < l + 1; m++) {

        // `III` originally
        let lower_class_limit = l - m + 1;
        let val = data[lower_class_limit - 1];

        // here we're estimating variance for each potential classing
        // of the data, for each potential number of classes. `w`
        // is the number of data points considered so far.
        w++;

        // increase the current sum and sum-of-squares
        sum += val;
        sum_squares += val * val;

        // the variance at this point in the sequence is the difference
        // between the sum of squares and the total x 2, over the number
        // of samples.
        variance = sum_squares - (sum * sum) / w;

        i4 = lower_class_limit - 1;

        if (i4 !== 0) {
          for (let j = 2; j < n_classes + 1; j++) {
            // if adding this element to an existing class
            // will increase its variance beyond the limit, break
            // the class at this point, setting the lower_class_limit
            // at this point.
            if (variance_combinations[l][j] >=
              (variance + variance_combinations[i4][j - 1])) {
              lower_class_limits[l][j] = lower_class_limit;
              variance_combinations[l][j] = variance +
                variance_combinations[i4][j - 1];
            }
          }
        }
      }

      lower_class_limits[l][1] = 1;
      variance_combinations[l][1] = variance;
    }

    // return the two matrices. for just providing breaks, only
    // `lower_class_limits` is needed, but variances can be useful to
    // evaluage goodness of fit.
    return {
      lower_class_limits: lower_class_limits,
      variance_combinations: variance_combinations
    };
  };

  /**
   * This function take the calculated matrices
   * and derive an array of n breaks.
   * @function
   * @private
   * @api stable
   */

  M.style.quantification.jenksBreaks_ = function(data, lower_class_limits, n_classes) {

    let k = data.length - 1;
    let kclass = [];
    let countNum = n_classes;

    // the calculation of classes will never include the upper and
    // lower bounds, so we need to explicitly set them
    kclass[n_classes] = data[data.length - 1];
    kclass[0] = data[0];

    // the lower_class_limits matrix is used as indexes into itself
    // here: the `k` variable is reused in each iteration.
    while (countNum > 1) {
      kclass[countNum - 1] = data[lower_class_limits[k][countNum] - 2];
      k = lower_class_limits[k][countNum] - 1;
      countNum--;
    }

    return kclass;
  };

  /**
   * This function takes an array and creates a unique element array with it.
   * @function
   * @private
   * @param {Array} array - array of elements
   * @return {Array}
   * @api stable
   */

  M.style.quantification.uniqueArray_ = function(array) {
    let uniqueArray = [];
    array.forEach(function(elem) {
      if (uniqueArray.indexOf(elem) === -1) {
        uniqueArray.push(elem);
      }
    });
    return uniqueArray;
  };

  /**
   * @constant
   * @api stable
   */
  M.style.quantification.DEFAULT_CLASES_JENKS = 5;
  /**
   * @constant
   * @api stable
   */
  M.style.quantification.DEFAULT_CLASES_QUANTILE = 5;
})();