1
0
Fork 0
mirror of https://github.com/koalyptus/TableFilter.git synced 2024-05-17 13:56:38 +02:00
TableFilter/src/extensions/colOps/colOps.js
2017-01-12 14:07:21 +11:00

374 lines
11 KiB
JavaScript

import {Feature} from '../../feature';
import {createText, elm} from '../../dom';
import {isArray, isFn, isUndef, EMPTY_FN} from '../../types';
import {numSortAsc} from '../../sort';
const EVENTS = [
'after-filtering',
'after-page-change',
'after-page-length-change'
];
/**
* Column calculations extension
*/
export default class ColOps extends Feature {
/**
* Creates an instance of ColOps
*
* @param {TableFilter} tf TableFilter instance
* @param {Object} opts Configuration object
*/
constructor(tf, opts) {
super(tf, opts.name);
/**
* Callback fired before columns operations start
* @type {Function}
*/
this.onBeforeOperation = isFn(opts.on_before_operation) ?
opts.on_before_operation : EMPTY_FN;
/**
* Callback fired after columns operations are completed
* @type {Function}
*/
this.onAfterOperation = isFn(opts.on_after_operation) ?
opts.on_after_operation : EMPTY_FN;
/**
* Configuration options
* @type {Object}
*/
this.opts = opts;
this.enable();
}
/**
* Initializes ColOps instance
*/
init() {
if (this.initialized) {
return;
}
// subscribe to events
this.emitter.on(EVENTS, () => this.calc());
this.calc();
/**
* @inherited
*/
this.initialized = true;
}
/**
* Calculates columns' values
* Configuration options are stored in 'opts' property
* - 'id' contains ids of elements showing result (array)
* - 'col' contains the columns' indexes (array)
* - 'operation' contains operation type (array, values: 'sum', 'mean',
* 'min', 'max', 'median', 'q1', 'q3')
* - 'write_method' array defines which method to use for displaying the
* result (innerHTML, setValue, createTextNode) - default: 'innerHTML'
* - 'tot_row_index' defines in which row results are displayed
* (integers array)
*
* - changes made by Nuovella:
* (1) optimized the routine (now it will only process each column once),
* (2) added calculations for the median, lower and upper quartile.
*/
calc() {
let tf = this.tf;
if (!tf.isInitialized()) {
return;
}
this.onBeforeOperation(tf, this);
this.emitter.emit('before-column-operation', tf, this);
let opts = this.opts,
labelId = opts.id,
colIndexes = opts.col,
operation = opts.operation,
outputType = opts.write_method,
totRowIndex = opts.tot_row_index,
excludeRow = opts.exclude_row,
decimalPrecision = isUndef(opts.decimal_precision) ?
2 : opts.decimal_precision;
//nuovella: determine unique list of columns to operate on
let uColIdx = [],
uColMax = 0;
uColIdx[uColMax] = colIndexes[0];
for (let ii = 1; ii < colIndexes.length; ii++) {
let saved = 0;
//see if colIndexes[ii] is already in the list of unique indexes
for (let jj = 0; jj <= uColMax; jj++) {
if (uColIdx[jj] === colIndexes[ii]) {
saved = 1;
}
}
//if not saved then, save the index;
if (saved === 0) {
uColMax++;
uColIdx[uColMax] = colIndexes[ii];
}
}
if (isArray(labelId) && isArray(colIndexes) && isArray(operation)) {
let rows = tf.tbl.rows,
colValues = [],
uCol = 0;
for (; uCol <= uColMax; uCol++) {
//this retrieves col values
//use uColIdx because we only want to pass through this loop
//once for each column get the values in this unique column
colValues.push(
tf.getColValues(uColIdx[uCol], false, true, excludeRow)
);
let curValues = colValues[uCol];
//next: calculate all operations for this column
let result,
// nbValues = 0,
// temp,
meanValue = 0,
sumValue = 0,
minValue = null,
maxValue = null,
q1Value = null,
medValue = null,
q3Value = null,
meanFlag = false,
sumFlag = false,
minFlag = false,
maxFlag = false,
q1Flag = false,
medFlag = false,
q3Flag = false,
// theList = [],
// opsThisCol = [],
// decThisCol = [],
// labThisCol = [],
// oTypeThisCol = [],
// values = [],
operations = [],
precisions = [],
labels = [],
writeType,
idx = -1,
k = 0,
// j = 0,
i = 0;
for (; k < colIndexes.length; k++) {
if (colIndexes[k] !== uColIdx[uCol]) {
continue;
}
idx++;
operations[idx] = operation[k].toLowerCase();
precisions[idx] = decimalPrecision[k];
labels[idx] = labelId[k];
writeType = isArray(outputType) ? outputType[k] : null;
switch (operations[idx]) {
case 'mean':
meanFlag = true;
break;
case 'sum':
sumFlag = true;
break;
case 'min':
minFlag = true;
break;
case 'max':
maxFlag = true;
break;
case 'median':
medFlag = true;
break;
case 'q1':
q1Flag = true;
break;
case 'q3':
q3Flag = true;
break;
}
}
//sort the values for calculation of median and quartiles
if ((q1Flag === 1) || (q3Flag === 1) || (medFlag === 1)) {
curValues = this.sortColumnValues(curValues, numSortAsc);
}
if (sumFlag || meanFlag) {
sumValue = this.calcSum(curValues);
}
if (maxFlag) {
maxValue = this.calcMax(curValues);
}
if (minFlag) {
minValue = this.calcMin(curValues);
}
if (meanFlag) {
meanValue = sumValue / curValues.length;
}
if (medFlag) {
medValue = this.calcMedian(curValues);
}
if (q1Flag) {
q1Value = this.calcQ1(curValues);
}
if (q3Flag) {
q3Value = this.calcQ3(curValues);
}
for (; i <= idx; i++) {
switch (operations[i]) {
case 'mean':
result = meanValue;
break;
case 'sum':
result = sumValue;
break;
case 'min':
result = minValue;
break;
case 'max':
result = maxValue;
break;
case 'median':
result = medValue;
break;
case 'q1':
result = q1Value;
break;
case 'q3':
result = q3Value;
break;
}
this.writeResult(
result,
labels[i],
writeType,
precisions[i]
);
}//for i
// row(s) with result are always visible
let totRow = totRowIndex && totRowIndex[uCol] ?
rows[totRowIndex[uCol]] : null;
if (totRow) {
totRow.style.display = '';
}
}//for uCol
}//if typeof
this.onAfterOperation(tf, this);
this.emitter.emit('after-column-operation', tf, this);
}
calcSum(values = []) {
return values.reduce((x, y) => x + y);
}
calcMax(values = []) {
return Math.max.apply(null, values);
}
calcMin(values = []) {
return Math.min.apply(null, values);
}
calcMedian(values = []) {
let nbValues = values.length;
let aux = 0;
if (nbValues % 2 === 1) {
aux = Math.floor(nbValues / 2);
return values[aux];
} else {
return (values[nbValues / 2] +
values[((nbValues / 2) - 1)]) / 2;
}
}
calcQ1(values = []) {
let nbValues = values.length;
let posa = 0.0;
posa = Math.floor(nbValues / 4);
if (4 * posa === nbValues) {
return (values[posa - 1] + values[posa]) / 2;
} else {
return values[posa];
}
}
calcQ3(values = []) {
let nbValues = values.length;
let posa = 0.0;
let posb = 0.0;
posa = Math.floor(nbValues / 4);
if (4 * posa === nbValues) {
posb = 3 * posa;
return (values[posb] + values[posb - 1]) / 2;
} else {
return values[nbValues - posa - 1];
}
}
sortColumnValues(values = [], sorter) {
return values.sort(sorter);
}
writeResult(result = 0, label, writeType = 'innerhtml', precision = 2) {
let labelElm = elm(label);
if (!labelElm) {
return;
}
result = result.toFixed(precision);
if (isNaN(result) || !isFinite(result)) {
result = '';
}
switch (writeType.toLowerCase()) {
case 'innerhtml':
labelElm.innerHTML = result;
break;
case 'setvalue':
labelElm.value = result;
break;
case 'createtextnode':
let oldnode = labelElm.firstChild;
let txtnode = createText(result);
labelElm.replaceChild(txtnode, oldnode);
break;
}
}
/**
* Remove extension
*/
destroy() {
if (!this.initialized) {
return;
}
// unsubscribe to events
this.emitter.off(EVENTS, () => this.calc());
this.initialized = false;
}
}