ferrix_app/widgets/
line_charts.rs1use iced::{
24 Color as IColor, Element, Size, Theme,
25 widget::{
26 canvas::{Cache, Frame, Geometry},
27 container,
28 },
29};
30use plotters::prelude::*;
31use plotters_iced2::{Chart, ChartBuilder, ChartWidget, DrawingBackend};
32use std::collections::VecDeque;
33
34use crate::messages::Message;
35
36#[derive(Debug, Clone)]
37pub struct LineChart {
38 data: Vec<LineSeries>,
39 max_points: usize,
40 style: Style,
41}
42
43#[derive(Debug, Clone)]
44pub struct LineSeries {
45 name: String,
46 data: VecDeque<f64>,
47 color: RGBColor,
48 max_points: usize,
49}
50
51#[derive(Debug, Clone)]
52pub struct Style {
53 pub text_size: usize,
54 pub text_color: IColor,
55 pub y_axis_color: IColor,
56}
57
58impl Default for Style {
59 fn default() -> Self {
60 Self {
61 text_size: 11,
62 text_color: IColor::WHITE,
63 y_axis_color: IColor::WHITE,
64 }
65 }
66}
67
68fn to_rgbcolor(color: IColor) -> RGBColor {
69 let oc = color.into_rgba8();
70 RGBColor(oc[0], oc[1], oc[2])
71}
72
73impl LineSeries {
74 pub fn new(name: String, color: IColor, max_len: usize) -> Self {
75 Self {
76 name,
77 max_points: max_len,
78 color: to_rgbcolor(color),
79 data: VecDeque::with_capacity(max_len),
80 }
81 }
82
83 pub fn push(&mut self, value: f64) {
84 if self.data.len() > self.max_points {
85 self.data.pop_front();
86 }
87
88 self.data.push_back(value);
89 }
90}
91
92impl LineChart {
93 pub fn new() -> Self {
94 Self {
95 data: Vec::with_capacity(8),
96 max_points: 100,
97 style: Style::default(),
98 }
99 }
100
101 pub fn set_style(&mut self, theme: &Theme) {
102 let style = Style {
103 text_size: 12,
104 text_color: theme.palette().text,
105 y_axis_color: theme.palette().text,
106 };
107 self.style = style;
108 }
109
110 pub fn set_max_values(&mut self, value: usize) {
111 self.max_points = value;
112 for s in &mut self.data {
113 s.max_points = value;
114 }
115 self.update_axis();
116 }
117
118 pub fn series_count(&self) -> usize {
119 self.data.len()
120 }
121
122 pub fn push_series(&mut self, value: LineSeries) {
123 self.data.push(value);
124 }
125
126 pub fn push_to(&mut self, idx: usize, value: f64) {
127 if self.data.len() < idx {
128 return;
129 }
130 self.data[idx].push(value);
131 }
132
133 pub fn push_value(&mut self, value: f64, idx: usize) {
134 if self.data.len() < idx {
135 return;
136 }
137 self.update_axis();
138 self.data[idx].data.push_back(value);
139 }
140
141 pub fn view<'a>(&'a self) -> Element<'a, Message> {
142 let chart = ChartWidget::new(self);
143 container(chart).into()
144 }
145
146 fn update_axis(&mut self) {
147 'm: loop {
148 for s in &mut self.data {
149 if s.data.len() > self.max_points {
150 s.data.pop_front();
151 } else {
152 break 'm;
153 }
154 }
155 }
156 }
157}
158
159impl Chart<Message> for LineChart {
160 type State = ();
161
162 #[inline]
163 fn draw<R: plotters_iced2::Renderer, F: Fn(&mut Frame)>(
164 &self,
165 renderer: &R,
166 size: Size,
167 f: F,
168 ) -> Geometry {
169 renderer.draw_cache(&Cache::new(), size, f)
170 }
171
172 fn build_chart<DB: DrawingBackend>(&self, _state: &Self::State, mut builder: ChartBuilder<DB>) {
173 let mut chart = builder
174 .x_label_area_size(0)
175 .y_label_area_size(0)
176 .margin(20)
177 .build_cartesian_2d(0..(self.max_points), 0.0..100.0)
178 .expect("Failed to build chart");
179
180 chart
181 .configure_mesh()
182 .bold_line_style(to_rgbcolor(self.style.y_axis_color).mix(0.05))
183 .disable_x_axis()
184 .disable_x_mesh()
185 .light_line_style(TRANSPARENT)
186 .y_labels(8)
187 .x_labels(self.max_points)
188 .draw()
189 .expect("Failed to draw chart mesh");
190
191 for series in &self.data {
192 chart
193 .draw_series(
194 AreaSeries::new(
195 series.data.iter().enumerate().map(|x| (x.0, *x.1 as f64)),
196 0.,
197 plotters::style::TRANSPARENT,
198 )
199 .border_style(ShapeStyle::from(series.color).stroke_width(2)),
200 )
201 .expect("Failed to draw chart data")
202 .label(&series.name)
203 .legend(|(x, y)| {
204 Rectangle::new([(x - 5, y - 5), (x + 5, y + 5)], series.color.filled())
205 });
206 }
207
208 if !self.data.is_empty() {
209 chart
210 .configure_series_labels()
211 .label_font(
212 ("sans-serif", self.style.text_size as i32)
213 .into_font()
214 .color(&to_rgbcolor(self.style.text_color)),
215 )
216 .draw()
217 .expect("Failed to draw chart");
218 }
219 }
220}