1
0
Fork 0
mirror of https://github.com/koalyptus/TableFilter.git synced 2024-06-01 05:23:02 +02:00
TableFilter/src/extensions/colOps/colOps.js

374 lines
11 KiB
JavaScript
Raw Normal View History

import {Feature} from '../../feature';
2016-05-25 09:31:53 +02:00
import {createText, elm} from '../../dom';
2016-12-22 12:01:59 +01:00
import {isArray, isFn, isUndef, EMPTY_FN} from '../../types';
2017-01-11 04:13:57 +01:00
import {numSortAsc} from '../../sort';
2014-11-15 15:34:32 +01:00
2016-11-02 13:01:31 +01:00
const EVENTS = [
'after-filtering',
'after-page-change',
'after-page-length-change'
];
2016-09-01 10:27:02 +02:00
/**
* Column calculations extension
*/
export default class ColOps extends Feature {
2014-11-15 15:34:32 +01:00
/**
2016-09-01 10:27:02 +02:00
* Creates an instance of ColOps
*
* @param {TableFilter} tf TableFilter instance
* @param {Object} opts Configuration object
2014-11-15 15:34:32 +01:00
*/
constructor(tf, opts) {
super(tf, opts.name);
2014-11-15 15:34:32 +01:00
2016-09-01 10:27:02 +02:00
/**
* Callback fired before columns operations start
* @type {Function}
*/
2016-05-15 04:56:12 +02:00
this.onBeforeOperation = isFn(opts.on_before_operation) ?
2016-12-22 12:01:59 +01:00
opts.on_before_operation : EMPTY_FN;
2016-09-01 10:27:02 +02:00
/**
* Callback fired after columns operations are completed
* @type {Function}
*/
2016-05-15 04:56:12 +02:00
this.onAfterOperation = isFn(opts.on_after_operation) ?
2016-12-22 12:01:59 +01:00
opts.on_after_operation : EMPTY_FN;
2015-02-14 09:59:12 +01:00
2016-09-01 10:27:02 +02:00
/**
* Configuration options
* @type {Object}
*/
this.opts = opts;
2016-09-01 10:27:02 +02:00
this.enable();
2014-11-15 15:34:32 +01:00
}
2016-09-02 05:32:59 +02:00
/**
* Initializes ColOps instance
*/
init() {
if (this.initialized) {
return;
}
// subscribe to events
2016-11-02 13:01:31 +01:00
this.emitter.on(EVENTS, () => this.calc());
this.calc();
2016-09-01 10:27:02 +02:00
/**
* @inherited
*/
this.initialized = true;
}
2014-11-15 15:34:32 +01:00
/**
* Calculates columns' values
* Configuration options are stored in 'opts' property
2014-11-15 15:34:32 +01:00
* - '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.
*/
2014-11-16 01:29:07 +01:00
calc() {
2016-09-01 10:27:02 +02:00
let tf = this.tf;
if (!tf.isInitialized()) {
2014-11-15 15:34:32 +01:00
return;
}
2016-12-22 12:01:59 +01:00
this.onBeforeOperation(tf, this);
2016-09-02 05:32:59 +02:00
this.emitter.emit('before-column-operation', tf, this);
2014-11-15 15:34:32 +01:00
2016-09-01 10:27:02 +02:00
let opts = this.opts,
labelId = opts.id,
2017-01-11 04:13:57 +01:00
colIndexes = opts.col,
operation = opts.operation,
outputType = opts.write_method,
totRowIndex = opts.tot_row_index,
excludeRow = opts.exclude_row,
2016-05-15 04:56:12 +02:00
decimalPrecision = isUndef(opts.decimal_precision) ?
2 : opts.decimal_precision;
2014-11-15 15:34:32 +01:00
//nuovella: determine unique list of columns to operate on
let uColIdx = [],
uColMax = 0;
2017-01-11 04:13:57 +01:00
uColIdx[uColMax] = colIndexes[0];
2014-11-15 15:34:32 +01:00
2017-01-11 04:13:57 +01:00
for (let ii = 1; ii < colIndexes.length; ii++) {
2016-09-01 10:27:02 +02:00
let saved = 0;
2017-01-11 04:13:57 +01:00
//see if colIndexes[ii] is already in the list of unique indexes
for (let jj = 0; jj <= uColMax; jj++) {
2017-01-11 04:13:57 +01:00
if (uColIdx[jj] === colIndexes[ii]) {
2014-11-15 15:34:32 +01:00
saved = 1;
}
}
//if not saved then, save the index;
if (saved === 0) {
uColMax++;
2017-01-11 04:13:57 +01:00
uColIdx[uColMax] = colIndexes[ii];
2014-11-15 15:34:32 +01:00
}
}
2017-01-11 04:13:57 +01:00
if (isArray(labelId) && isArray(colIndexes) && isArray(operation)) {
2016-09-01 10:27:02 +02:00
let rows = tf.tbl.rows,
colValues = [],
uCol = 0;
2014-11-15 15:34:32 +01:00
for (; uCol <= uColMax; uCol++) {
2014-11-15 15:34:32 +01:00
//this retrieves col values
//use uColIdx because we only want to pass through this loop
2014-11-15 15:34:32 +01:00
//once for each column get the values in this unique column
colValues.push(
tf.getColValues(uColIdx[uCol], false, true, excludeRow)
2016-11-02 13:01:31 +01:00
);
2014-11-15 15:34:32 +01:00
2017-01-12 04:07:21 +01:00
let curValues = colValues[uCol];
2014-11-15 15:34:32 +01:00
//next: calculate all operations for this column
2016-09-01 10:27:02 +02:00
let result,
2017-01-12 04:07:21 +01:00
// nbValues = 0,
2017-01-11 04:13:57 +01:00
// temp,
meanValue = 0,
sumValue = 0,
minValue = null,
maxValue = null,
q1Value = null,
medValue = null,
q3Value = null,
2017-01-12 04:07:21 +01:00
meanFlag = false,
sumFlag = false,
minFlag = false,
maxFlag = false,
q1Flag = false,
medFlag = false,
q3Flag = false,
// theList = [],
// opsThisCol = [],
// decThisCol = [],
// labThisCol = [],
// oTypeThisCol = [],
2017-01-12 04:07:21 +01:00
// values = [],
operations = [],
precisions = [],
labels = [],
2017-01-10 04:07:16 +01:00
writeType,
idx = -1,
k = 0,
2017-01-12 04:07:21 +01:00
// j = 0,
i = 0;
2014-11-15 15:34:32 +01:00
2017-01-11 04:13:57 +01:00
for (; k < colIndexes.length; k++) {
2017-01-12 04:07:21 +01:00
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;
2014-11-15 15:34:32 +01:00
}
}
2017-01-12 04:07:21 +01:00
//sort the values for calculation of median and quartiles
2017-01-11 04:13:57 +01:00
if ((q1Flag === 1) || (q3Flag === 1) || (medFlag === 1)) {
2017-01-12 04:07:21 +01:00
curValues = this.sortColumnValues(curValues, numSortAsc);
2017-01-11 04:13:57 +01:00
}
2017-01-12 04:07:21 +01:00
if (sumFlag || meanFlag) {
sumValue = this.calcSum(curValues);
2017-01-11 04:13:57 +01:00
}
2017-01-12 04:07:21 +01:00
if (maxFlag) {
maxValue = this.calcMax(curValues);
2017-01-11 04:13:57 +01:00
}
2017-01-12 04:07:21 +01:00
if (minFlag) {
minValue = this.calcMin(curValues);
2017-01-11 04:13:57 +01:00
}
2017-01-12 04:07:21 +01:00
if (meanFlag) {
meanValue = sumValue / curValues.length;
2014-11-15 15:34:32 +01:00
}
2017-01-12 04:07:21 +01:00
if (medFlag) {
medValue = this.calcMedian(curValues);
2014-11-15 15:34:32 +01:00
}
2017-01-12 04:07:21 +01:00
if (q1Flag) {
q1Value = this.calcQ1(curValues);
2014-11-15 15:34:32 +01:00
}
2017-01-12 04:07:21 +01:00
if (q3Flag) {
q3Value = this.calcQ3(curValues);
2014-11-15 15:34:32 +01:00
}
for (; i <= idx; i++) {
switch (operations[i]) {
2014-11-15 15:34:32 +01:00
case 'mean':
result = meanValue;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
case 'sum':
result = sumValue;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
case 'min':
result = minValue;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
case 'max':
result = maxValue;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
case 'median':
result = medValue;
2014-11-15 15:34:32 +01:00
break;
case 'q1':
result = q1Value;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
case 'q3':
result = q3Value;
2016-02-22 08:14:58 +01:00
break;
2014-11-15 15:34:32 +01:00
}
2017-01-10 04:07:16 +01:00
this.writeResult(
result,
labels[i],
writeType,
precisions[i]
);
2014-11-15 15:34:32 +01:00
}//for i
// row(s) with result are always visible
let totRow = totRowIndex && totRowIndex[uCol] ?
rows[totRowIndex[uCol]] : null;
if (totRow) {
2014-11-15 15:34:32 +01:00
totRow.style.display = '';
}
}//for uCol
2014-11-15 15:34:32 +01:00
}//if typeof
2016-12-22 12:01:59 +01:00
this.onAfterOperation(tf, this);
2016-09-02 05:32:59 +02:00
this.emitter.emit('after-column-operation', tf, this);
2014-11-15 15:34:32 +01:00
}
2017-01-11 07:13:38 +01:00
calcSum(values = []) {
return values.reduce((x, y) => x + y);
}
2017-01-11 04:13:57 +01:00
calcMax(values = []) {
return Math.max.apply(null, values);
}
calcMin(values = []) {
return Math.min.apply(null, values);
}
2017-01-12 04:07:21 +01:00
calcMedian(values = []) {
let nbValues = values.length;
2017-01-11 07:13:38 +01:00
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;
}
}
2017-01-12 04:07:21 +01:00
calcQ1(values = []) {
let nbValues = values.length;
2017-01-11 07:13:38 +01:00
let posa = 0.0;
posa = Math.floor(nbValues / 4);
if (4 * posa === nbValues) {
return (values[posa - 1] + values[posa]) / 2;
} else {
return values[posa];
}
}
2017-01-12 04:07:21 +01:00
calcQ3(values = []) {
let nbValues = values.length;
2017-01-11 07:13:38 +01:00
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];
}
}
2017-01-11 04:13:57 +01:00
sortColumnValues(values = [], sorter) {
return values.sort(sorter);
}
writeResult(result = 0, label, writeType = 'innerhtml', precision = 2) {
2017-01-10 04:07:16 +01:00
let labelElm = elm(label);
2017-01-10 04:07:16 +01:00
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;
}
}
2016-09-02 05:32:59 +02:00
/**
* Remove extension
*/
destroy() {
if (!this.initialized) {
return;
}
// unsubscribe to events
2016-11-02 13:01:31 +01:00
this.emitter.off(EVENTS, () => this.calc());
this.initialized = false;
}
}