Source: transform/src/stack-transform/index.js

import { stack } from 'muze-utils';

import group from '../group-transform';
/*
    Normalize the data to a form which can be given to d3 stack for stacking the data
    ['Product', 'Sales', 'Region'],
    0 ['A', 2000, 'North'],
    1 ['A', 3000, 'South'],
    2 ['B', 3000, 'South'],
    3 ['B', 2000, 'North'],
    4 ['C', 3000, 'South'],
    5 ['C', 2000, 'North']
            |
            |
    ['Product', 'North', 'South'],
    ['A', 2000, 3000],
    ['B', 2000, 3000],
    ['C', 2000, 3000]
*/
// eslint-disable-next-line require-jsdoc
const normalizeData = (data, schema, valueField, uniqueField, groupBy) => {
    const groupedData = group(schema, data, {
        groupBy: uniqueField
    });
    const uniqueFieldIndex = schema.findIndex(d => d.name === uniqueField);
    const valueFieldIndex = schema.findIndex(d => d.name === valueField);
    const seriesKeyIndex = schema.findIndex(d => d.name === groupBy);
    const seriesKeys = data.map(d => d[seriesKeyIndex]).filter((item, pos, arr) => arr.indexOf(item) === pos).sort();
    const fieldNames = schema.reduce((acc, obj, i) => {
        acc[i] = obj.name;
        return acc;
    }, {});
    const dataArr = groupedData.map((arr) => {
        const tuples = {};
        const rowObj = arr.values.reduce((acc, row) => {
            acc = row.reduce((obj, value, i) => {
                if (i === seriesKeyIndex) {
                    obj[value] = row[valueFieldIndex];
                    tuples[value] = row;
                } else if (i !== valueFieldIndex) {
                    obj[fieldNames[i]] = value;
                }
                return obj;
            }, acc);
            return acc;
        }, {});
        rowObj._tuple = tuples;
            // Set missing values field to zero value
        seriesKeys.forEach((seriesKey) => {
            if (rowObj[seriesKey] === undefined) {
                rowObj[seriesKey] = 0;
                const newArr = new Array(arr.values[0].length);
                newArr[uniqueFieldIndex] = arr.key;
                newArr[seriesKeyIndex] = seriesKey;
                rowObj._tuple[seriesKey] = newArr;
            }
        });
        return rowObj;
    });

    return {
        data: dataArr,
        keys: seriesKeys
    };
};
/**
 * Generate a stacked representation of data
 * @param {Array} schema schema Array
 * @param {Array} data data array
 * @param {Object} config Configuration object
 * @return {Array} stacked data
 */
export default (schema, data, config) => {
    const uniqueField = config.uniqueField;
    const valueField = config.value;
    const groupBy = config.groupBy;
    const sort = config.sort || 'descending';
    const normalizedData = normalizeData(data, schema, valueField, uniqueField, groupBy);
    const keys = normalizedData.keys;
    const map = {};
    const orderBy = config.orderBy;
    const orderIndex = schema.findIndex(d => d.name === orderBy);
    const groupByIndex = schema.findIndex(d => d.name === groupBy);
    if (orderIndex !== -1) {
        keys.forEach((key) => {
            const name = data.find(d => d[groupByIndex] === key);
            map[key] = name[orderIndex];
        });
        normalizedData.keys.sort((a, b) => map[a].localeCompare(map[b]));
    }

    const stackData = stack({
        keys: normalizedData.keys,
        offset: config.offset || 'diverging',
        order: sort,
        data: normalizedData.data
    });
    stackData.forEach((seriesData) => {
        seriesData.forEach((dataObj) => {
            dataObj.data = dataObj.data._tuple[seriesData.key];
        });
    });
    return stackData;
};