Skip to main content

perspective_viewer/components/style_controls/
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 digits_section;
14mod misc_section;
15mod style_section;
16mod types;
17
18use std::rc::Rc;
19
20use perspective_client::config::ColumnType;
21pub use types::*;
22use yew::{Callback, Component, Properties, html};
23
24use crate::config::*;
25
26#[derive(Properties, PartialEq, Clone)]
27pub struct CustomNumberFormatProps {
28    pub restored_config: CustomNumberFormatConfig,
29    pub on_change: Callback<ColumnConfigFieldUpdate>,
30    pub view_type: ColumnType,
31    pub column_name: String,
32
33    /// Resolved default values for this column under the active plugin.
34    pub defaults: Rc<NumberFormatDefaults>,
35
36    #[prop_or_default]
37    pub keys: Vec<String>,
38}
39
40pub enum CustomNumberFormatMsg {
41    StyleChanged(Option<NumberStyle>),
42    NotationChanged(Option<NotationName>),
43    CurrencyCode(Option<CurrencyCode>),
44    CurrencyDisplay(Option<CurrencyDisplay>),
45    CompactDisplay(Option<CompactDisplay>),
46    CurrencySign(Option<CurrencySign>),
47    UseGrouping(Option<UseGrouping>),
48    SignDisplay(Option<SignDisplay>),
49    Unit(Option<Unit>),
50    UnitDisplay(Option<UnitDisplay>),
51    MinimumIntegerDigits(Option<f64>),
52    SigChange(Option<(f64, f64)>),
53    FracChange(Option<(f64, f64)>),
54    RoundingIncrement(RoundingIncrement),
55    TrailingZero(Option<TrailingZeroDisplay>),
56    RoundingMode(Option<RoundingMode>),
57    RoundingPriority(Option<RoundingPriority>),
58}
59
60#[derive(Default)]
61pub struct CustomNumberFormat {
62    config: CustomNumberFormatConfig,
63    style: NumberStyle,
64    notation: Option<NotationName>,
65}
66
67impl Component for CustomNumberFormat {
68    type Message = CustomNumberFormatMsg;
69    type Properties = CustomNumberFormatProps;
70
71    fn create(ctx: &yew::prelude::Context<Self>) -> Self {
72        Self::initialize(ctx)
73    }
74
75    fn changed(
76        &mut self,
77        ctx: &yew::prelude::Context<Self>,
78        _old_props: &Self::Properties,
79    ) -> bool {
80        *self = Self::initialize(ctx);
81        true
82    }
83
84    fn update(&mut self, ctx: &yew::prelude::Context<Self>, msg: Self::Message) -> bool {
85        match msg {
86            CustomNumberFormatMsg::StyleChanged(style) => {
87                let style = style.unwrap_or_default();
88                let new_style = match style {
89                    NumberStyle::Decimal => NumberFormatStyle::Decimal,
90                    NumberStyle::Percent => NumberFormatStyle::Percent,
91                    NumberStyle::Currency => {
92                        NumberFormatStyle::Currency(CurrencyNumberFormatStyle::default())
93                    },
94                    NumberStyle::Unit => NumberFormatStyle::Unit(UnitNumberFormatStyle {
95                        unit: Unit::default(),
96                        unit_display: None,
97                    }),
98                };
99                self.config._style = Some(new_style);
100                self.style = style;
101            },
102            CustomNumberFormatMsg::NotationChanged(notation) => {
103                self.notation = notation;
104                let new_notation = notation.map(|notation| match notation {
105                    NotationName::Standard => Notation::Standard,
106                    NotationName::Scientific => Notation::Scientific,
107                    NotationName::Engineering => Notation::Engineering,
108                    NotationName::Compact => Notation::Compact(CompactDisplay::default()),
109                });
110                self.config._notation = new_notation;
111            },
112            CustomNumberFormatMsg::CurrencyCode(val) => {
113                if let Some(NumberFormatStyle::Currency(currency)) = &mut self.config._style {
114                    currency.currency = val.unwrap_or_default();
115                }
116            },
117            CustomNumberFormatMsg::CurrencyDisplay(val) => {
118                if let Some(NumberFormatStyle::Currency(currency)) = &mut self.config._style {
119                    currency.currency_display = val;
120                }
121            },
122            CustomNumberFormatMsg::CurrencySign(val) => {
123                if let Some(NumberFormatStyle::Currency(currency)) = &mut self.config._style {
124                    currency.currency_sign = val;
125                }
126            },
127            CustomNumberFormatMsg::CompactDisplay(val) => {
128                if let Some(Notation::Compact(old)) = &mut self.config._notation {
129                    if let Some(val) = val {
130                        *old = val;
131                    }
132                } else {
133                    tracing::error!("Unreachable change in compact display!");
134                }
135            },
136            CustomNumberFormatMsg::UseGrouping(val) => {
137                self.config.use_grouping = val;
138            },
139            CustomNumberFormatMsg::SignDisplay(val) => {
140                self.config.sign_display = val;
141            },
142            CustomNumberFormatMsg::Unit(val) => {
143                if let Some(NumberFormatStyle::Unit(style)) = &mut self.config._style {
144                    style.unit = val.unwrap_or_default();
145                }
146            },
147            CustomNumberFormatMsg::UnitDisplay(val) => {
148                if let Some(NumberFormatStyle::Unit(style)) = &mut self.config._style {
149                    style.unit_display = val;
150                }
151            },
152            CustomNumberFormatMsg::MinimumIntegerDigits(val) => {
153                self.config.minimum_integer_digits = val;
154            },
155            CustomNumberFormatMsg::FracChange(val) => {
156                let defaults = &ctx.props().defaults;
157                self.config.rounding_increment = None;
158                self.config.maximum_fraction_digits = val.map(|(_, val)| {
159                    let min = self
160                        .config
161                        .minimum_fraction_digits
162                        .unwrap_or(defaults.fraction.0);
163                    val.max(min)
164                });
165
166                self.config.minimum_fraction_digits = val.map(|(val, _)| {
167                    let max = self
168                        .config
169                        .maximum_fraction_digits
170                        .unwrap_or(defaults.fraction.1);
171                    val.min(max)
172                });
173            },
174            CustomNumberFormatMsg::SigChange(val) => {
175                let defaults = &ctx.props().defaults;
176                self.config.maximum_significant_digits = val.map(|(_, val)| {
177                    let min = self
178                        .config
179                        .minimum_significant_digits
180                        .unwrap_or(defaults.significant.0);
181                    val.max(min)
182                });
183
184                self.config.minimum_significant_digits = val.map(|(val, _)| {
185                    let max = self
186                        .config
187                        .maximum_significant_digits
188                        .unwrap_or(defaults.significant.1);
189                    val.min(max)
190                });
191            },
192            CustomNumberFormatMsg::RoundingIncrement(val) => {
193                if let RoundingIncrement::Custom(val) = val {
194                    self.config.rounding_priority = None;
195                    self.config.rounding_increment = Some(val);
196                    self.config.maximum_fraction_digits = Some(0.);
197                    self.config.minimum_fraction_digits = Some(0.);
198                } else {
199                    self.config.rounding_increment = None;
200                }
201            },
202            CustomNumberFormatMsg::TrailingZero(val) => {
203                self.config.trailing_zero_display = val;
204            },
205            CustomNumberFormatMsg::RoundingMode(val) => {
206                self.config.rounding_mode = val;
207            },
208            CustomNumberFormatMsg::RoundingPriority(val) => {
209                self.config.rounding_increment = None;
210                self.config.rounding_priority = val;
211            },
212        };
213
214        let filtered_config = self.config.clone().filter_default(&ctx.props().defaults);
215        let mut value = serde_json::Map::new();
216        if filtered_config != CustomNumberFormatConfig::default() {
217            value.insert(
218                "number_format".to_owned(),
219                serde_json::to_value(&filtered_config).unwrap_or(serde_json::Value::Null),
220            );
221        }
222        ctx.props().on_change.emit(ColumnConfigFieldUpdate {
223            keys: ctx.props().keys.clone(),
224            value,
225        });
226        true
227    }
228
229    fn view(&self, ctx: &yew::prelude::Context<Self>) -> yew::prelude::Html {
230        html! {
231            <>{ self.style_section(ctx) }{ self.digits_section(ctx) }{ self.misc_section(ctx) }</>
232        }
233    }
234}
235
236impl CustomNumberFormat {
237    fn initialize(ctx: &yew::prelude::Context<Self>) -> Self {
238        let defaults = &ctx.props().defaults;
239        let mut config = ctx.props().restored_config.clone();
240        if config._style.is_none() && defaults.style != NumberFormatStyle::Decimal {
241            config._style = Some(defaults.style.clone());
242        }
243
244        if config._notation.is_none() && defaults.notation != Notation::Standard {
245            config._notation = Some(defaults.notation.clone());
246        }
247
248        Self {
249            style: config
250                ._style
251                .as_ref()
252                .map(NumberStyle::from)
253                .unwrap_or_default(),
254            notation: config._notation.as_ref().map(NotationName::from),
255            config,
256        }
257    }
258}