Source: visual-group/src/group-helper/group-utils.js

import { Store, dataSelect, FieldType } from 'muze-utils';
import { DATA_UPDATE_COUNTER } from '../enums/defaults';
import { Variable } from '../variable';
import { PolarEncoder, CartesianEncoder } from '../encoder';
import {
    DIMENSION,
    MEASURE,
    ORDINAL,
    LINEAR,
    ROW,
    COLUMN,
    COL,
    LEFT,
    RIGHT,
    TOP,
    BOTTOM,
    COLOR,
    SIZE,
    TEMPORAL,
    SHAPE,
    INTERACTION,
    GRID_LINES,
    GRID_BANDS,
    HEADER,
    FACET,
    X,
    Y,
    POLAR
} from '../enums/constants';

/**
 * Creates an instance of a store which contains the arguments to make the class reactive
 *
 * @return {Object} instance of store
 * @memberof VisualGroup
 */
export const initStore = () => new Store({
    [DATA_UPDATE_COUNTER]: DATA_UPDATE_COUNTER
});

/**
 *
 *
 * @param {*} arr
 */
export const isDistributionEqual = arr => [...arr[0], ...arr[1]].reduce((isEqual, row) => {
    const rowType = row.type();

    if (rowType === MEASURE || rowType === TEMPORAL) {
        isEqual = true;
    }
    return isEqual;
}, false);

/**
 *
 *
 */
export const initializeCacheMaps = () => ({
    cellMap: new Map(),
    xAxesMap: new Map(),
    yAxesMap: new Map(),
    entryCellMap: new Map(),
    exitCellMap: new Map()
});

/**
 *
 *
 * @param {*} axisName
 * @param {*} id
 */
export const getAxisKey = (axisName, id, scaleType) => `${axisName}-axis-${id}-${scaleType}`;

/**
 *
 *
 * @param {*} rowId
 * @param {*} columnId
 */
export const getCellKey = (rowId, columnId) => `cell-${rowId}-${columnId}`;

/**
 *
 *
 * @param {*} config
 *
 */
export const extractUnitConfig = (config) => {
    const unitConfig = {};
    const attrNames = [INTERACTION, GRID_LINES, GRID_BANDS];

    attrNames.forEach((attr) => {
        if (config[attr] !== undefined) {
            unitConfig[attr] = config[attr];
        }
    });
    return unitConfig;
};

/**
 *
 *
 * @param {*} headers
 * @param {*} index
 * @param {*} rowLength
 *
 * @memberof MatrixResolver
 */
export const getHeaderText = (headers, index, rowLength) => {
    let header = '';

    if (index === rowLength - 1 && headers.length > rowLength) {
        for (let i = index; i < headers.length - 1; i++) {
            header += `${headers[i].toString()} / `;
        }
        header += headers[headers.length - 1].toString();
        return header;
    } else if (headers[index]) {
        return headers[index].toString();
    }
    return '';
};

/**
 *
 *
 * @param {*} fields
 * @param {*} fieldHeaders
 * @param {*} TextCell
 * @param {*} labelManager
 *
 */
export const headerCreator = (fields, fieldHeaders, TextCell, labelManager) => {
    const headers = fields.length > 0 ? fields[0].map((cell, i) => new TextCell({ type: HEADER }, { labelManager })
                    .source(getHeaderText(fieldHeaders, i, fields[0].length))
                    .config({ show: cell.config().show })) : [];
    return headers;
};

/**
 *
 *
 * @param {*} variable
 * @param {*} allFields
 *
 */
export const findInGroup = (variable, allFields) => {
    let channel = null;

    [COLOR, SIZE, SHAPE].forEach((e) => {
        if (this.store.get(e) && variable === this.store.get(e)[0]) {
            channel = e;
        }
    });

    if (channel) {
        return { channel };
    }
    [ROW, COL].forEach((facetType) => {
        allFields[`${facetType}Facets`].forEach((e) => {
            if (e.toString() === variable) {
                channel = { channel: FACET, type: facetType === COL ? COLUMN : ROW };
            }
        });
    });

    if (channel) {
        return channel;
    }

    [ROW, COL].forEach((projType) => {
        allFields[`${projType}Projections`].forEach((e) => {
            e.forEach((m) => {
                if (m.toString() === variable) {
                    channel = projType === COL ? X : Y;
                }
            });
        });
    });

    if (channel) {
        return { channel };
    }
    return null;
};

/**
 *
 *
 * @param {*} datamodel
 * @param {*} field
 */
export const getAxisType = (fieldsConfig, field) => {
    let fieldType = ORDINAL;

    if (field && fieldsConfig[field].def.type !== DIMENSION) {
        fieldType = LINEAR;
    }
    return fieldType;
};

/**
 *
 *
 * @param {*} datamodel
 * @param {*} fieldName
 *
 */
export const retriveDomainFromData = (datamodel, fieldName) => {
    const field = datamodel.getFieldspace().fields.find(d => d._ref.name === fieldName.toString());
    return field.domain();
};

/**
 *
 *
 * @memberof MatrixResolver
 */
export const mutateAxesFromMap = (cacheMaps, axes) => {
    let xAxes = null;
    let yAxes = null;
    const {
        xAxesMap,
        yAxesMap
    } = cacheMaps;
    const {
        x: xAxisSet,
        y: yAxisSet
    } = axes;

    xAxisSet && xAxisSet.forEach((axisId) => {
        const xAxis = xAxesMap.get(axisId);
        xAxes = xAxes || [];
        xAxes.push(xAxis);
    });

    yAxisSet && yAxisSet.forEach((axisId) => {
        const yAxis = yAxesMap.get(axisId);
        yAxes = yAxes || [];
        yAxes.push(yAxis);
    });

    return {
        xAxes, yAxes
    };
};

/**
 *
 *
 * @param {*} layers
 *
 */
export const getEncoder = (layers) => {
    let encoder = new CartesianEncoder();

    if (layers) {
        // Figuring out the kind of layers the group will have
        encoder = layers.every(e => e.mark === 'arc') ? new PolarEncoder() : encoder;
    }
    return encoder;
};

/**
 *
 *
 * @param {*} type
 * @param {*} fields
 * @param {*} userAxisFromConfig
 *
 */
export const getHeaderAxisFrom = (type, fields, userAxisFromConfig) => {
    let axisFrom = userAxisFromConfig[type];
    let headerFrom = '';
    const options = type === ROW ? [LEFT, RIGHT] : [BOTTOM, TOP];
    const [firstField, secondField] = fields;
    const firstFieldType = i => (firstField.length ? firstField[i].type() : null);
    const secondFieldType = i => (secondField.length ? secondField[i].type() : null);

    if (firstFieldType(firstField.length - 1) === DIMENSION && secondFieldType(0) === DIMENSION) {
        headerFrom = axisFrom ? options[1 - options.indexOf(axisFrom)] : options[1];

        if (type === COLUMN && (firstField[firstField.length - 1].toString() === secondField[0].toString())) {
            axisFrom = TOP;
        } else {
            axisFrom = axisFrom || options[0];
        }
    } else if (secondFieldType(secondField.length - 1) === DIMENSION) {
        headerFrom = type === ROW ? RIGHT : BOTTOM;
        axisFrom = type === ROW ? RIGHT : BOTTOM;
    } else {
        headerFrom = type === ROW ? LEFT : TOP;
        axisFrom = type === ROW ? LEFT : TOP;
    }
    if (firstFieldType(firstField.length - 1) === MEASURE && secondFieldType(0) === MEASURE) {
        axisFrom = type === ROW ? LEFT : TOP;
    } else if (secondFieldType(0) === MEASURE) {
        axisFrom = type === ROW ? RIGHT : BOTTOM;
    } else if (firstFieldType(firstField.length - 1) === MEASURE) {
        axisFrom = type === ROW ? LEFT : TOP;
    }
    return [headerFrom, axisFrom];
};

/**
 *
 *
 * @param {*} type
 * @param {*} fields
 * @param {*} layers
 *
 * @memberof MatrixResolver
 */
export const setFacetsAndProjections = (context, fieldInfo, encoder) => {
    const {
        fields,
        type
    } = fieldInfo;
    const { facets, projections } = encoder.simpleEncoder.getFacetsAndProjections(fields, type);

    context.facets({ [`${type}Facets`]: facets });
    context.projections({ [`${type}Projections`]: projections });

    return { facets, projections, fields };
};

/**
 * Creates a selection set from a data set with corresponding attributes
 *
 * @export
 * @param {Selection} sel contains previous selection
 * @param {Object} appendObj Object to be appended
 * @param {Array} data Data based on which the selection is entered/updated/removed
 * @param {Object} [attrs={}] Attributes to be set on the data
 * @return {Selection} Merged selection
 */
export const createSelection = (sel, appendObj, data, idFn) => {
    let selection = sel || dataSelect([]);

    selection = selection.data(data, idFn);

    const enter = selection.enter().append(appendObj);
    const mergedSelection = enter.merge(selection);

    selection.exit() && selection.exit().remove();
    return mergedSelection;
};

const getRowBorders = (left, right) => {
    const borders = {};
    borders.top = false;
    borders.bottom = false;
    if (left.length > 1 || right.length > 1) {
        borders.top = true;
        borders.bottom = true;
    }
    return borders;
};

const getColumnsBorders = (top, bottom) => {
    const borders = {};
    borders.left = false;
    borders.right = false;
    if (top.length || bottom.length) {
        if ((top[0] && top[0].length > 1) || (bottom[0] && bottom[0].length > 1)) {
            borders.left = true;
            borders.right = true;
        }
    }
    return borders;
};

const getValueBorders = (rows, columns) => {
    const borders = { top: true, left: true, bottom: true, right: true };
    const borderTypeRow = [LEFT, RIGHT];
    const borderTypeCol = [TOP, BOTTOM];
    rows.forEach((e, i) => {
        if (e[0] && e[0].length) {
            borders[borderTypeRow[i]] = true;
        } else {
            borders[borderTypeRow[i]] = false;
        }
    });
    columns.forEach((e, i) => {
        if (e.length) {
            borders[borderTypeCol[i]] = true;
        } else {
            borders[borderTypeCol[i]] = false;
        }
    });

    return borders;
};

export const getBorders = (matrices, encoder) => {
    let showRowBorders = { top: false, bottom: false };
    let showColBorders = { left: false, right: false };
    let showValueBorders = { top: false, bottom: false, left: false, right: false };
    const {
        rows,
        columns,
        values: valueMatrix
    } = matrices;
    const [leftRows, rightRows] = rows;
    const [topColumns, bottomColumns] = columns;
    const {
        rowDimensions,
        columnDimensions,
        rowTemporalFields,
        columnTemporalFields,
        columnMeasures,
        rowMeasures
    } = encoder.fieldInfo();
    const allDimensionLength = rowDimensions.length + columnDimensions.length;
    const allMeasuresLength = rowMeasures.length + columnMeasures.length;
    const allTemporalFieldsLength = rowTemporalFields.length + columnTemporalFields.length;

    if (encoder.constructor.type() === POLAR) {
        if (!allDimensionLength) {
            return { showRowBorders, showColBorders, showValueBorders };
        }
    } else if (!allMeasuresLength && !allTemporalFieldsLength && allDimensionLength <= 2) {
        return { showRowBorders, showColBorders, showValueBorders };
    }

    showRowBorders = getRowBorders(leftRows, rightRows);
    showColBorders = getColumnsBorders(topColumns, bottomColumns);
    showValueBorders = getValueBorders([leftRows, rightRows], [topColumns, bottomColumns]);

    if (valueMatrix.length > 1) {
        showValueBorders.top = true;
        showValueBorders.bottom = true;
    }
    if (valueMatrix.length && valueMatrix[0].length > 1) {
        showValueBorders.left = true;
        showValueBorders.right = true;
    }
    return { showRowBorders, showColBorders, showValueBorders };
};

export const getFieldsFromSuppliedLayers = (suppliedLayerConfig, fieldsConfig) => {
    let fields = [];
    const encodingArr = suppliedLayerConfig.map(conf => (conf.encoding || {}));
    fields = [...fields, [].concat(...encodingArr.map(enc => Object.values(enc).map(d => d.field)))];
    fields = fields.filter(field => fieldsConfig[field] && fieldsConfig[field].def.type === FieldType.DIMENSION);
    return fields;
};

export const extractFields = (facetsAndProjections, layerFields) => {
    const fields = Object.values(facetsAndProjections).map((arr) => {
        const flattenArray = [].concat(...arr);
        return [].concat(...flattenArray.map((field) => {
            if (field instanceof Variable) {
                return field.getMembers();
            }
            return field;
        }));
    });
    return [].concat(...fields, ...layerFields);
};