perspective_viewer/config/
number_string_format.rs

1// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
2// ┃ ██████ ██████ ██████       █      █      █      █      █ █▄  ▀███ █       ┃
3// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█  ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄  ▀█ █ ▀▀▀▀▀ ┃
4// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄   █ ▄▄▄▄▄ ┃
5// ┃ █      ██████ █  ▀█▄       █ ██████      █      ███▌▐███ ███████▄ █       ┃
6// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
7// ┃ Copyright (c) 2017, the Perspective Authors.                              ┃
8// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
9// ┃ This file is part of the Perspective library, distributed under the terms ┃
10// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
11// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
12
13mod enums;
14pub use enums::*;
15use serde::{Deserialize, Serialize};
16use serde_with::skip_serializing_none;
17use strum::{Display, EnumIter};
18use ts_rs::TS;
19
20#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, TS)]
21#[serde(rename_all = "camelCase", tag = "style")]
22pub enum NumberFormatStyle {
23    #[default]
24    Decimal,
25    Currency(CurrencyNumberFormatStyle),
26    Percent,
27    Unit(UnitNumberFormatStyle),
28}
29
30#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
31#[serde(rename_all = "camelCase")]
32pub enum CurrencyDisplay {
33    Code,
34    #[default]
35    Symbol,
36    NarrowSymbol,
37    Name,
38}
39
40#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
41#[serde(rename_all = "camelCase")]
42pub enum CurrencySign {
43    #[default]
44    Standard,
45    Accounting,
46}
47
48#[skip_serializing_none]
49#[derive(Default, Serialize, Deserialize, Debug, PartialEq, Clone, TS)]
50#[serde(rename_all = "camelCase")]
51pub struct CurrencyNumberFormatStyle {
52    #[serde(default)]
53    pub currency: CurrencyCode,
54    pub currency_display: Option<CurrencyDisplay>,
55    pub currency_sign: Option<CurrencySign>,
56}
57
58#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
59#[serde(rename_all = "camelCase")]
60pub enum UnitDisplay {
61    #[default]
62    Short,
63    Narrow,
64    Long,
65}
66
67#[skip_serializing_none]
68#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, TS)]
69#[serde(rename_all = "camelCase")]
70pub struct UnitNumberFormatStyle {
71    #[serde(default)]
72    pub unit: Unit,
73    pub unit_display: Option<UnitDisplay>,
74}
75
76#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
77#[serde(rename_all = "camelCase")]
78pub enum RoundingPriority {
79    #[default]
80    Auto,
81    MorePrecision,
82    LessPrecision,
83}
84
85#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
86#[serde(rename_all = "camelCase")]
87pub enum RoundingMode {
88    Ceil,
89    Floor,
90    Expand,
91    Trunc,
92    HalfCeil,
93    HalfFloor,
94    #[default]
95    HalfExpand,
96    HalfTrunc,
97    HalfEven,
98}
99
100#[derive(Default, Debug, PartialEq, Clone, TS)]
101pub enum RoundingIncrement {
102    #[default]
103    Auto,
104    Custom(f64),
105}
106impl std::fmt::Display for RoundingIncrement {
107    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
108        match self {
109            RoundingIncrement::Auto => f.write_str("Auto"),
110            RoundingIncrement::Custom(val) => f.write_fmt(format_args!("{val}")),
111        }
112    }
113}
114
115pub const ROUNDING_INCREMENTS: [f64; 15] = [
116    1., 2., 5., 10., 20., 25., 50., 100., 200., 250., 500., 1000., 2000., 2500., 5000.,
117];
118
119#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
120#[serde(rename_all = "camelCase")]
121pub enum TrailingZeroDisplay {
122    #[default]
123    Auto,
124    StripIfInteger,
125}
126
127#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, TS)]
128#[serde(rename_all = "camelCase", tag = "notation")]
129pub enum Notation {
130    #[default]
131    Standard,
132    Scientific,
133    Engineering,
134    Compact(CompactDisplay),
135}
136
137#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
138#[serde(rename_all = "camelCase", tag = "compactDisplay")]
139pub enum CompactDisplay {
140    #[default]
141    Short,
142    Long,
143}
144
145#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
146#[serde(rename_all = "snake_case")]
147pub enum UseGrouping {
148    Always,
149
150    #[default]
151    Auto,
152    Min2, // default if notation is compact
153
154    #[serde(untagged)]
155    False(bool),
156}
157
158#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy, EnumIter, Display, TS)]
159#[serde(rename_all = "camelCase")]
160pub enum SignDisplay {
161    #[default]
162    Auto,
163    Always,
164    ExceptZero,
165    Negative,
166    Never,
167}
168
169#[skip_serializing_none]
170#[derive(Serialize, Deserialize, Debug, Default, PartialEq, Clone, TS)]
171#[serde(rename_all = "camelCase")]
172pub struct CustomNumberFormatConfig {
173    #[serde(flatten)]
174    #[ts(skip)]
175    pub _style: Option<NumberFormatStyle>,
176
177    // see Digit Options
178    // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#minimumintegerdigits
179    // these min/max props can all be specified but it results in possible conflicts
180    // may consider making them distinct options
181    pub minimum_integer_digits: Option<f64>,
182    pub minimum_fraction_digits: Option<f64>,
183    pub maximum_fraction_digits: Option<f64>,
184    pub minimum_significant_digits: Option<f64>,
185    pub maximum_significant_digits: Option<f64>,
186    pub rounding_priority: Option<RoundingPriority>,
187
188    // specific values https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat/NumberFormat#roundingincrement
189    // Only available with automatic rounding priority
190    // Cannot be mixed with sigfig rounding. (Does this mean max/min sigfig must be unset?)
191    pub rounding_increment: Option<f64>,
192    pub rounding_mode: Option<RoundingMode>,
193    pub trailing_zero_display: Option<TrailingZeroDisplay>,
194
195    #[serde(flatten)]
196    #[ts(skip)]
197    pub _notation: Option<Notation>,
198    pub use_grouping: Option<UseGrouping>,
199    pub sign_display: Option<SignDisplay>,
200}
201
202impl CustomNumberFormatConfig {
203    pub fn filter_default(self, is_float: bool) -> Self {
204        let (frac_min, frac_max) = if is_float { (2., 2.) } else { (0., 0.) };
205        let rounding_increment = self.rounding_increment;
206        let use_grouping = self
207            .use_grouping
208            .filter(|val| *val != UseGrouping::default());
209
210        let mut minimum_fraction_digits =
211            self.minimum_fraction_digits.filter(|val| *val != frac_min);
212
213        let mut maximum_fraction_digits =
214            self.maximum_fraction_digits.filter(|val| *val != frac_max);
215
216        let mut show_frac = is_float
217            && (minimum_fraction_digits.is_some()
218                || maximum_fraction_digits.is_some()
219                || use_grouping.is_some()
220                || matches!(
221                    self._style,
222                    Some(NumberFormatStyle::Percent | NumberFormatStyle::Unit(_))
223                ))
224            || !is_float && matches!(self._style, Some(NumberFormatStyle::Currency(_)));
225
226        // Rounding increment does not work unless `minimum_fraction_digits`
227        // and `maximum_fraction_digits` are set to 0.
228        if rounding_increment.is_some() {
229            show_frac = true;
230            minimum_fraction_digits = Some(0.);
231            maximum_fraction_digits = Some(0.);
232        }
233
234        let minimum_significant_digits = self.minimum_significant_digits.filter(|val| *val != 1.);
235        let maximum_significant_digits = self.maximum_significant_digits.filter(|val| *val != 21.);
236        let show_sig = minimum_significant_digits.is_some() || maximum_significant_digits.is_some();
237        Self {
238            _style: self
239                ._style
240                .filter(|style| !matches!(style, NumberFormatStyle::Decimal)),
241            minimum_integer_digits: self.minimum_integer_digits.filter(|val| *val != 1.),
242            minimum_fraction_digits: show_frac
243                .then_some(minimum_fraction_digits.unwrap_or(frac_min)),
244            maximum_fraction_digits: show_frac
245                .then_some(maximum_fraction_digits.unwrap_or(frac_max)),
246            minimum_significant_digits: show_sig
247                .then_some(minimum_significant_digits.unwrap_or(1.)),
248            maximum_significant_digits: show_sig
249                .then_some(minimum_significant_digits.unwrap_or(21.)),
250            rounding_priority: self
251                .rounding_priority
252                .filter(|val| *val != RoundingPriority::default()),
253            rounding_increment,
254            rounding_mode: self
255                .rounding_mode
256                .filter(|val| *val != RoundingMode::default()),
257            trailing_zero_display: self
258                .trailing_zero_display
259                .filter(|val| *val != TrailingZeroDisplay::default()),
260            _notation: self
261                ._notation
262                .filter(|notation| !matches!(notation, Notation::Standard)),
263            use_grouping,
264            sign_display: self
265                .sign_display
266                .filter(|val| *val != SignDisplay::default()),
267        }
268    }
269}