UNPKG

@seasketch/geoprocessing

Version:

Geoprocessing and reporting framework for SeaSketch 2.0

156 lines (143 loc) 5.54 kB
/** * Rounds number to a fixed number of decimals * @param value Value to round * @param decimals Number of digits after the decimal point to keep * @param options.keepSmallValues If true, will keep any small value as-is which would be rounded to 0, defaults to false * @returns rounded number */ export const roundDecimal = ( /** Value to round */ value: number, /** Number of digits after the decimal point to keep */ decimals = 1, options: { /** If true, will keep any small value as-is which would be rounded to 0, defaults to false */ keepSmallValues?: boolean; } = {}, ) => { const { keepSmallValues = false } = options; const roundedValue = Number( Math.round(Number.parseFloat(`${value}e${decimals}`)) + `e-${decimals}`, ); return keepSmallValues && value && !roundedValue ? value : roundedValue; }; /** * Rounds number to a fixed number of decimals, then formats as a human readable string * @param value Value to round * @param decimals Number of digits after the decimal point to keep * @param options.keepSmallValues If true, will keep any small value as-is which would be rounded to 0, defaults to false * @returns rounded number as a human readable string */ export const roundDecimalFormat = ( /** Value to round */ value: number, /** Number of digits after the decimal point to keep */ decimals = 1, options: { /** If true, will keep any small value as-is which would be rounded to 0, defaults to false */ keepSmallValues?: boolean; } = {}, ) => { const NumberFormatter = new Intl.NumberFormat("en", { style: "decimal" }); return NumberFormatter.format(roundDecimal(value, decimals, options)); }; /** Formats number to string, if less than zero will leave as-is, otherwise will format as large number */ export const numberFormat = (val: number) => { const NumberFormatter = new Intl.NumberFormat("en", { style: "decimal" }); return val < 0 ? `${val}` : NumberFormatter.format(val); }; export interface PercentEdgeOptions { /** Number of decimal digits to round value to if is within lower or upper edge range. defaults to 1. Override with this option */ digits?: number; /** Number of decimal digits to round value to if exactly matches lowerBound, defaults to 0 (whole number) */ digitsIfMatchLower?: number; /** Define a lower value bound. Defaults to 0 (zero). */ lowerBound?: number; /** Enable special formatting of values from lowerBound up to lower value. Defaults to .001 aka 1/10 of a percent */ lower?: number; /** Optional string value to display if between zero and lower. Overrides default special handling, no use of percent formatter. Example - "< 0.1% for real" */ lowerOverride?: string; /** Define an upper value bound. Enable special formatting of values from upper to upperBound */ upperBound?: number; /** Define lower bound to upper value. Enable special formatting of values from upper to upperBound */ upper?: number; /** Optional string value to display if between upper and upperBound. Overrides default special handling, no use of percent formatter. Example - "almost 20%, keep going!" */ upperOverride?: string; } /** * Special percent formatter designed to produce readable percent values for * display with special handling of numbers within some edge range of * user-defined lower or upper bounds. Defaults to handle only lower edge with * lowerBound = 0 and lower = .001. All bound values are expected to be in * decimal percent. So 1/10th of a percent is .001 */ export const percentWithEdge = ( val: number, options: PercentEdgeOptions = {}, ) => { const { digits = 1, digitsIfMatchLower = 0, lower = 0.001, lowerBound = 0, lowerOverride, upper, upperBound, upperOverride, } = options; const PercentFormatter = new Intl.NumberFormat("en", { style: "percent", maximumFractionDigits: digits, }); const MatchPercentFormatter = new Intl.NumberFormat("en", { style: "percent", maximumFractionDigits: digitsIfMatchLower, }); if (val === lowerBound) { return MatchPercentFormatter.format(val); } else if (val > lowerBound && val < lower) { if (lowerOverride) { return lowerOverride; } else { return `< ${PercentFormatter.format(lower)}`; } } else if ( upper !== undefined && upperBound !== undefined && val < upperBound && val > upper ) { if (upperOverride) { return upperOverride; } else { return PercentFormatter.format(upper); } } else { return PercentFormatter.format(val); } }; /** * Special percent formatter designed to produce readable percent values for display given an upper percent goal * All parameters are expected to be decimal percent values e.g. .001 = 1/10th of a percent. */ export const percentGoalWithEdge = ( /** Actual percent value */ val: number, /** Goal percent value */ goal: number, /** Override options passed to percentWithEdge, supports same parameters */ options?: PercentEdgeOptions, ) => { return percentWithEdge(val, { upperBound: goal, upper: goal - 0.001, ...options, }); }; /** Formats number to string, rounding decimal to number of digits, if value is less than lower will clamp to lower value */ export const roundLower = (val: number, { lower } = { lower: 1 }) => { const NumberFormatter = new Intl.NumberFormat("en", { style: "decimal" }); return val < lower ? `< ${lower}` : NumberFormatter.format(roundDecimal(val, 1)); };