Source: plot/utils.js

export { LodashCustomizer, readUTCDate, setAxisLimits }
import { merge, isArray } from "lodash";

/**
 * Implementation of lodash mergeWith customizer functions.
 * Usage: https://lodash.com/docs/4.17.15#mergeWith.
 */
class LodashCustomizer {

    /**
     * Change the behavior of lodash array concatenation for deep merge.
     * By default the lodash merge function follows the behavior of jQuery's deep extend.
     * With this method, arrays are concatenated.
     * @param {*} objValue - First argument of lodash mergeWith function.
     * @param {*} srcValue - Second argument of lodash mergeWith function.
     * @returns Concatenated Array.
     */
    static concatArrays(objValue, srcValue) {
        if (isArray(objValue)) {
            return objValue.concat(srcValue);
        }
    }

    /**
     * Modifies lodash merge algorithm for values.
     * If a regex expression matches for a specific key (defined in the patternDict) during the merge process, instead of replacing the srcValue by the objValue, the srcValue is kept.
     * Consequently, for the predefined keys in patternDict, if objValue matches the pattern in patternDict, srcValue is returned. Else, objValue.
     * @param {*} objValue - First argument of lodash mergeWith function.
     * @param {*} srcValue - Second argument of lodash mergeWith function.
     * @param {String} key - Third argument of lodash mergeWith function.
     * @param {{}} [patternDict={}] - Contain the assignment of key names and regex patterns: { objValue1: pattern1, objValue2: pattern1, ...}, i.e., { mimeType: "^$", photoWidth: "^0$", ...}.
     * @returns Derived value.
     */
    static overwriteValues(objValue, srcValue, key, patternDict = {}) {
        if (key != null && key in patternDict) {
            if (new RegExp(patternDict[key]).test(objValue)) {
                return srcValue
            }
            else return objValue
        }
    }
}


/**
 * Convert a time string to a UTC date object.
 * Observe: Does not change time zones.
 * @param {String} date UTC Date in ISO time format.
 * @returns {Date} Date time object.
 */
function readUTCDate(date) {
    const zuluDate = date.toLowerCase().slice(-1) == 'z' ? date : date + 'Z';
    return new Date(zuluDate)
}

/**
 * For each plotly axis, set axis limits with respect to the data.  
 * @param {Array} data - Plotly data config.
 * @param {Object} layout - Plotly layout config.
 * @param {Number} [offsetFactor] - Space between axis and data point.
 * @returns {Object} Updated layout config.
 */
function setAxisLimits(data, layout, offsetFactor = 0.02) {
    const dataLimits = data.reduce((acc, curr, idx) => {
        let axisCollect = acc;
        const dataPoints = [curr.x, curr.y];
        dataPoints.forEach((values, index) => {
            if (axisCollect[index] == undefined)
                axisCollect[index] = { 'minValue': [], 'maxValue': [] };
            if (values) {
                if (isFinite(Math.min(...values))) {
                    axisCollect[index]['minValue'].push(Math.min(...values));
                    axisCollect[index]['maxValue'].push(Math.max(...values));
                }
                else {
                    axisCollect[index]['minValue'].push(new Date(values.at(0)));
                    axisCollect[index]['maxValue'].push(new Date(values.at(-1)));
                }
            }
            else {
                axisCollect[index]['minValue'].push(undefined);
                axisCollect[index]['maxValue'].push(undefined);
            }
        })
        return axisCollect
    }, [])


    const layoutPlotly = dataLimits.reduce((acc, curr, idx) => {

        let areDateObjectsOnAxis = curr['minValue'].some((item) => item instanceof Date)
        const axisMinValue = Math.max(...curr['minValue'])
        const axisMaxValue = Math.max(...curr['maxValue'])

        const offsetValue = (axisMaxValue - axisMinValue) * offsetFactor;

        let startValueWithOffset = axisMinValue - offsetValue;
        let endValueWithOffset = axisMaxValue + offsetValue;

        if (areDateObjectsOnAxis) {
            startValueWithOffset = new Date(startValueWithOffset).toISOString();
            endValueWithOffset = new Date(endValueWithOffset).toISOString();
        }
        const axisTypes = ['xaxis', 'yaxis', 'zaxis'];
        const currAxisType = axisTypes[idx];

        const layout = acc;
        if (axisMinValue != axisMaxValue) {
            layout[currAxisType] = {
                "range": [
                    startValueWithOffset,
                    endValueWithOffset
                ],
                minallowed: startValueWithOffset,
                maxallowed: endValueWithOffset
            };
        }
        return layout
    }, {})

    const layoutUpdated = merge(layoutPlotly, layout)
    return layoutUpdated
}