Formatters.js

/** @module Formatters */
import * as d3 from 'd3-format';
import curry from 'lodash.curry';

// //////////////////////////////
// Utility Functions
// //////////////////////////////

/**
 * Returns null/undefined for null/undefined values,
 * otherwise it returns the result of a passed in formatter
 *
 * @param {Any} value value to format
 * @param {Function} formatter formatter function
 * @return Formatted value or null/undefined
 */
export function safeFormat(value, formatter) {
  if (value == null || isNaN(value)) {
    return value;
  }

  return formatter(value);
}

/**
 * Wraps a formatter function that replaces the value with null
 * when it is 0. Useful for not rendering zeroes in tables.
 *
 * @param {Function} formatter formatter function
 * @return {Function} a formatter function
 */
export function zeroAsNull(formatter) {
  return function zeroAsNullWrapped(value) {
    return formatter(value === 0 ? null : value);
  };
}

/**
 * Wraps a formatting function and puts a + in front of formatted value
 * if it is positive.
 *
 * @param {Function} formatter formatter function
 * @return {Function} a formatter function
 */
export function makePlusMinus(formatter) {
  return function plusMinusWrapped(value) {
    if (value != null && value > 0) {
      return `+${formatter(value)}`;
    }

    return formatter(value);
  };
}

/**
 * Wraps a formatting function by multiplying the value by 100 and
 * adds a % to the end.
 *
 * @param {Function} formatter formatter function
 * @return {Function} a formatter function
 */
export function makePercent(formatter) {
  return function percentWrapped(value) {
    if (value != null) {
      return `${formatter(value * 100)}%`;
    }

    return formatter(value);
  };
}


// //////////////////////////////
// Formatters
// //////////////////////////////

/**
 * Formatter (curried) - renders to `numDecimals` decimal places
 *
 * @example
 * decFormat(1, 10.89321)
 * > '10.9'
 *
 * @example
 * decFormat(2, 10.89321)
 * > '10.89'
 *
 * @example
 * decFormat(1)(10.89321)
 * > '10.9'
 *
 * @function
 * @param {Number} numDecimals number of decimals to use
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const decFormat = curry((numDecimals, value) =>
  safeFormat(value, d3.format(`0.${numDecimals}f`))
);

/**
 * Formatter (curried) - renders value multiplied by 100
 * with `numDecimal` decimal points.
 * Commonly used for percentages without the %.
 *
 * @example
 * decPercentFormat(1, 0.8132)
 * > '81.3'
 *
 * @function
 * @param {Number} numDecimals number of decimals to use
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const decPercentFormat = curry((numDecimals, value) =>
  safeFormat(value * 100, d3.format(`0.${numDecimals}f`))
);

/**
 * Formatter - renders value with at most one decimal point
 *
 * @example
 * atMostDecFormat(10.89321)
 * > '10.9'
 *
 * @example
 * atMostDecFormat(15)
 * > '15'
 *
 * @example
 * atMostDecFormat(15.001)
 * > '15.0'
 *
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export function atMostDecFormat(value) {
  if (Number.isInteger(value)) {
    return value;
  }

  return decFormat(1, value);
}

/**
 * Formatter (curried) - renders value as a percentage
 *
 * @example
 * percentFormat(1, 0.38523)
 * > '38.5%'
 *
 * @function
 * @param {Number} numDecimals number of decimals to use
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const percentFormat = curry((numDecimals, value) =>
  safeFormat(value, d3.format(`0.${numDecimals}%`))
);

/**
 * Formatter (curried) - renders positive values with a +
 * and negative with a -.
 *
 * @example
 * plusMinusFormat(1, 0.38523)
 * > '+0.4'
 *
 * @example
 * plusMinusFormat(1, -15)
 * > '-15.0'
 *
 * @function
 * @param {Number} numDecimals number of decimals to use
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const plusMinusFormat = curry((numDecimals, value) =>
  safeFormat(value, d3.format(`+0.${numDecimals}f`))
);

/**
 * Formatter (curried) - renders values prefixed with a ±
 *
 * @example
 * seFormat(1, 0.38523)
 * > '±0.4'
 *
 * @example
 * seFormat(2, -15)
 * > '±15.00'
 *
 * @function
 * @param {Number} numDecimals number of decimals to use
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const seFormat = curry((numDecimals, value) =>
  safeFormat(value, x => `±${d3.format(`0.${numDecimals}f`)(Math.abs(x))}`)
);

/**
 * Formatter - adds leading zeroes to a value
 * (adds max of 8 zeros)
 *
 * @example
 * leadingZeroFormat(5, 2)
 * > '05'
 *
 * @example
 * leadingZeroFormat(12, 2)
 * > '12'
 *
 * @param {Number} length desired length of string
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export const leadingZeroFormat = curry((length, value) =>
  `00000000${value}`.slice(-length)
);

/**
 * Formatter - renders values formatted as money (prefixed with $, uses commas)
 *
 * @example
 * moneyFormat(9.132)
 * > '$9.13'
 *
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export function moneyFormat(value) {
  return safeFormat(value, d3.format('$,.2f'));
}

/**
 * Formatter - renders values formatted as with thousands separated with commas
 * (or whatever your locale uses as by d3.format(','))
 *
 * @example
 * commaFormat(1396512)
 * > '1,396,521'
 *
 * @param {Number} value value to format
 * @return {String} formatted value
 */
export function commaFormat(value) {
  return safeFormat(value, d3.format(','));
}