dioxus_charts/
grid.rs

1use log::debug;
2
3use crate::types::*;
4use crate::utils::magnitude;
5
6const LABEL_OFFSET: f32 = 6.0;
7const TICK_SIZE: f32 = 10.0;
8
9#[derive(Copy, Clone)]
10pub enum Direction {
11    Horizontal,
12    Vertical,
13}
14
15#[derive(Copy, Clone)]
16pub(crate) struct Axis {
17    view: Rect,
18    step_len: f32,
19    steps: i32,
20    world_start: f32,
21    world: f32,
22    grid_ticks: bool,
23    label_interpolation: Option<fn(f32) -> String>,
24    label_size: i32,
25    direction: Direction,
26}
27
28impl Default for Axis {
29    fn default() -> Self {
30        Self {
31            view: Rect::default(),
32            step_len: 0.0,
33            steps: 0,
34            world_start: 0.0,
35            world: 0.0,
36            grid_ticks: false,
37            label_interpolation: None,
38            label_size: 60,
39            direction: Direction::Horizontal,
40        }
41    }
42}
43
44impl Axis {
45    pub fn builder<'a>() -> AxisBuilder<'a> {
46        AxisBuilder::default()
47    }
48
49    pub fn world_to_view(&self, v: f32, start_offset: f32) -> f32 {
50        if self.world > 0.0 {
51            match self.direction {
52                Direction::Vertical => v / self.world * self.view.width() + self.view.min.x,
53                Direction::Horizontal => {
54                    let c = (v - start_offset) / self.world * self.view.height() + self.view.min.y;
55                    self.view.min.y - c + self.view.max.y
56                }
57            }
58        } else {
59            0.0
60        }
61    }
62
63    pub fn step_to_world(&self, v: f32) -> f32 {
64        self.world / (self.steps as f32 - 1.0) * v
65    }
66
67    pub fn lines(&self) -> Vec<Rect> {
68        let mut lines = Vec::<Rect>::new();
69
70        for i in 0..self.steps {
71            let w = self.step_to_world(i as f32);
72            let v = self.world_to_view(w, 0.0);
73
74            match self.direction {
75                Direction::Vertical => {
76                    let end = if self.grid_ticks && i != 0 {
77                        self.view.max.y - TICK_SIZE
78                    } else {
79                        self.view.min.y
80                    };
81
82                    lines.push(Rect::new(v, self.view.max.y, v, end));
83                }
84                Direction::Horizontal => {
85                    let end = if self.grid_ticks && i != 0 {
86                        self.view.min.x + TICK_SIZE
87                    } else {
88                        self.view.max.x
89                    };
90
91                    lines.push(Rect::new(self.view.min.x, v, end, v));
92                }
93            }
94        }
95
96        lines
97    }
98
99    pub fn tick_centers(&self) -> Vec<Point> {
100        let mut points = Vec::<Point>::new();
101
102        for i in 1..self.steps {
103            let p = i - 1;
104            let w1 = self.step_to_world(p as f32);
105            let w2 = self.step_to_world(i as f32);
106            let v1 = self.world_to_view(w1, 0.0);
107            let v2 = self.world_to_view(w2, 0.0);
108            let center = (v1 + v2) / 2.0;
109
110            match self.direction {
111                Direction::Vertical => {
112                    let center = (v1 + v2) / 2.0;
113                    points.push(Point::new(center, self.view.max.y));
114                }
115                Direction::Horizontal => {
116                    points.push(Point::new(self.view.min.x, center));
117                }
118            }
119        }
120
121        match self.direction {
122            Direction::Vertical => points,
123            Direction::Horizontal => points.into_iter().rev().collect(),
124        }
125    }
126
127    pub fn centered_text_rects(&self, n_labels: i32) -> Vec<Rect> {
128        let mut texts = Vec::<Rect>::new();
129        let n_labels = self.steps.min(n_labels + 1);
130
131        for i in 1..n_labels {
132            let p = i - 1;
133            let w1 = self.step_to_world(p as f32);
134            let w2 = self.step_to_world(i as f32);
135            let v1 = self.world_to_view(w1, 0.0);
136            let v2 = self.world_to_view(w2, 0.0);
137
138            match self.direction {
139                Direction::Vertical => {
140                    let width = v2 - v1;
141                    let height = self.label_size as f32;
142                    texts.push(Rect::new(v1, self.view.max.y + LABEL_OFFSET, width, height));
143                }
144                Direction::Horizontal => {
145                    let height = v1 - v2;
146                    let width = self.label_size as f32;
147                    texts.push(Rect::new(
148                        self.view.min.x - LABEL_OFFSET - width,
149                        v2,
150                        width,
151                        height,
152                    ));
153                }
154            }
155        }
156
157        texts
158    }
159
160    pub fn text_data(&self, n_labels: usize) -> Vec<TextData> {
161        let mut texts = Vec::<TextData>::new();
162        let n_labels = self.steps.min(n_labels as i32);
163
164        for i in 0..n_labels {
165            let w = self.step_to_world(i as f32);
166            let v = self.world_to_view(w, 0.0);
167
168            match self.direction {
169                Direction::Vertical => {
170                    texts.push(TextData {
171                        x: v,
172                        y: self.view.max.y + LABEL_OFFSET,
173                        anchor: "start",
174                        baseline: "hanging",
175                    });
176                }
177                Direction::Horizontal => {
178                    texts.push(TextData {
179                        x: self.view.min.x - LABEL_OFFSET,
180                        y: v,
181                        anchor: "end",
182                        baseline: "text-bottom",
183                    });
184                }
185            }
186        }
187
188        texts
189    }
190
191    pub fn generated_labels(&self) -> Labels {
192        let mut labels = Labels::new();
193
194        for i in 0..=self.steps {
195            if let Some(func) = self.label_interpolation {
196                labels.push(func(self.world_start + i as f32 * self.step_len));
197            } else {
198                labels.push(format!("{}", self.world_start + i as f32 * self.step_len));
199            }
200        }
201
202        labels
203    }
204}
205
206pub(crate) struct AxisBuilder<'a> {
207    view: Rect,
208    lowest: Option<f32>,
209    highest: Option<f32>,
210    direction: Direction,
211    label_interpolation: Option<fn(f32) -> String>,
212    labels_centered: bool,
213    label_size: i32,
214    grid_ticks: bool,
215    max_ticks: i32,
216    stacked_series: bool,
217    series: Option<&'a Series>,
218    labels: Option<&'a Labels>,
219}
220
221impl<'a> Default for AxisBuilder<'a> {
222    fn default() -> Self {
223        Self {
224            view: Rect::default(),
225            highest: None,
226            lowest: None,
227            direction: Direction::Horizontal,
228            label_interpolation: None,
229            labels_centered: false,
230            label_size: 60,
231            grid_ticks: false,
232            max_ticks: 8,
233            stacked_series: false,
234            series: None,
235            labels: None,
236        }
237    }
238}
239
240impl<'a> AxisBuilder<'a> {
241    pub fn with_view(mut self, view: Rect) -> Self {
242        self.view = view;
243        self
244    }
245
246    pub fn with_lowest(mut self, lowest: Option<f32>) -> Self {
247        self.lowest = lowest;
248        self
249    }
250
251    pub fn with_highest(mut self, highest: Option<f32>) -> Self {
252        self.highest = highest;
253        self
254    }
255
256    pub fn with_series(mut self, series: &'a Series) -> Self {
257        self.series = Some(series);
258        self
259    }
260
261    pub fn with_stacked_series(mut self, stacked: bool) -> Self {
262        self.stacked_series = stacked;
263        self
264    }
265
266    pub fn with_labels(mut self, labels: Option<&'a Labels>) -> Self {
267        self.labels = labels;
268        self
269    }
270
271    pub fn with_label_size(mut self, size: i32) -> Self {
272        self.label_size = size;
273        self
274    }
275
276    pub fn with_max_ticks(mut self, n_ticks: i32) -> Self {
277        self.max_ticks = n_ticks;
278        self
279    }
280
281    pub fn with_grid_ticks(mut self, show_ticks: bool) -> Self {
282        self.grid_ticks = show_ticks;
283        self
284    }
285
286    pub fn with_direction(mut self, direction: Direction) -> Self {
287        self.direction = direction;
288        self
289    }
290
291    pub fn with_centered_labels(mut self, labels: Option<&'a Labels>) -> Self {
292        self.labels = labels;
293        self.labels_centered = true;
294        self
295    }
296
297    pub fn with_label_interpolation(mut self, func: Option<fn(f32) -> String>) -> Self {
298        self.label_interpolation = func;
299        self
300    }
301
302    pub fn build(self) -> Axis {
303        if let Some(series) = self.series {
304            let highest = if let Some(high) = self.highest {
305                high
306            } else if self.stacked_series {
307                MultiZip(series.iter().map(|a| a.iter().copied()).collect())
308                    .map(|t| t.iter().sum())
309                    .reduce(f32::max)
310                    .unwrap()
311            } else {
312                series
313                    .iter()
314                    .map(|a| a.iter().copied().reduce(f32::max).unwrap())
315                    .reduce(f32::max)
316                    .unwrap()
317            };
318
319            //if self.stacked_series {
320            //    let mzip = MultiZip(series.iter().map(|a| a.iter()).collect());
321
322            //    for z in mzip {
323            //        debug!("{z:?}");
324            //    }
325            //}
326
327            let lowest = if let Some(low) = self.lowest {
328                low
329            } else {
330                series
331                    .iter()
332                    .map(|a| a.iter().copied().reduce(f32::min).unwrap())
333                    .reduce(f32::min)
334                    .unwrap()
335            };
336
337            debug!("highest: {}", highest);
338            debug!("lowest: {}", lowest);
339            let value_range = highest - lowest;
340            let minimum_tick = value_range / (self.max_ticks as f32 - 2.0);
341            let magnitude = magnitude(minimum_tick);
342            let residual = minimum_tick / magnitude;
343            debug!("magnitude: {}", magnitude);
344
345            let step = match residual {
346                n if n > 9.0 => 10.0,
347                n if n > 8.0 => 9.0,
348                n if n > 7.0 => 8.0,
349                n if n > 6.0 => 7.0,
350                n if n > 5.0 => 6.0,
351                n if n > 4.0 => 5.0,
352                n if n > 3.0 => 4.0,
353                n if n > 2.5 => 3.0,
354                n if n > 2.0 => 2.5,
355                n if n > 1.5 => 2.0,
356                n if n > 1.0 => 1.5,
357                _ => 1.0,
358            } * magnitude;
359
360            debug!("step_len: {}", step);
361
362            let max = if self.highest.is_some() {
363                highest
364            } else {
365                let max = (highest / step).ceil() * step;
366                if max < highest {
367                    debug!("step added to max");
368                    max + step
369                } else {
370                    max
371                }
372            };
373
374            let min = if self.lowest.is_some() {
375                lowest
376            } else {
377                let min = (lowest / step).floor() * step;
378                if min > lowest {
379                    debug!("step added to min");
380                    min - step
381                } else {
382                    min
383                }
384            };
385
386            let range = max - min;
387            debug!("range: {} min: {}, max: {}", range, min, max);
388            let steps = unsafe { (range / step).round().to_int_unchecked::<i32>() + 1 };
389            debug!("steps: {}", steps);
390
391            Axis {
392                view: self.view,
393                step_len: step,
394                steps,
395                world_start: min,
396                world: range,
397                label_interpolation: self.label_interpolation,
398                grid_ticks: self.grid_ticks,
399                label_size: self.label_size,
400                direction: self.direction,
401            }
402        } else if let Some(labels) = self.labels {
403            let len = labels.len();
404            let steps = if self.labels_centered {
405                len as i32 + 1
406            } else {
407                len as i32
408            };
409
410            Axis {
411                view: self.view,
412                step_len: steps as f32 / (steps as f32 - 1.0),
413                steps,
414                world: steps as f32,
415                grid_ticks: self.grid_ticks,
416                label_size: self.label_size,
417                direction: self.direction,
418                ..Axis::default()
419            }
420        } else {
421            Axis::default()
422        }
423    }
424}
425
426pub(crate) struct Grid {
427    pub x: Axis,
428    pub y: Axis,
429}
430
431impl Grid {
432    pub fn new<'a>(x: AxisBuilder<'a>, y: AxisBuilder<'a>) -> Grid {
433        Grid {
434            x: x.with_direction(Direction::Vertical).build(),
435            y: y.with_direction(Direction::Horizontal).build(),
436        }
437    }
438
439    pub fn world_to_view(&self, cx: f32, cy: f32, inverted: bool) -> Point {
440        if inverted {
441            Point {
442                x: self.x.world_to_view(cx, self.x.world_start),
443                y: self.y.world_to_view(self.y.step_to_world(cy), 0.0),
444            }
445        } else {
446            Point {
447                x: self.x.world_to_view(self.x.step_to_world(cx), 0.0),
448                y: self.y.world_to_view(cy, self.y.world_start),
449            }
450        }
451    }
452
453    pub fn lines(&self) -> Vec<Rect> {
454        [self.x.lines().as_slice(), self.y.lines().as_slice()].concat()
455    }
456
457    pub fn text_data(&self, x_n_labels: Option<usize>, y_n_labels: Option<usize>) -> Vec<TextData> {
458        [
459            self.x.text_data(x_n_labels.unwrap_or(0)).as_slice(),
460            self.y.text_data(y_n_labels.unwrap_or(0)).as_slice(),
461        ]
462        .concat()
463    }
464}