Skip to content
Snippets Groups Projects
utils.js 8.43 KiB
Newer Older
import {bisector} from 'd3-array';
import {select} from 'd3-selection';
/**
 * Determine the unicode variant of an HTML decimal entity
 *
 * @param someString
 * @returns {string}
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const unicodeHtmlEntity = function(someString) {
    let numericValue = parseInt(someString.slice(2, -1), 10);
    if (numericValue) {
        return String.fromCharCode(numericValue);
Naab, Daniel James's avatar
Naab, Daniel James committed
    } else {

/**
 * Determine if a string contains an HTML decimal entity
 *
 * @param someString
 * @returns {array} or {null}
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const getHtmlFromString = function(someString) {
    let re = /&(?:[a-z]+|#\d+);/g;
    return someString.match(re);
/**
 * Replace html entities with unicode entities
 *
 * @param someString
 * @returns {*}
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const replaceHtmlEntities = function(someString) {
    let entities = getHtmlFromString(someString);
Yan, Andrew N.'s avatar
Yan, Andrew N. committed
    if (entities) {
        for (let entity of entities) {
            let unicodeEntity = unicodeHtmlEntity(entity);
            someString = someString.replace(entity, unicodeEntity);
        }
    }
    return someString;
/**
 * Determine if two sets are equal
 *
 * @param set1
 * @param set2
 * @returns {boolean}
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const setEquality = function(set1, set2) {
    let sizeEqual = set1.size === set2.size;
    let itemsEqual = Array.from(set1).every(x => {
Naab, Daniel James's avatar
Naab, Daniel James committed
        return set2.has(x);
    });
    return sizeEqual && itemsEqual;

const TEXT_WRAP_LINE_HEIGHT = 1.1;  // ems
//const TEXT_WRAP_BREAK_CHARS = ['/', '&', '-'];
const TEXT_WRAP_BREAK_CHARS = [];
/**
 * Wrap long svg text labels into multiple lines.
 * Based on: https://bl.ocks.org/ericsoco/647db6ebadd4f4756cae
Bucknell, Mary S.'s avatar
Bucknell, Mary S. committed
 * @param  {D3 selection} text
 * @param  {Number} width
Bucknell, Mary S.'s avatar
Bucknell, Mary S. committed
 * @param {Array of Strings} break_chars - these along with spaces are acceptable places to break on}
export const wrap = function(text, width, break_chars = TEXT_WRAP_BREAK_CHARS) {
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
    text.each(function() {
        const elem = select(this);

        // To determine line breaks, add a space after each break character
        let textContent = elem.text();
            textContent = textContent.replace(char, char + ' ');
        });

        let x = elem.attr('x');
        let y = elem.attr('y');
        let dy = parseFloat(elem.attr('dy') || 0);

        let tspan = elem
            .text(null)
            .append('tspan')
                .attr('x', x)
                .attr('y', y)
                .attr('dy', dy + 'em');

        // Iteratively add each word to the line until we exceed the maximum width.
        let line = [];
        let lineCount = 0;
Bucknell, Mary S.'s avatar
Bucknell, Mary S. committed
        const words = textContent.split(/\s+/);
        for (const word of words) {
            line.push(word);
            tspan.text(line.join(' '));

            // If we exceeded the line width, remove the last word from the array
            // and append this tspan to the DOM node.
Bucknell, Mary S.'s avatar
Bucknell, Mary S. committed
            let tspanNode = tspan.node();
            if (tspanNode.getComputedTextLength() > width) {
                // Remove the last word and put it on the next line.
                let spanContent = line.join(' ');
                line = [word];

                // Remove the spaces trailing break characters that were added above
                    spanContent = spanContent.replace(char + ' ', char);
                });

                // Insert this text as a tspan
                lineCount++;
                tspan.text(spanContent);
                tspan = elem
                    .append('tspan')
                        .attr('x', x)
                        .attr('y', y)
                        .attr('dy', lineCount * TEXT_WRAP_LINE_HEIGHT + dy + 'em')
                        .text(word);

/**
 * This will execute the equivalent media query as the USWDS given a minWidth.
 * For example, this SASS:
 *     @media($medium-screen)
 * is equivalent to:
 * @param  {Number} minWidth
 * @return {Boolean} true if the media query is active at the given width
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const mediaQuery = function(minWidth) {
    return window.matchMedia(`screen and (min-width: ${minWidth}px)`).matches;
/**
 * Returns a function that will be a no-op if a condition is false.
 * Use to insert conditionals into declarative-style D3-chained method calls.
 * @param  {Boolean} condition If true, will run `func`
 * @param  {Function} func
 */
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
export const callIf = function(condition, func) {
    return function(...args) {
 * @returns {Array of Objects} - one for each data line in the RDB.
 */
export const parseRDB = function(rdbData) {
    const rdbLines = rdbData.split('\n');
    let dataLines = rdbLines.filter(rdbLine => rdbLine[0] !== '#').filter(rdbLine => rdbLine.length > 0);
    // remove the useless row
    dataLines.splice(1, 1);
    let recordData = [];
    if (dataLines.length > 0) {
        let headers = dataLines.shift().split('\t');
        for (let dataLine of dataLines) {
            let data = dataLine.split('\t');
            let dataObject = {};
            for (let i = 0; i < headers.length; i++) {
                dataObject[headers[i]] = data[i];
            }
            recordData.push(dataObject);
        }
    }
    return recordData;

/*
 * Convert a temperature measurement in fahrenheit to celsius.
 * @param {String or Number} fahrenheit
 * @returns {Number} measurement in celsius
 */
export const convertFahrenheitToCelsius = function(fahrenheit) {
    return (fahrenheit - 32) * 5 / 9;
};

/*
 * Convert a temperature measurement in celsius to fahrenheit.
 * @returns {Number} measurement in fahrenheit
 */
export const convertCelsiusToFahrenheit = function(celsius) {
    return celsius * 9 / 5 + 32;
};

/*
 * Return the variables sorted with the ones we care about first
 * @param {Array of variable Object}
 * @return {Array of variable Object}
export const sortedParameters = function(parameters) {
        '00065': 0,
        '00060': 1,
    const allParameters = parameters ? Object.values(parameters) : [];
    const pertinentParmCds = Object.keys(PARAM_PERTINENCE);
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
    const highPertinenceVars = allParameters
        .filter(x => pertinentParmCds.includes(x.parameterCode))
            const aPertinence = PARAM_PERTINENCE[a.parameterCode];
            const bPertinence = PARAM_PERTINENCE[b.parameterCode];
            if (aPertinence < bPertinence) {
                return -1;
            } else {
                return 1;
            }
        });
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
    const lowPertinenceVars = allParameters
        .filter(x => !pertinentParmCds.includes(x.parameterCode))
            const aDesc = a.description.toLowerCase();
            const bDesc = b.description.toLowerCase();
            if (aDesc < bDesc) {
                return -1;
            } else {
                return 1;
            }
        });
    return highPertinenceVars.concat(lowPertinenceVars);
};

/*
 * Return the data point nearest to time and its index.
 * @param {Array} data - array of Object where one of the keys is dateTime in Unix epoch.
 * @param {Date} time
 * @return {Object} - datum
 */
export const getNearestTime = function(data, time) {
    // Function that returns the left bounding point for a given chart point.
    if (data.length === 0) {
        return null;
    }
    const bisectDate = bisector(d => d.dateTime).left;
    let index = bisectDate(data, time, 1);
    let datum;
    let d0 = data[index - 1];
    let d1 = data[index];

    if (d0 && d1) {
        datum = time - d0.dateTime > d1.dateTime - time ? d1 : d0;
    } else {
        datum = d0 || d1;
    }

    // Return the nearest data point and its index.
    return datum;
};
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
* When given an approval code, will return the text equivalent
*  @param {String} approvalCode - Usually a letter such as 'A'
*   @return {String} - an easy to understand text version of an approval code
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
*/
export const approvalCodeText = function(approvalCode) {
    const approvalText = {
        P: 'Provisional',
        A: 'Approved',
        R: 'Revised',
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
        E: 'Estimated',
        default: `unknown code: ${approvalCode}`
Briggs, Aaron Shane's avatar
Briggs, Aaron Shane committed
    };

    return approvalText[approvalCode] || approvalText.default;
};