Skip to main content

ironcalc_base/
cf_types.rs

1use bitcode::{Decode, Encode};
2use serde::{Deserialize, Serialize};
3
4use crate::types::{Color, Dxf, Style};
5
6#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
7pub enum ValueOperator {
8    Equal,
9    GreaterThan,
10    GreaterThanOrEqual,
11    LessThan,
12    LessThanOrEqual,
13    NotEqual,
14    Between,
15    NotBetween,
16}
17
18#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
19pub enum TextOperator {
20    Contains, // NOT(ISERROR(SEARCH(value,A1)))
21    DoesNotContain,
22    BeginsWith,
23    EndsWith, // RIGHT(E1,LEN(value))=
24    Equals,
25}
26
27#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
28pub enum PeriodType {
29    Between,
30    NotBetween,
31    Yesterday,
32    Today,
33    Tomorrow,
34    Last7Days,
35    Next7Days,
36    LastWeek,
37    ThisWeek,
38    NextWeek,
39    LastMonth,
40    ThisMonth,
41    NextMonth,
42    LastYear,
43    ThisYear,
44    NextYear,
45}
46
47// These are the threshold definitions for icon set and color scale conditional formatting.
48#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
49pub enum Cfvo {
50    Min,
51    Max,
52    Number(f64),
53    Percent(f64),
54    Percentile(f64),
55    Formula(String),
56}
57
58#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
59pub enum Icon {
60    ArrowUp,
61    ArrowRight,
62    ArrowDown,
63    ArrowAngleUp,
64    ArrowAngleDown,
65    Circle,
66    TriangleUp,
67    TriangleDown,
68    TriangleUpFilled,
69    TriangleDownFilled,
70    FlatRectangle,
71    Rhombus,
72    Flag,
73    Check,
74    Cross,
75    Exclamation,
76    Star,
77    Heart,
78    ThumbsUp,
79    ThumbsDown,
80}
81
82#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
83pub struct ColorScaleThreshold {
84    pub cfvo: Cfvo,
85    pub color: Color,
86}
87
88#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
89pub struct IconThreshold {
90    pub icon: Icon,
91    pub cfvo: Cfvo,
92    pub color: Color,
93    // If true, the threshold is "strict":
94    // the icon applies only if the value is strictly greater than (for ">=" operator) the threshold value.
95    pub is_strict: bool,
96}
97
98#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
99#[serde(tag = "type")]
100pub enum CfRule {
101    ColorScale {
102        thresholds: Vec<ColorScaleThreshold>,
103    },
104    CellIs {
105        operator: ValueOperator,
106        formula: String,
107        // Only present for Between and NotBetween operators
108        formula2: Option<String>,
109        dxf_id: u32,
110        stop_if_true: bool,
111    },
112    Formula {
113        formula: String,
114        dxf_id: u32,
115        stop_if_true: bool,
116    },
117    Text {
118        operator: TextOperator,
119        value: String,
120        dxf_id: u32,
121        stop_if_true: bool,
122    },
123    TimePeriod {
124        time_period: PeriodType,
125        date1: Option<String>,
126        date2: Option<String>,
127        dxf_id: u32,
128        stop_if_true: bool,
129    },
130    DuplicateValues {
131        dxf_id: u32,
132        stop_if_true: bool,
133    },
134    UniqueValues {
135        dxf_id: u32,
136        stop_if_true: bool,
137    },
138    Blanks {
139        dxf_id: u32,
140        stop_if_true: bool,
141    },
142    NotBlanks {
143        dxf_id: u32,
144        stop_if_true: bool,
145    },
146    Errors {
147        dxf_id: u32,
148        stop_if_true: bool,
149    },
150    NoErrors {
151        dxf_id: u32,
152        stop_if_true: bool,
153    },
154    AboveAverage {
155        dxf_id: u32,
156        stop_if_true: bool,
157    },
158    BelowAverage {
159        dxf_id: u32,
160        stop_if_true: bool,
161    },
162    Top10 {
163        rank: u32,
164        percent: bool,
165        dxf_id: u32,
166        stop_if_true: bool,
167    },
168    Bottom10 {
169        rank: u32,
170        percent: bool,
171        dxf_id: u32,
172        stop_if_true: bool,
173    },
174    DataBar {
175        // If Options are None, they default to Automatic:
176        // min is Min(0, values in the range), max is Max(0, values in the range).
177        min: Option<Cfvo>,
178        max: Option<Cfvo>,
179        positive_color: Color,
180        negative_color: Color,
181        is_gradient: bool,
182        // missing:
183        // has_border: bool,
184        // border_color_positive: Color,
185        // border_color_negative: Color,
186        // axis_position: DataBarAxisPosition, (automatic, none, cell_midpoint)
187        // axis_color: Color,
188        show_value: bool,
189    },
190    IconSet {
191        // The icon thresholds from highest to lowest value.
192        // In a set with 5 icons, we have 4 thresholds:
193        //  * the first applies to values >= threshold1,
194        //  * the second applies to values >= threshold2 and < threshold1,
195        //  * the third applies to values >= threshold3 and < threshold2,
196        //  * the fourth applies to values >= threshold4 and < threshold3,
197        //  * the fifth applies to values < threshold4.
198        thresholds: Vec<IconThreshold>,
199        show_value: bool,
200    },
201    IconRating {
202        // In a rating an icon is repeated `max` times, with `count` of them filled in.
203        icon: Icon,
204        color: Color,
205        // thresholds from highest to lowest value. There are `max-1` thresholds.
206        // (threshold, is_strict)
207        thresholds: Vec<(Cfvo, bool)>,
208        show_value: bool,
209    },
210}
211
212/// User-facing input type for creating or updating a CF rule.
213/// Mirrors `CfRule` but dxf-based variants carry a `Dxf` format
214/// instead of a `dxf_id` index.  Non-dxf variants (ColorScale, DataBar,
215/// IconSet, IconRating) are identical to their `CfRule` counterparts.
216#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
217#[serde(tag = "type")]
218pub enum CfRuleInput {
219    ColorScale {
220        thresholds: Vec<ColorScaleThreshold>,
221    },
222    CellIs {
223        operator: ValueOperator,
224        formula: String,
225        formula2: Option<String>,
226        format: Dxf,
227        stop_if_true: bool,
228    },
229    Text {
230        operator: TextOperator,
231        value: String,
232        format: Dxf,
233        stop_if_true: bool,
234    },
235    Formula {
236        formula: String,
237        format: Dxf,
238        stop_if_true: bool,
239    },
240    TimePeriod {
241        time_period: PeriodType,
242        date1: Option<String>,
243        date2: Option<String>,
244        format: Dxf,
245        stop_if_true: bool,
246    },
247    DuplicateValues {
248        format: Dxf,
249        stop_if_true: bool,
250    },
251    UniqueValues {
252        format: Dxf,
253        stop_if_true: bool,
254    },
255    Blanks {
256        format: Dxf,
257        stop_if_true: bool,
258    },
259    NotBlanks {
260        format: Dxf,
261        stop_if_true: bool,
262    },
263    Errors {
264        format: Dxf,
265        stop_if_true: bool,
266    },
267    NoErrors {
268        format: Dxf,
269        stop_if_true: bool,
270    },
271    AboveAverage {
272        format: Dxf,
273        stop_if_true: bool,
274    },
275    BelowAverage {
276        format: Dxf,
277        stop_if_true: bool,
278    },
279    Top10 {
280        rank: u32,
281        percent: bool,
282        format: Dxf,
283        stop_if_true: bool,
284    },
285    Bottom10 {
286        rank: u32,
287        percent: bool,
288        format: Dxf,
289        stop_if_true: bool,
290    },
291    DataBar {
292        min: Option<Cfvo>,
293        max: Option<Cfvo>,
294        positive_color: Color,
295        negative_color: Color,
296        is_gradient: bool,
297        show_value: bool,
298    },
299    IconSet {
300        thresholds: Vec<IconThreshold>,
301        show_value: bool,
302    },
303    IconRating {
304        icon: Icon,
305        color: Color,
306        thresholds: Vec<(Cfvo, bool)>,
307        show_value: bool,
308    },
309}
310
311#[derive(Serialize, Deserialize, Encode, Decode, Debug, PartialEq, Clone)]
312pub struct ConditionalFormatting {
313    pub range: String,
314    pub cf_rule: CfRule,
315    pub priority: u32,
316}
317
318/// A conditional formatting rule together with its storage `index` in the
319/// worksheet's `conditional_formatting` vector.
320///
321/// `get_conditional_formatting_list` returns rules sorted by priority, which
322/// loses the link to the storage index that the index-based mutators
323/// (`get_dxf_for_conditional_formatting`, `update_conditional_formatting`,
324/// `delete_conditional_formatting`, `raise`/`lower_conditional_formatting_priority`)
325/// require. Carrying `index` here lets callers address a rule unambiguously
326/// regardless of the display ordering. This type is transient (never persisted).
327#[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
328pub struct ConditionalFormattingView {
329    pub index: usize,
330    pub range: String,
331    pub cf_rule: CfRule,
332    pub priority: u32,
333}
334
335// ---------------------------------------------------------------------------
336// Evaluated CF result for a single cell (transient, not stored in Workbook).
337// ---------------------------------------------------------------------------
338
339/// The winning CF result for a cell, stored in Model::cf_cache after evaluate().
340#[derive(Clone, Debug)]
341pub(crate) enum CfCellResult {
342    /// A dxf-based rule matched; dxf_id indexes into styles.dxfs.
343    Dxf(u32),
344    /// Color scale: the pre-computed interpolated fill color.
345    ColorScale(Color),
346    /// Data bar: proportion filled (0..1), colors, gradient flag, and show_value flag.
347    DataBar {
348        positive_color: Color,
349        negative_color: Color,
350        is_gradient: bool,
351        value: f64,
352        /// Proportion \[0,1\] at which the zero axis falls within the cell width.
353        axis_position: f64,
354        show_value: bool,
355    },
356    /// Custom icon: icon name (Icon enum variant) and color.
357    Icon {
358        icon: Icon,
359        color: Color,
360        show_value: bool,
361    },
362    /// Rating: show `count` copies of `icon` out of `max` possible.
363    Rating {
364        icon: Icon,
365        count: u32,
366        max: u32,
367        color: Color,
368        show_value: bool,
369    },
370}
371
372// ---------------------------------------------------------------------------
373// Public output types returned by get_cell_style().
374// ---------------------------------------------------------------------------
375
376/// Icon set decoration for a cell.
377#[derive(Serialize, Deserialize, Clone, Debug)]
378pub struct CfIcon {
379    pub icon: Icon,
380    pub color: Color,
381    pub show_value: bool,
382}
383
384/// Data bar decoration for a cell.
385#[derive(Serialize, Deserialize, Clone, Debug)]
386pub struct CfDataBar {
387    pub positive_color: Color,
388    pub negative_color: Color,
389    pub is_gradient: bool,
390    /// Proportion of the bar to fill, in \[0.0, 1.0\].
391    pub value: f64,
392    /// Proportion \[0,1\] at which the zero axis falls within the cell width.
393    pub axis_position: f64,
394    pub show_value: bool,
395}
396
397/// Rating decoration for a cell: show `count` copies of `icon` out of `max` possible.
398#[derive(Serialize, Deserialize, Clone, Debug)]
399pub struct CfRating {
400    /// Icon to show for each rating point (e.g. star, circle, etc.).
401    pub icon: Icon,
402    /// Number of filled icons to show (1..=max).
403    pub count: u32,
404    /// Maximum number of icons in the scale (3, 4, or 5).
405    pub max: u32,
406    pub color: Color,
407    pub show_value: bool,
408}
409
410/// Returns the ordered (Icon, color) pairs for a named Excel icon-set preset,
411/// ordered from lowest to highest value bucket.
412/// `name` is the XLSX `iconSetType` attribute value (e.g. `"3TrafficLights2"`).
413/// Returns `None` for unknown names.
414pub fn icon_set_icons(name: &str) -> Option<Vec<(Icon, Color)>> {
415    let s = |c: &'static str| Color::Rgb(c.to_string());
416    match name {
417        "3Arrows" => Some(vec![
418            (Icon::ArrowDown, s("#e43400")),
419            (Icon::ArrowRight, s("#ffeb84")),
420            (Icon::ArrowUp, s("#84cb1f")),
421        ]),
422        "3ArrowsGray" => Some(vec![
423            (Icon::ArrowDown, s("#808080")),
424            (Icon::ArrowRight, s("#808080")),
425            (Icon::ArrowUp, s("#808080")),
426        ]),
427        "4Arrows" => Some(vec![
428            (Icon::ArrowDown, s("#e43400")),
429            (Icon::ArrowAngleDown, s("#ffeb84")),
430            (Icon::ArrowAngleUp, s("#ffeb84")),
431            (Icon::ArrowUp, s("#84cb1f")),
432        ]),
433        "4ArrowsGray" => Some(vec![
434            (Icon::ArrowDown, s("#808080")),
435            (Icon::ArrowAngleDown, s("#808080")),
436            (Icon::ArrowAngleUp, s("#808080")),
437            (Icon::ArrowUp, s("#808080")),
438        ]),
439        "5Arrows" => Some(vec![
440            (Icon::ArrowDown, s("#e43400")),
441            (Icon::ArrowAngleDown, s("#ffeb84")),
442            (Icon::ArrowRight, s("#ffeb84")),
443            (Icon::ArrowAngleUp, s("#ffeb84")),
444            (Icon::ArrowUp, s("#84cb1f")),
445        ]),
446        "5ArrowsGray" => Some(vec![
447            (Icon::ArrowDown, s("#808080")),
448            (Icon::ArrowAngleDown, s("#808080")),
449            (Icon::ArrowRight, s("#808080")),
450            (Icon::ArrowAngleUp, s("#808080")),
451            (Icon::ArrowUp, s("#808080")),
452        ]),
453        "3Triangles" => Some(vec![
454            (Icon::TriangleDown, s("#f8696b")),
455            (Icon::FlatRectangle, s("#ffeb84")),
456            (Icon::TriangleUp, s("#63be7b")),
457        ]),
458        "3TrafficLights1" | "3TrafficLights" | "3TrafficLights2" => Some(vec![
459            (Icon::Circle, s("#f8696b")),
460            (Icon::Circle, s("#ffeb84")),
461            (Icon::Circle, s("#63be7b")),
462        ]),
463        "4TrafficLights" => Some(vec![
464            (Icon::Circle, s("#000000")),
465            (Icon::Circle, s("#f8696b")),
466            (Icon::Circle, s("#ffeb84")),
467            (Icon::Circle, s("#63be7b")),
468        ]),
469        "3Signs" => Some(vec![
470            (Icon::Cross, s("#f8696b")),
471            (Icon::TriangleUp, s("#ffeb84")),
472            (Icon::Circle, s("#63be7b")),
473        ]),
474        "4RedToBlack" => Some(vec![
475            (Icon::Circle, s("#000000")),
476            (Icon::Circle, s("#808080")),
477            (Icon::Circle, s("#f66f00")),
478            (Icon::Circle, s("#e43400")),
479        ]),
480        "3Symbols" => Some(vec![
481            (Icon::Cross, s("#f8696b")),
482            (Icon::Exclamation, s("#ffeb84")),
483            (Icon::Check, s("#63be7b")),
484        ]),
485        "3Symbols2" => Some(vec![
486            (Icon::Cross, s("#f8696b")),
487            (Icon::Exclamation, s("#ffeb84")),
488            (Icon::Check, s("#63be7b")),
489        ]),
490        "3Flags" => Some(vec![
491            (Icon::Flag, s("#f8696b")),
492            (Icon::Flag, s("#ffeb84")),
493            (Icon::Flag, s("#63be7b")),
494        ]),
495        _ => None,
496    }
497}
498
499/// The full visual description of a cell, including any conditional formatting overlay.
500#[derive(Serialize, Deserialize, Clone, Debug)]
501pub struct ExtendedStyle {
502    /// The final cell style (base style with any CF dxf/color-scale overlay applied).
503    pub style: Style,
504    /// Set when a icon-set rule applies to the cell.
505    pub icon: Option<CfIcon>,
506    /// Set when a data-bar rule applies to the cell.
507    pub data_bar: Option<CfDataBar>,
508    /// Set when a rating rule (IconSetRating3/4/5) applies to the cell.
509    pub rating: Option<CfRating>,
510}