leptos_chartistry/series/
stack.rs

1use super::{ApplyUseSeries, GetYValue, IntoUseLine, SeriesAcc, UseY};
2use crate::{
3    colours::{Colour, ColourScheme, BATLOW},
4    Line,
5};
6use leptos::prelude::*;
7use std::sync::Arc;
8
9/// Default colour scheme for stack. Assumes a light background with dark values for high values.
10pub const STACK_COLOUR_SCHEME: [Colour; 10] = BATLOW;
11
12/// Draws a stack of lines on top of each other.
13///
14/// # Example
15/// ```rust
16/// # use leptos_chartistry::*;
17/// # struct MyData { x: f64, y1: f64, y2: f64 }
18/// let stack = Stack::new()
19///     .line(Line::new(|data: &MyData| data.y1).with_name("fairies"))
20///     .line(Line::new(|data: &MyData| data.y2).with_name("pixies"));
21/// ```
22/// See this in action with the [stacked line chart example](https://feral-dot-io.github.io/leptos-chartistry/examples.html#stacked-line-chart).
23#[derive(Clone)]
24#[non_exhaustive]
25pub struct Stack<T, Y> {
26    lines: Vec<Line<T, Y>>,
27    /// Colour scheme for the stack. Interpolates colours across the whole scheme.
28    pub colours: RwSignal<ColourScheme>,
29}
30
31impl<T, Y> Stack<T, Y> {
32    /// Create a new empty stack.
33    pub fn new() -> Self {
34        Self::default()
35    }
36
37    /// Adds a line to the stack.
38    pub fn line(mut self, line: impl Into<Line<T, Y>>) -> Self {
39        self.lines.push(line.into());
40        self
41    }
42
43    /// Gets the current number of lines in the stack.
44    pub fn len(&self) -> usize {
45        self.lines.len()
46    }
47
48    /// Returns true if there are no lines in the stack.
49    pub fn is_empty(&self) -> bool {
50        self.lines.is_empty()
51    }
52
53    /// Sets the colour scheme for the stack.
54    pub fn with_colours<Opt>(self, colours: impl Into<ColourScheme>) -> Self {
55        self.colours.set(colours.into());
56        self
57    }
58}
59
60impl<T, Y> Default for Stack<T, Y> {
61    fn default() -> Self {
62        Self {
63            lines: Vec::new(),
64            colours: RwSignal::new(ColourScheme::from(STACK_COLOUR_SCHEME).invert()),
65        }
66    }
67}
68
69impl<T, Y, I: IntoIterator<Item = Line<T, Y>>> From<I> for Stack<T, Y> {
70    fn from(lines: I) -> Self {
71        let mut stack = Self::default();
72        for line in lines {
73            stack = stack.line(line);
74        }
75        stack
76    }
77}
78
79impl<T: 'static> ApplyUseSeries<T, f64> for Stack<T, f64> {
80    fn apply_use_series(self: Arc<Self>, series: &mut SeriesAcc<T, f64>) {
81        let colours = self.colours;
82        let total_lines = self.lines.len();
83        let mut previous = Vec::with_capacity(total_lines);
84        for (id, line) in self.lines.clone().into_iter().enumerate() {
85            let colour = Memo::new(move |_| colours.get().interpolate(id, total_lines));
86            let line = StackedLine {
87                line,
88                previous: previous.clone(),
89            };
90            // Add line
91            let get_y = series.push_line(colour, line);
92            // Sum next line with this one
93            previous.push(get_y);
94        }
95    }
96}
97
98#[derive(Clone)]
99struct StackedLine<T, Y> {
100    line: Line<T, Y>,
101    previous: Vec<Arc<dyn GetYValue<T, Y>>>,
102}
103
104#[derive(Clone)]
105struct UseStackLine<T, Y> {
106    line: Arc<dyn GetYValue<T, Y>>,
107    previous: Vec<Arc<dyn GetYValue<T, Y>>>,
108}
109
110impl<T: 'static> IntoUseLine<T, f64> for StackedLine<T, f64> {
111    fn into_use_line(self, id: usize, colour: Memo<Colour>) -> (UseY, Arc<dyn GetYValue<T, f64>>) {
112        let (line, get_y) = self.line.into_use_line(id, colour);
113        let get_y = Arc::new(UseStackLine {
114            line: get_y,
115            previous: self.previous.clone(),
116        });
117        (line, get_y)
118    }
119}
120
121impl<T> GetYValue<T, f64> for UseStackLine<T, f64> {
122    fn value(&self, t: &T) -> f64 {
123        self.line.value(t)
124    }
125
126    fn stacked_value(&self, t: &T) -> f64 {
127        self.previous
128            .iter()
129            .chain(std::iter::once(&self.line))
130            .map(|get_y| get_y.value(t))
131            .filter(|v| v.is_normal())
132            .sum()
133    }
134}