Source: visual-group/src/encoder/field-sanitizer.js

import { ComposedVars, SimpleVariable } from '../variable';
import { DIMENSION, COLUMNS, ROWS, TEMPORAL } from '../enums/constants';

/**
 * Gets the list of fields in a sorted order by measurement and dimension
 *
 * @param {Array} fieldList List of fields in the view
 * @param {Object} fieldMap Mapping of fields in the datamodel
 * @return {Array} fields sorted by measurement and dimensions
 */
const orderFields = (fieldArray, type) => {
    const dimensionArr = [[], []];
    const measureArr = [[], []];
    const temporalArr = [[], []];
    const categoricalArr = [[], []];

    fieldArray.forEach((fieldList, index) => {
        fieldList.forEach((field) => {
            if (field.type() === DIMENSION) {
                dimensionArr[index].push(field);
                field.subtype() === TEMPORAL ? temporalArr[index].push(field) : categoricalArr[index].push(field);
            } else {
                measureArr[index].push(field);
            }
        });
    });

    const numOfMeasures = measureArr[0].length + (measureArr[1] ? measureArr[1].length : 0);

    // Single array of fields
    if (!fieldArray[1]) {
        // Push measures to bottom
        measureArr[1] = type === COLUMNS ? measureArr[0] : [];
        // Push measures to left
        measureArr[0] = type !== COLUMNS ? measureArr[0] : [];
        // Bottom and right dimensions empty
        dimensionArr[1] = [];
        // Left and top filled with dimensions
        dimensionArr[0] = dimensionArr[0];
        if (numOfMeasures === 0) {
            const allDimensions = [...dimensionArr[0], ...dimensionArr[1]];
            if (type === COLUMNS) {
                dimensionArr[1] = allDimensions[allDimensions.length - 1] ? [allDimensions[allDimensions.length - 1]]
                        : [];
                allDimensions.splice(-1, 1);
            } else {
                dimensionArr[1] = [];
            }
            dimensionArr[0] = [...allDimensions];
        }
    }

    if (dimensionArr[0].length && dimensionArr[1].length && numOfMeasures > 0) {
        dimensionArr[0] = [...dimensionArr[0], ...dimensionArr[1]];
        dimensionArr[1] = [];
    }
    return {
        fields: dimensionArr.map((list, i) => (i === 1 ?
            measureArr[i].concat(dimensionArr[i]) : dimensionArr[i].concat(measureArr[i]))),
        dimensions: [...dimensionArr[0], ...dimensionArr[1]],
        measures: [...measureArr[0], ...measureArr[1]],
        temporal: [...temporalArr[0], ...temporalArr[1]],
        categorical: [...categoricalArr[0], ...categoricalArr[1]]
    };
};

/**
 * Gets the list of normalized fields
 *
 * @param {Array} fields List of fields in the view
 * @param {Object} fieldMap Mapping of fields in the datamodel
 * @return {Array} fields normalized by measurement and dimensions
 */
const normalizeFields = (config, type) => {
    const fieldsArr = [];
    const fields = config[type];

    if (!(fields[0] instanceof Array)) {
        fieldsArr[0] = fields;
    } else {
        fieldsArr[0] = fields[0] || [];
        fieldsArr[1] = fields[1] || [];
    }
    return fieldsArr;
};

/**
 *
 *
 * @param {*} fields
 * @param {*} datamodel
 *
 */
const convertToVar = (datamodel, fields) => {
    const vars = [];

    fields && fields.forEach((field) => {
        if (field instanceof ComposedVars) {
            vars.push(field);
            field.data(datamodel);
        } else {
            vars.push(new SimpleVariable(field).data(datamodel));
        }
    });
    return vars;
};

/**
 *
 *
 * @param {*} rows
 * @param {*} columns
 * @param {*} datamodel
 *
 */
export const transformFields = (datamodel, config) => {
    const [rowsInfo, columnsInfo] = [ROWS, COLUMNS].map((fields) => {
        const normalizedFields = normalizeFields(config, fields);
        const norFields = [convertToVar(datamodel, normalizedFields[0])];

        if (normalizedFields[1]) {
            norFields[1] = convertToVar(datamodel, normalizedFields[1]);
        }
        return orderFields(norFields, fields);
    });
    const {
        fields: rows,
        dimensions: rowDimensions,
        measures: rowMeasures,
        temporal: rowTemporalFields,
        categorical: rowCategoricalFields
    } = rowsInfo;
    const {
        fields: columns,
        dimensions: columnDimensions,
        measures: columnMeasures,
        temporal: columnTemporalFields,
        categorical: columnCategoricalFields
    } = columnsInfo;
    return {
        rows,
        rowDimensions,
        rowMeasures,
        rowTemporalFields,
        rowCategoricalFields,
        columns,
        columnTemporalFields,
        columnCategoricalFields,
        columnDimensions,
        columnMeasures
    };
};