Source

constants/graphFunctions/graphFunctions.js

import Rainbow from 'rainbowvis.js';

/**
 * Takes an array of (numbers/strings/dates) and find the number of occurrences of each distinct element.
 * it returns a Map (key,value) pairs. Key is the element and Value is the number of occurrences.
 * @method
 * @param {Array} arr  Takes an array of numbers/strings/dates to find the number of Occurrences of each distinct element.
 * @returns {Map} Returns Map, where keys are the elements in the array and, value is the number of Occurrences of each element in the array.
 * @example 
 *  mostOccurrences(['July','Aug','Sept','July','June']) 
 * return  (
 *   Map(['July',2],['Aug',1],['Sept',1],['June',1])
 * )
 */
export const mostOccurrences = (arr) => {
    try {
        let map = new Map();
        if (arr !== undefined && Array.isArray(arr) && arr.length >= 0) {
            while (arr.length !== 0) {
                if (!map.has(arr[0])) {
                    map.set(arr[0], 1);
                } else {
                    map.set(arr[0], map.get(arr[0]) + 1);
                }
                arr.shift();
            }
        }
        return map;
    } catch {
        console.log('Error: Data Array was empty, cannot calculate the Occurrences of null')
        return;
    }
};

/**
 * Converts the Array of Swimmer Objects.__EMPTY_10 property (Meet Month is stored in excel date value),
 * into a usable month to be viewed and compared with against. Where the month is in [0,1,2,3,4,..11] (with 0 being january and so on)
 * @method
 * @param {object[]} meets Takes an Array of Swimmer Objects to convert dates on.
 * @returns {object[]}   Where .__EMPTY_10 is now a useable month from JS Date.
 * @example 
 * meetMonth([Object('__EMPTY_':..., '__EMPTY_10': 36949)]) // * __EMPTY_10 is in excel date value
 * 
 * [Object('__EMPTY_':..., '__EMPTY_10': 36949)] // * __EMPTY_10 is in excel date value
 * 
 * return (
 * [Object('__EMPTY_1':..., '__EMPTY_10': 0)] // * __EMPTY_10 is now a single value contaning the month corresponding months
 * ) 
 *
 */
export const meetMonth = (meets) => {
    try {
        return meets.map(date => new Date(Math.floor(date.__EMPTY_10 - (25567 + 2)) * 86400 * 1000).getMonth());
    } catch {
        console.log('Error: Unable to map the array');
        return;
    }
}

/**
 *  Takes the Array of Swimmer Objects and converts the months to be aligned on the graph correctly. Also finds the most Occurrences of the month data.
 * @method
 * @param {object[]} data  Takes an Array of Swimmer Objects to convert dates and map.
 * @returns {Array} Returns an Array where index[0] is September and the values of Occurrences in September is the value at the index.
 * @example
 * peakDistribution(Object('__EMPTY_':..., '__EMPTY_10': 36949), Object('__EMPTY_':..., '__EMPTY_10': 36949), Object('__EMPTY_':..., '__EMPTY_10': 36949))
 * // 36949 -> January
 * return (
 *  // [sept,oct,nov,dec,jan,feb,mar,apr,may,june,july,aug]
 *  [0,0,0,0,3,0,0,0,0,0,0,0]
 * )
 */
export const peakDistribution = (data) => {
    try {
        // * Data for most common meet occurence month
        let months = Array(12).fill(0);
        let meets = mostOccurrences(meetMonth(data));
        meets.forEach((value, key) => {
            months[key] = value
        });

        // * Shifts the Months so that is graphed about September-August
        for (let i = 0; i < 4; i++) {
            months.unshift(months.pop());
        }
        return months;
    } catch {
        console.log('Error: Unable to map the array');
        return;
    }
}

/**
 * Converts an Array of Numbers (times) to a date string in the format MM:SS.sss where the Time is the Average Time of the Array Input.
 * @method
 * @param {Number[]} time An Array of Numbers (Times)
 * @returns {string} datestring - Returns a readable date string based on the average of the array of times.
 * @example
 *  averageTime([30000,25000,35000])
 *  return (
 *      00:30.00
 * )
 */
export const averageTime = (time) => {
    try {
        time.sort((a, b) => a - b);
        let average = time.reduce((a, b) => a + b);
        return new Date(average / time.length).toISOString().substr(14, 8);
    }
    catch {
        console.log('Error: Empty Data Array')
    }
}

/**
 * Converts an Array of Numbers (times) to a date string in the format MM:SS.sss where the Time is the Median of the Array
 * @method
 * @param {Number[]} time  An Array of Numbers (Times)
 * @param {Number[]} time  An Array of Numbers (Times)
 * @returns {string} datestring - Returns a readable date string based on the median of the array of times.
 * @example
 *  medianTime([30000,25000,35000])
 *  return (
 *      00:30.00
 * )
 */
export const medianTime = (time) => {
    try {
        // * Ensure data is in correct order to find median, must be sorted least to greatest
        time.sort((a, b) => a - b);
        let index = Math.floor(time.length / 2);
        let median;
        // * If the length % 2 === 0 (aka even number)
        if (time.length % 2 === 0 && time.length !== 0) {
            // * Minus one is needed for correct array index of even number centre points
            median = (time[index - 1] + time[index]) / 2;
        } else if (time.length % 2 === 1) {
            median = time[index];
        }
        return new Date(median).toISOString().substr(14, 8)
    } catch {
        console.log('Error: Empty Data Array')
    }
}

/**
 * Finds the Mode an Array of Numbers (times) and returns an object with the Mode Time and the number of Occurrences.
 * @method
 * @param {Number[]} time An Array of Numbers (Times)
 * @returns {object} {mostCommonNumber} Returns The Most Common Number in Array
 * @returns {object} {maxCount} Returns the count of the Most Common Number
 * @example 
 *  modeTime([30000,25000,35000,30000])
 *  return (
 *  {
 *   "mostCommonNumber": 30,
 *   "maxCount": 2
    }
 * )
 */
export const modeTime = (time) => {
    try {
        // * Times is given in Milliseconds, and from lowest to highest. We will convert to seconds and round down to standardize mode to seconds
        let toModeSeconds = time.map(el => Math.floor(el / 1000));
        let modeOccurence = mostOccurrences(toModeSeconds);

        // * Iterates through the map and finds the most occuring time
        let mostCommonNumber = NaN;
        let maxCount = -1;
        for (let [num, count] of modeOccurence.entries()) {
            if (count > maxCount) {
                maxCount = count;
                mostCommonNumber = num;
            }
        }
        return { mostCommonNumber, maxCount };
    } catch {
        console.log('Error: Empty Data Array')
    }
}

/**
 *  Takes an Number[] times and calculates the Standard Deviation for the race times, returns a date string.
 *  Generally time is input in Milliseconds.
 * @method
 * @param {Number[]} time An Array of Numbers (times)
 * @return {string} standardDev returns the date string with the standard deviation for data.
 * @example 
 * // returns 00:03.30 (aka. 00:03.30 of standard deviation between time given in ms)
 * standardDeviation([ 9000, 2000, 5000, 4000, 12000, 7000]) 
 * return (
 *      00:03.30
 * )
 */
export const standardDeviation = (time) => {
    try {
        if (time === undefined || !Array.isArray(time) || time.length === 0) {
            return '';
        }
        let average = time.reduce((a, b) => a + b);
        let av = (average / time.length);
        let standardDev = time.map(t => Math.pow((t - av), 2));
        standardDev = Math.sqrt(standardDev.reduce((a, b) => a + b) / time.length);
        return new Date(standardDev).toISOString().substr(14, 8);
    } catch (e) {
        console.log(e);
    }
}

/**
 *  Takes an Array of Objects and returns an Array of all meets in each meet in the respective Objects.
 * @method
 * @param {object[]} meetcitys - An Object Array of all entities in the respective excel sheet to for the Swimming Race. 
 * @returns {String[]}  
 * @example
 *  meetCity(Object('__EMPTY_':..., '__EMPTY_11': 'Toronto'), Object('__EMPTY_':..., '__EMPTY_11': 'Winnipeg' ), Object('__EMPTY_':..., '__EMPTY_11': 'Toronto'))
 *  return (
 *      ['Toronto', 'Winnipeg', 'Toronto']
 * )
 */
export const meetCity = (meetcitys) => {
    if (Array.isArray(meetcitys)) {
        return meetcitys.map(meetcity => meetcity.__EMPTY_11);
    }
    return [];
}

/**
 *  Takes an Array of Objects and returns an Array of all meet names from each object
 * @method
 * @param {object[]} meets - An Object Array of all entities in the respective excel sheet to for the Swimming Race. 
 * @returns {String[]} meets
 * @example
 *  meetName(Object('__EMPTY_':..., '__EMPTY_12': 'Victor Davis'), Object('__EMPTY_':..., '__EMPTY_12': 'Dash For Cash' ), Object('__EMPTY_':..., '__EMPTY_12': 'HOBBS'))
 *  return (
 *      ['Victor Davis', 'Dash For Cash', 'HOBBS']
 * )
 */
export const meetName = (meets) => {
    if (Array.isArray(meets)) {
        return meets.map(meet => meet.__EMPTY_12);
    }
    return [];
}

// * Ensures that all time strings given are in an appropriate ISO String format
export const standardizeTimes = (time) => {
    if (time.length === 5) time = '00:' + time;
    if (time.length === 7) time = '0' + time;
    let milli = ((parseInt(time.split(':')[0] * 60000)) + (parseInt(time.split(':')[1].split('.')[0] * 1000)) + (parseInt(time.split('.')[1]) * 10));
    return milli;
}

/**
 * Color Array takes input arrayLength (Number). It returns an array of gradient colors the same length as the input. 
 * Useful for creating a gradient of colours for a data set that will differ but match a color scheme / theme.
 * @method
 * @param {Number} arrayLength  - The length of the Data Array denoting, how many colours along the gradient will be needed.
 * @returns {String[]} colorArray
 * @example
 *  colorArray(10)
 *  return (
 *      ["#00aad8", "#1aa3d0", "#339cc7", "#4d95bf", "#668eb6", "#8087ae", "#997fa6", "#b3789d", "#cc7195","#e66a8c"]
 * )
 */
export const colorArray = (arrayLength) => {
    if (typeof arrayLength === 'number' && arrayLength >= 1) {
        // * Creates The Colors for the Component depending on how many distinct items there are in the array
        let myRainbow = new Rainbow();
        myRainbow.setSpectrum('#00aad8', '#ff6384')
        myRainbow.setNumberRange(0, arrayLength);
        let colorArray = [];
        for (let i = 0; i < arrayLength; i++) {
            colorArray.push('#' + myRainbow.colorAt(i));
        }
        return colorArray;
    }
    return [];
}