All files / datamodel/src/operator data-builder.js

96.97% Statements 96/99
91.89% Branches 34/37
100% Functions 24/24
96.74% Lines 89/92

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235                                12x     9x 47x   22x   9x   3x 35x 35x 35x 10x   25x 12x   13x     12x                     4x 4x   4x 24x 24x 8x   16x 16x       4x                         16x       16x 36x 24x     16x                   12x       12x   12x 18x 18x 18x   18x         18x   15x 16x 4x 4x 4x 6x   4x 16x     4x 18x 18x 18x       4x 4x 16x     12x 12x       12x 12x 63x                                 94x       94x   94x         94x 94x   94x   94x   94x 320x 885x 318x 318x           94x   318x     94x             94x 297x 297x 297x 297x 1045x   297x       297x       297x       94x 12x     94x 7x 3x 10x 24x     3x     94x    
import { FieldType, DimensionSubtype } from '../enums';
import { rowDiffsetIterator } from './row-diffset-iterator';
import { mergeSort } from './merge-sort';
import { fieldInSchema } from '../helper';
import { isCallable, isArray, } from '../utils';
/**
 * Generates the sorting functions to sort the data of a DataModel instance
 * according to the input data type.
 *
 * @param {string} dataType - The data type e.g. 'measure', 'datetime' etc.
 * @param {string} sortType - The sorting order i.e. 'asc' or 'desc'.
 * @param {integer} index - The index of the data which will be sorted.
 * @return {Function} Returns the the sorting function.
 */
function getSortFn (dataType, sortType, index) {
    let retFunc;
    switch (dataType) {
    case FieldType.MEASURE:
    case DimensionSubtype.TEMPORAL:
        if (sortType === 'desc') {
            retFunc = (a, b) => b[index] - a[index];
        } else {
            retFunc = (a, b) => a[index] - b[index];
        }
        break;
    default:
        retFunc = (a, b) => {
            const a1 = `${a[index]}`;
            const b1 = `${b[index]}`;
            if (a1 < b1) {
                return sortType === 'desc' ? 1 : -1;
            }
            if (a1 > b1) {
                return sortType === 'desc' ? -1 : 1;
            }
            return 0;
        };
    }
    return retFunc;
}
 
/**
 * Groups the data according to the specified target field.
 *
 * @param {Array} data - The input data array.
 * @param {number} fieldIndex - The target field index within schema array.
 * @return {Array} Returns an array containing the grouped data.
 */
function groupData(data, fieldIndex) {
    const hashMap = new Map();
    const groupedData = [];
 
    data.forEach((datum) => {
        const fieldVal = datum[fieldIndex];
        if (hashMap.has(fieldVal)) {
            groupedData[hashMap.get(fieldVal)][1].push(datum);
        } else {
            groupedData.push([fieldVal, [datum]]);
            hashMap.set(fieldVal, groupedData.length - 1);
        }
    });
 
    return groupedData;
}
 
/**
 * Creates the argument value used for sorting function when sort is done
 * with another fields.
 *
 * @param {Array} groupedDatum - The grouped datum for a single dimension field value.
 * @param {Array} targetFields - An array of the sorting fields.
 * @param {Array} targetFieldDetails - An array of the sorting field details in schema.
 * @return {Object} Returns an object containing the value of sorting fields and the target field name.
 */
function createSortingFnArg(groupedDatum, targetFields, targetFieldDetails) {
    const arg = {
        label: groupedDatum[0]
    };
 
    targetFields.reduce((acc, next, idx) => {
        acc[next] = groupedDatum[1].map(datum => datum[targetFieldDetails[idx].index]);
        return acc;
    }, arg);
 
    return arg;
}
 
/**
 * Sorts the data before return in dataBuilder.
 *
 * @param {Object} dataObj - An object containing the data and schema.
 * @param {Array} sortingDetails - An array containing the sorting configs.
 */
function sortData(dataObj, sortingDetails) {
    const { data, schema } = dataObj;
    let fieldName;
    let sortMeta;
    let fDetails;
    let i = sortingDetails.length - 1;
 
    for (; i >= 0; i--) {
        fieldName = sortingDetails[i][0];
        sortMeta = sortingDetails[i][1];
        fDetails = fieldInSchema(schema, fieldName);
 
        Iif (!fDetails) {
            // eslint-disable-next-line no-continue
            continue;
        }
 
        if (isCallable(sortMeta)) {
            // eslint-disable-next-line no-loop-func
            mergeSort(data, (a, b) => sortMeta(a[fDetails.index], b[fDetails.index]));
        } else if (isArray(sortMeta)) {
            const groupedData = groupData(data, fDetails.index);
            const sortingFn = sortMeta[sortMeta.length - 1];
            const targetFields = sortMeta.slice(0, sortMeta.length - 1);
            const targetFieldDetails = targetFields.map(f => fieldInSchema(schema, f));
 
            groupedData.forEach((groupedDatum) => {
                groupedDatum.push(createSortingFnArg(groupedDatum, targetFields, targetFieldDetails));
            });
 
            mergeSort(groupedData, (a, b) => {
                const m = a[2];
                const n = b[2];
                return sortingFn(m, n);
            });
 
            // Empty the array
            data.length = 0;
            groupedData.forEach((datum) => {
                data.push(...datum[1]);
            });
        } else {
            sortMeta = String(sortMeta).toLowerCase() === 'desc' ? 'desc' : 'asc';
            mergeSort(data, getSortFn(fDetails.type, sortMeta, fDetails.index));
        }
    }
 
    dataObj.uids = [];
    data.forEach((value) => {
        dataObj.uids.push(value.pop());
    });
}
 
 
/**
 * Builds the actual data array.
 *
 * @param {Array} fieldStore - An array of field.
 * @param {string} rowDiffset - A string consisting of which rows to be included eg. '0-2,4,6';
 * @param {string} colIdentifier - A string consisting of the details of which column
 * to be included eg 'date,sales,profit';
 * @param {Object} sortingDetails - An object containing the sorting details of the DataModel instance.
 * @param {Object} options - The options required to create the type of the data.
 * @return {Object} Returns an object containing the multidimensional array and the relative schema.
 */
export function dataBuilder (fieldStore, rowDiffset, colIdentifier, sortingDetails, options) {
    const defOptions = {
        addUid: false,
        columnWise: false
    };
    options = Object.assign({}, defOptions, options);
 
    const retObj = {
        schema: [],
        data: [],
        uids: []
    };
    const addUid = options.addUid;
    const reqSorting = sortingDetails && sortingDetails.length > 0;
    // It stores the fields according to the colIdentifier argument
    const tmpDataArr = [];
    // Stores the fields according to the colIdentifier argument
    const colIArr = colIdentifier.split(',');
 
    colIArr.forEach((colName) => {
        for (let i = 0; i < fieldStore.length; i += 1) {
            if (fieldStore[i].name === colName) {
                tmpDataArr.push(fieldStore[i]);
                break;
            }
        }
    });
 
    // Inserts the schema to the schema object
    tmpDataArr.forEach((field) => {
        /** @todo Need to use extend2 here otherwise user can overwrite the schema. */
        retObj.schema.push(field.schema);
    });
 
    Iif (addUid) {
        retObj.schema.push({
            name: 'uid',
            type: 'identifier'
        });
    }
 
    rowDiffsetIterator(rowDiffset, (i) => {
        retObj.data.push([]);
        const insertInd = retObj.data.length - 1;
        let start = 0;
        tmpDataArr.forEach((field, ii) => {
            retObj.data[insertInd][ii + start] = field.data[i];
        });
        Iif (addUid) {
            retObj.data[insertInd][tmpDataArr.length] = i;
        }
        // Creates an array of unique identifiers for each row
        retObj.uids.push(i);
 
        // If sorting needed then there is the need to expose the index
        // mapping from the old index to its new index
        if (reqSorting) { retObj.data[insertInd].push(i); }
    });
 
    // Handles the sort functionality
    if (reqSorting) {
        sortData(retObj, sortingDetails);
    }
 
    if (options.columnWise) {
        const tmpData = Array(...Array(retObj.schema.length)).map(() => []);
        retObj.data.forEach((tuple) => {
            tuple.forEach((data, i) => {
                tmpData[i].push(data);
            });
        });
        retObj.data = tmpData;
    }
 
    return retObj;
}