All files / owid-grapher/clientUtils formatValue.ts

55.93% Statements 33/59
53.33% Branches 8/15
100% Functions 1/1
55.93% Lines 33/59

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 881x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x 94x                                             94x                                                                  
import { d3Format } from "./Util"
 
export interface TickFormattingOptions {
    numDecimalPlaces?: number
    unit?: string
    noTrailingZeroes?: boolean
    noSpaceUnit?: boolean
    numberPrefixes?: boolean
    shortNumberPrefixes?: boolean
    showPlus?: boolean
}
 
// todo: Should this be numberSuffixes instead of Prefixes?
// todo: we should have unit tests for this one. lot's of great features but hard to see how to use all of them.
export function formatValue(
    value: number,
    options: TickFormattingOptions
): string {
    const noTrailingZeroes = options.noTrailingZeroes ?? true
    const numberPrefixes =
        (options.numberPrefixes || options.shortNumberPrefixes) ?? true
 
    const shortNumberPrefixes = options.shortNumberPrefixes ?? false
    const showPlus = options.showPlus ?? false
    const numDecimalPlaces = options.numDecimalPlaces ?? 2
    const unit = options.unit ?? ""
    const isNoSpaceUnit = options.noSpaceUnit ?? unit[0] === "%"
 
    let output: string = value.toString()
 
    const absValue = Math.abs(value)
    if (!isNoSpaceUnit && numberPrefixes && absValue >= 1e6) {
        if (!isFinite(absValue)) output = "Infinity"
        else if (absValue >= 1e12)
            output = formatValue(value / 1e12, {
                ...options,
                unit: shortNumberPrefixes ? "T" : "trillion",
                noSpaceUnit: shortNumberPrefixes,
                numDecimalPlaces: 2,
            })
        else if (absValue >= 1e9)
            output = formatValue(value / 1e9, {
                ...options,
                unit: shortNumberPrefixes ? "B" : "billion",
                noSpaceUnit: shortNumberPrefixes,
                numDecimalPlaces: 2,
            })
        else if (absValue >= 1e6)
            output = formatValue(value / 1e6, {
                ...options,
                unit: shortNumberPrefixes ? "M" : "million",
                noSpaceUnit: shortNumberPrefixes,
                numDecimalPlaces: 2,
            })
    } else if (!isNoSpaceUnit && shortNumberPrefixes && absValue >= 1e3) {
        output = formatValue(value / 1e3, {
            ...options,
            unit: "k",
            noSpaceUnit: true,
            numDecimalPlaces: 2,
        })
    } else {
        const targetDigits = Math.pow(10, -numDecimalPlaces)
 
        if (value !== 0 && Math.abs(value) < targetDigits) {
            if (value < 0) output = `>-${targetDigits}`
            else output = `<${targetDigits}`
        } else
            output = d3Format(`${showPlus ? "+" : ""},.${numDecimalPlaces}f`)(
                value
            )
 
        if (noTrailingZeroes) {
            // Convert e.g. 2.200 to 2.2
            const m = output.match(/(.*?[0-9,-]+.[0-9,]*?)0*$/)
            if (m) output = m[1]
            if (output[output.length - 1] === ".")
                output = output.slice(0, output.length - 1)
        }
    }
 
    if (unit === "$" || unit === "£") output = unit + output
    else if (isNoSpaceUnit) output = output + unit
    else if (unit.length > 0) output = output + " " + unit
 
    return output
}