(ns metabase.util.formatting.numbers (:require [metabase.util :as u] [metabase.util.formatting.internal.numbers :as internal] [metabase.util.formatting.internal.numbers-core :as core])) | |
(declare format-number) | |
Extra defaults that are mixed in when formatted a currency value in compact mode. | (def compact-currency-options
{:currency-style "symbol"}) |
#?(:cljs
(def ^:export compact-currency-options-js
"Extra defaults that are mixed in when formatted a currency value in compact mode."
(clj->js compact-currency-options)))
;; Compact form ===================================================================================================
(def ^:private display-compact-decimals-cutoff 1000) | |
(def ^:private humanized-powers [[1000000000000 "T"] [1000000000 "B"] [1000000 "M"] [1000 "k"]]) | |
(defn- format-number-compact-basic [number options]
(let [options (dissoc options :compact :number-style)
abs-value (abs number)]
(cond
(zero? number) "0"
(< abs-value display-compact-decimals-cutoff) (format-number number options)
:else (let [[power suffix] (first (filter #(>= abs-value (first %)) humanized-powers))]
(str (format-number (/ number power)
(merge options {:minimum-fraction-digits 1 :maximum-fraction-digits 1}))
suffix))))) | |
(defmulti ^:private format-number-compact*
{:arglists '([number options])}
(fn [_ {:keys [number-style]}] number-style)) | |
(defmethod format-number-compact* :default [number options] (format-number-compact-basic number options)) | |
(defmethod format-number-compact* "percent" [number options] (str (format-number-compact-basic (* 100 number) options) "%")) | |
(defmethod format-number-compact* "currency" [number options]
(let [options (merge options compact-currency-options)
formatter (internal/number-formatter-for-options options)]
(if (< (abs number) display-compact-decimals-cutoff)
(core/format-number-basic formatter number)
(core/wrap-currency formatter (format-number-compact-basic number options))))) | |
(defmethod format-number-compact* "scientific" [number options]
(internal/format-number-scientific number (merge options {:maximum-fraction-digits 1 :minimum-fraction-digits 1}))) | |
(defn- format-number-compact [number options]
(format-number-compact* number (-> options
(dissoc :compact)
core/prep-options))) | |
High-level ===================================================================================================== | (defn- format-number-standard [number options]
(let [options (core/prep-options options)
nf (cond
(:number-formatter options) (:number-formatter options)
;; Hacky special case inherited from the TS version - to match classic behavior for small numbers,
;; treat maximum-fraction-digits as maximum-significant-digits instead.
;; "Small" means |x| < 1, or < 1% for percentages.
(and (not (:decimals options))
(not (:minimum-fraction-digits options))
(not= (:number-style options) "currency")
(< (abs number)
(if (= (:number-style options) "percent")
0.01
1)))
(-> options
(dissoc :maximum-fraction-digits)
(assoc :maximum-significant-digits (max 2 (:minimum-significant-digits options 0)))
internal/number-formatter-for-options)
:else (internal/number-formatter-for-options options))]
(core/format-number-basic nf number))) |
Formats a number according to a map of options.
The options:
- | (defn ^:export format-number
[number options]
(let [{:keys [compact negative-in-parentheses number-style scale] :as options} (u/normalize-map options)]
(cond
(and scale (not (NaN? scale))) (format-number (* scale number) (dissoc options :scale))
(and (neg? number)
negative-in-parentheses) (str "("
(format-number (- number) (assoc options :negative-in-parentheses false))
")")
compact (format-number-compact number options)
(= (keyword number-style)
:scientific) (internal/format-number-scientific number options)
:else (format-number-standard number options)))) |