Last active
February 20, 2025 08:16
-
-
Save adgarcia/bfa2f928a1eb22567185a9c5d0ac0877 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import * as d3 from 'd3' | |
| const QUADRILLION = 1e15 | |
| const THOUSAND = 1e3 | |
| // can grab this from our existing set of supported locales and currency symbols | |
| type Locales = 'en' | |
| type FormatPicker = (s: number, locale: d3.FormatLocaleObject) => (s: number) => string | |
| interface Formatter { | |
| format: (s: number | string, locale?: Locales) => string | |
| } | |
| // The base class might be overkill | |
| class BaseFormatter implements Formatter { | |
| readonly locales: Record<Locales, d3.FormatLocaleObject> = { | |
| en: d3.formatLocale({ | |
| decimal: '.', | |
| thousands: ',', | |
| grouping: [3], | |
| currency: ['$', ''], | |
| }) | |
| } | |
| readonly formatPicker: FormatPicker | |
| readonly preProcessing: (s: string) => string | |
| readonly postProcessing: (s: string) => string | |
| constructor(picker: FormatPicker, postProcessing?: (s: string) => string, preProcessing?: (s: string) => string) { | |
| this.formatPicker = picker | |
| this.preProcessing = preProcessing ?? (s => s) | |
| this.postProcessing = postProcessing ?? (s => s) | |
| } | |
| format = (s: number | string, locale: Locales = 'en') => { | |
| const asNumber = typeof s === 'string' ? parseFloat(this.preProcessing(s)) : s | |
| const formatFunction = this.formatPicker(asNumber, this.locales[locale] ?? this.locales.en) | |
| return this.postProcessing(formatFunction(asNumber)) | |
| } | |
| } | |
| // the SI type formats it the way we want, but the unit is obv off. | |
| // Replace G(giga) with B(billion), and P(peta) with Q(quadrillion) | |
| const updateSIUnits = (s: string) => s.toUpperCase().replace('G', 'B').replace('P', 'Q') | |
| // Returns number of additional significant digits in the most significant group of digits | |
| // e.g placesInLastGroup(123456, 3) returns 2 because: | |
| // * a group size of 3 gives us 2 groups: 123 and 456 | |
| // * the most significant group is 123 and there are 2 additional significant digits (2 and 3) | |
| const placesInLastGroup = (input: number, groupSize: number = 3) => Math.floor(Math.log10(input))%groupSize | |
| export const MarketCapFormatter: BaseFormatter = new BaseFormatter((input: number, locale: d3.FormatLocaleObject) => { | |
| // 123.456789 maps to $123.46 | |
| let specifier: d3.FormatSpecifier = d3.formatSpecifier('$,.2f') | |
| if (input >= QUADRILLION) { | |
| // e.g 100 quadrillion maps to $1.2e+17 | |
| specifier = d3.formatSpecifier('$,.1e') | |
| } else if (input >= THOUSAND) { | |
| // e.g 123,456,789.01 maps to $123.4M | |
| // 12,456,789.01 maps to $12.4M | |
| // 1,456,789.01 maps to $1.4M | |
| const finalPlaces = placesInLastGroup(input) | |
| specifier = finalPlaces === 0 ? d3.formatSpecifier('$,.2s') | |
| : finalPlaces === 1 | |
| ? d3.formatSpecifier('$,.3s') | |
| : d3.formatSpecifier('$,.4s') | |
| } else if (input < 0) { | |
| return (_: number) => { | |
| return locale.format(specifier.toString())(0) | |
| } | |
| } | |
| return locale.format(specifier.toString()) | |
| }, updateSIUnits) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment