(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* (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)))) |