All files / owid-grapher/grapher/color BinningStrategies.ts

96.08% Statements 49/51
50% Branches 2/4
100% Functions 2/2
96.08% Lines 49/51

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 851x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x 3x 3x 3x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 3x 3x 3x 3x 3x 3x     3x 1x                                                                    
import { ckmeans } from "simple-statistics"
import { range, quantile } from "d3-array"
 
import {
    excludeUndefined,
    uniq,
    last,
    roundSigFig,
    first,
} from "../../clientUtils/Util"
import { BinningStrategy } from "./BinningStrategy"
 
/** Human-readable labels for the binning strategies */
export const binningStrategyLabels: Record<BinningStrategy, string> = {
    equalInterval: "Equal-interval",
    quantiles: "Quantiles",
    ckmeans: "Ckmeans",
    manual: "Manual",
}
 
function calcEqualIntervalStepSize(
    sortedValues: number[],
    binCount: number,
    minBinValue: number
): number {
    if (!sortedValues.length) return 10
    const stepSizeInitial = (last(sortedValues)! - minBinValue) / binCount
    return roundSigFig(stepSizeInitial, 1)
}
 
interface GetBinMaximumsWithStrategyArgs {
    binningStrategy: BinningStrategy
    sortedValues: number[]
    binCount: number
    /** `minBinValue` is only used in the `equalInterval` binning strategy. */
    minBinValue?: number
}
 
// Some algorithms can create bins that start & end at the same value.
// This also means the first bin can both start and end at the same value – the minimum
// value. This is why we uniq() and why we remove any values <= minimum value.
function normalizeBinValues(
    binValues: (number | undefined)[],
    minBinValue?: number
): any {
    const values = uniq(excludeUndefined(binValues))
    return minBinValue !== undefined
        ? values.filter((v) => v > minBinValue)
        : values
}
 
export function getBinMaximums(args: GetBinMaximumsWithStrategyArgs): number[] {
    const { binningStrategy, sortedValues, binCount, minBinValue } = args
    const valueCount = sortedValues.length
 
    if (valueCount < 1 || binCount < 1) return []
 
    if (binningStrategy === BinningStrategy.ckmeans) {
        const clusters = ckmeans(
            sortedValues,
            binCount > valueCount ? valueCount : binCount
        )
        return normalizeBinValues(clusters.map(last), minBinValue)
    } else if (binningStrategy === BinningStrategy.quantiles) {
        return normalizeBinValues(
            range(1, binCount + 1).map((v) =>
                quantile(sortedValues, v / binCount)
            ),
            minBinValue
        )
    } else {
        // Equal-interval strategy by default
        const minValue = minBinValue ?? first(sortedValues) ?? 0
        const binStepSize = calcEqualIntervalStepSize(
            sortedValues,
            binCount,
            minValue
        )
        return normalizeBinValues(
            range(1, binCount + 1).map((n) => minValue + n * binStepSize),
            minBinValue
        )
    }
}