Home Identifier Source Repository

src/extensions/colOps/colOps.js

import Dom from '../../dom';
import Str from '../../string';
import Types from '../../types';

export default class ColOps{

    /**
     * Column calculations
     * @param {Object} tf TableFilter instance
     */
    constructor(tf, opts) {

        //calls function before col operation
        this.onBeforeOperation = Types.isFn(opts.on_before_operation) ?
            opts.on_before_operation : null;
        //calls function after col operation
        this.onAfterOperation = Types.isFn(opts.on_after_operation) ?
            opts.on_after_operation : null;

        this.opts = opts;
        this.tf = tf;
    }

    init(){
        this.calc();
    }

    /**
     * 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() {
        var tf = this.tf;
        if(!tf.isFirstLoad && !tf.hasGrid()){
            return;
        }

        if(this.onBeforeOperation){
            this.onBeforeOperation.call(null, tf);
        }

        var opts = this.opts,
            labelId = opts.id,
            colIndex = opts.col,
            operation = opts.operation,
            outputType = opts.write_method,
            totRowIndex = opts.tot_row_index,
            excludeRow = opts.exclude_row,
            decimalPrecision = Types.isUndef(opts.decimal_precision) ?
                2 : opts.decimal_precision;

        //nuovella: determine unique list of columns to operate on
        var ucolIndex = [],
            ucolMax = 0;
        ucolIndex[ucolMax] = colIndex[0];

        for(var ii=1; ii<colIndex.length; ii++){
            var saved = 0;
            //see if colIndex[ii] is already in the list of unique indexes
            for(var jj=0; jj<=ucolMax; jj++){
                if(ucolIndex[jj] === colIndex[ii]){
                    saved = 1;
                }
            }
            //if not saved then, save the index;
            if (saved === 0){
                ucolMax++;
                ucolIndex[ucolMax] = colIndex[ii];
            }
        }

        if(Str.lower(typeof labelId)=='object' &&
            Str.lower(typeof colIndex)=='object' &&
            Str.lower(typeof operation)=='object'){
            var rows = tf.tbl.rows,
                colvalues = [];

            for(var ucol=0; ucol<=ucolMax; ucol++){
                //this retrieves col values
                //use ucolIndex because we only want to pass through this loop
                //once for each column get the values in this unique column
                colvalues.push(
                    tf.getColValues(ucolIndex[ucol], true, excludeRow));

                //next: calculate all operations for this column
                var result,
                    nbvalues=0,
                    temp,
                    meanValue=0,
                    sumValue=0,
                    minValue=null,
                    maxValue=null,
                    q1Value=null,
                    medValue=null,
                    q3Value=null,
                    meanFlag=0,
                    sumFlag=0,
                    minFlag=0,
                    maxFlag=0,
                    q1Flag=0,
                    medFlag=0,
                    q3Flag=0,
                    theList=[],
                    opsThisCol=[],
                    decThisCol=[],
                    labThisCol=[],
                    oTypeThisCol=[],
                    mThisCol=-1;

                for(var k=0; k<colIndex.length; k++){
                     if(colIndex[k] === ucolIndex[ucol]){
                        mThisCol++;
                        opsThisCol[mThisCol]=Str.lower(operation[k]);
                        decThisCol[mThisCol]=decimalPrecision[k];
                        labThisCol[mThisCol]=labelId[k];
                        oTypeThisCol = outputType !== undefined &&
                            Str.lower(typeof outputType)==='object' ?
                            outputType[k] : null;

                        switch(opsThisCol[mThisCol]){
                            case 'mean':
                                meanFlag=1;
                            break;
                            case 'sum':
                                sumFlag=1;
                            break;
                            case 'min':
                                minFlag=1;
                            break;
                            case 'max':
                                maxFlag=1;
                            break;
                            case 'median':
                                medFlag=1;
                                break;
                            case 'q1':
                                q1Flag=1;
                            break;
                            case 'q3':
                                q3Flag=1;
                            break;
                        }
                    }
                }

                for(var j=0; j<colvalues[ucol].length; j++){
                    //sort the list for calculation of median and quartiles
                    if((q1Flag==1)|| (q3Flag==1) || (medFlag==1)){
                        if (j<colvalues[ucol].length -1){
                            for(k=j+1; k<colvalues[ucol].length; k++) {
                                if(eval(colvalues[ucol][k]) <
                                    eval(colvalues[ucol][j])){
                                    temp = colvalues[ucol][j];
                                    colvalues[ucol][j] = colvalues[ucol][k];
                                    colvalues[ucol][k] = temp;
                                }
                            }
                        }
                    }
                    var cvalue = parseFloat(colvalues[ucol][j]);
                    theList[j] = parseFloat(cvalue);

                    if(!isNaN(cvalue)){
                        nbvalues++;
                        if(sumFlag===1 || meanFlag===1){
                            sumValue += parseFloat( cvalue );
                        }
                        if(minFlag===1){
                            if(minValue===null){
                                minValue = parseFloat( cvalue );
                            } else{
                                minValue = parseFloat( cvalue ) < minValue ?
                                    parseFloat( cvalue ): minValue;
                            }
                        }
                        if(maxFlag===1){
                            if (maxValue===null){
                                maxValue = parseFloat( cvalue );
                            } else {
                                maxValue = parseFloat( cvalue ) > maxValue ?
                                    parseFloat( cvalue ): maxValue;
                            }
                        }
                    }
                }//for j
                if(meanFlag===1){
                    meanValue = sumValue/nbvalues;
                }
                if(medFlag===1){
                    var aux = 0;
                    if(nbvalues%2 === 1){
                        aux = Math.floor(nbvalues/2);
                        medValue = theList[aux];
                    } else{
                        medValue =
                            (theList[nbvalues/2] + theList[((nbvalues/2)-1)])/2;
                    }
                }
                var posa;
                if(q1Flag===1){
                    posa=0.0;
                    posa = Math.floor(nbvalues/4);
                    if(4*posa == nbvalues){
                        q1Value = (theList[posa-1] + theList[posa])/2;
                    } else {
                        q1Value = theList[posa];
                    }
                }
                if (q3Flag===1){
                    posa=0.0;
                    var posb=0.0;
                    posa = Math.floor(nbvalues/4);
                    if (4*posa === nbvalues){
                        posb = 3*posa;
                        q3Value = (theList[posb] + theList[posb-1])/2;
                    } else {
                        q3Value = theList[nbvalues-posa-1];
                    }
                }

                for(var i=0; i<=mThisCol; i++){
                    switch( opsThisCol[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;
                    }

                    var precision = !isNaN(decThisCol[i]) ? decThisCol[i] : 2;

                    //if outputType is defined
                    if(oTypeThisCol && result){
                        result = result.toFixed( precision );

                        if(Dom.id(labThisCol[i])){
                            switch( Str.lower(oTypeThisCol) ){
                                case 'innerhtml':
                                    if (isNaN(result) || !isFinite(result) ||
                                        nbvalues===0){
                                        Dom.id(labThisCol[i]).innerHTML = '.';
                                    } else{
                                        Dom.id(labThisCol[i]).innerHTML= result;
                                    }
                                break;
                                case 'setvalue':
                                    Dom.id( labThisCol[i] ).value = result;
                                break;
                                case 'createtextnode':
                                    var oldnode = Dom.id(labThisCol[i])
                                        .firstChild;
                                    var txtnode = Dom.text(result);
                                    Dom.id(labThisCol[i])
                                        .replaceChild(txtnode, oldnode);
                                break;
                            }//switch
                        }
                    } else {
                        try{
                            if(isNaN(result) || !isFinite(result) ||
                                nbvalues===0){
                                Dom.id(labThisCol[i]).innerHTML = '.';
                            } else {
                                Dom.id(labThisCol[i]).innerHTML =
                                    result.toFixed(precision);
                            }
                        } catch(e) {}//catch
                    }//else
                }//for i

                // row(s) with result are always visible
                var totRow = totRowIndex && totRowIndex[ucol] ?
                                rows[totRowIndex[ucol]] : null;
                if(totRow){
                    totRow.style.display = '';
                }
            }//for ucol
        }//if typeof

        if(this.onAfterOperation){
            this.onAfterOperation.call(null, tf);
        }
    }

    destroy(){}

}