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;
})();