Skip to content

Instantly share code, notes, and snippets.

@adgarcia
Last active February 20, 2025 08:16
Show Gist options
  • Select an option

  • Save adgarcia/bfa2f928a1eb22567185a9c5d0ac0877 to your computer and use it in GitHub Desktop.

Select an option

Save adgarcia/bfa2f928a1eb22567185a9c5d0ac0877 to your computer and use it in GitHub Desktop.
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