1use std::time::{Duration, Instant};
17
18use color_eyre::Result;
19use ratatui::{
20 crossterm::event::{self, Event, KeyCode},
21 layout::{Constraint, Layout, Rect},
22 style::{Color, Modifier, Style, Stylize},
23 symbols::{self, Marker},
24 text::{Line, Span},
25 widgets::{Axis, Block, Chart, Dataset, GraphType, LegendPosition},
26 DefaultTerminal, Frame,
27};
28
29fn main() -> Result<()> {
30 color_eyre::install()?;
31 let terminal = ratatui::init();
32 let app_result = App::new().run(terminal);
33 ratatui::restore();
34 app_result
35}
36
37struct App {
38 signal1: SinSignal,
39 data1: Vec<(f64, f64)>,
40 signal2: SinSignal,
41 data2: Vec<(f64, f64)>,
42 window: [f64; 2],
43}
44
45#[derive(Clone)]
46struct SinSignal {
47 x: f64,
48 interval: f64,
49 period: f64,
50 scale: f64,
51}
52
53impl SinSignal {
54 const fn new(interval: f64, period: f64, scale: f64) -> Self {
55 Self {
56 x: 0.0,
57 interval,
58 period,
59 scale,
60 }
61 }
62}
63
64impl Iterator for SinSignal {
65 type Item = (f64, f64);
66 fn next(&mut self) -> Option<Self::Item> {
67 let point = (self.x, (self.x * 1.0 / self.period).sin() * self.scale);
68 self.x += self.interval;
69 Some(point)
70 }
71}
72
73impl App {
74 fn new() -> Self {
75 let mut signal1 = SinSignal::new(0.2, 3.0, 18.0);
76 let mut signal2 = SinSignal::new(0.1, 2.0, 10.0);
77 let data1 = signal1.by_ref().take(200).collect::<Vec<(f64, f64)>>();
78 let data2 = signal2.by_ref().take(200).collect::<Vec<(f64, f64)>>();
79 Self {
80 signal1,
81 data1,
82 signal2,
83 data2,
84 window: [0.0, 20.0],
85 }
86 }
87
88 fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> {
89 let tick_rate = Duration::from_millis(250);
90 let mut last_tick = Instant::now();
91 loop {
92 terminal.draw(|frame| self.draw(frame))?;
93
94 let timeout = tick_rate.saturating_sub(last_tick.elapsed());
95 if event::poll(timeout)? {
96 if let Event::Key(key) = event::read()? {
97 if key.code == KeyCode::Char('q') {
98 return Ok(());
99 }
100 }
101 }
102 if last_tick.elapsed() >= tick_rate {
103 self.on_tick();
104 last_tick = Instant::now();
105 }
106 }
107 }
108
109 fn on_tick(&mut self) {
110 self.data1.drain(0..5);
111 self.data1.extend(self.signal1.by_ref().take(5));
112
113 self.data2.drain(0..10);
114 self.data2.extend(self.signal2.by_ref().take(10));
115
116 self.window[0] += 1.0;
117 self.window[1] += 1.0;
118 }
119
120 fn draw(&self, frame: &mut Frame) {
121 let [top, bottom] = Layout::vertical([Constraint::Fill(1); 2]).areas(frame.area());
122 let [animated_chart, bar_chart] =
123 Layout::horizontal([Constraint::Fill(1), Constraint::Length(29)]).areas(top);
124 let [line_chart, scatter] = Layout::horizontal([Constraint::Fill(1); 2]).areas(bottom);
125
126 self.render_animated_chart(frame, animated_chart);
127 render_barchart(frame, bar_chart);
128 render_line_chart(frame, line_chart);
129 render_scatter(frame, scatter);
130 }
131
132 fn render_animated_chart(&self, frame: &mut Frame, area: Rect) {
133 let x_labels = vec![
134 Span::styled(
135 format!("{}", self.window[0]),
136 Style::default().add_modifier(Modifier::BOLD),
137 ),
138 Span::raw(format!("{}", (self.window[0] + self.window[1]) / 2.0)),
139 Span::styled(
140 format!("{}", self.window[1]),
141 Style::default().add_modifier(Modifier::BOLD),
142 ),
143 ];
144 let datasets = vec![
145 Dataset::default()
146 .name("data2")
147 .marker(symbols::Marker::Dot)
148 .style(Style::default().fg(Color::Cyan))
149 .data(&self.data1),
150 Dataset::default()
151 .name("data3")
152 .marker(symbols::Marker::Braille)
153 .style(Style::default().fg(Color::Yellow))
154 .data(&self.data2),
155 ];
156
157 let chart = Chart::new(datasets)
158 .block(Block::bordered())
159 .x_axis(
160 Axis::default()
161 .title("X Axis")
162 .style(Style::default().fg(Color::Gray))
163 .labels(x_labels)
164 .bounds(self.window),
165 )
166 .y_axis(
167 Axis::default()
168 .title("Y Axis")
169 .style(Style::default().fg(Color::Gray))
170 .labels(["-20".bold(), "0".into(), "20".bold()])
171 .bounds([-20.0, 20.0]),
172 );
173
174 frame.render_widget(chart, area);
175 }
176}
177
178fn render_barchart(frame: &mut Frame, bar_chart: Rect) {
179 let dataset = Dataset::default()
180 .marker(symbols::Marker::HalfBlock)
181 .style(Style::new().fg(Color::Blue))
182 .graph_type(GraphType::Bar)
183 .data(&[
185 (0., 0.4),
186 (10., 2.9),
187 (20., 13.5),
188 (30., 41.1),
189 (40., 80.1),
190 (50., 100.0),
191 (60., 80.1),
192 (70., 41.1),
193 (80., 13.5),
194 (90., 2.9),
195 (100., 0.4),
196 ]);
197
198 let chart = Chart::new(vec![dataset])
199 .block(Block::bordered().title_top(Line::from("Bar chart").cyan().bold().centered()))
200 .x_axis(
201 Axis::default()
202 .style(Style::default().gray())
203 .bounds([0.0, 100.0])
204 .labels(["0".bold(), "50".into(), "100.0".bold()]),
205 )
206 .y_axis(
207 Axis::default()
208 .style(Style::default().gray())
209 .bounds([0.0, 100.0])
210 .labels(["0".bold(), "50".into(), "100.0".bold()]),
211 )
212 .hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
213
214 frame.render_widget(chart, bar_chart);
215}
216
217fn render_line_chart(frame: &mut Frame, area: Rect) {
218 let datasets = vec![Dataset::default()
219 .name("Line from only 2 points".italic())
220 .marker(symbols::Marker::Braille)
221 .style(Style::default().fg(Color::Yellow))
222 .graph_type(GraphType::Line)
223 .data(&[(1., 1.), (4., 4.)])];
224
225 let chart = Chart::new(datasets)
226 .block(Block::bordered().title(Line::from("Line chart").cyan().bold().centered()))
227 .x_axis(
228 Axis::default()
229 .title("X Axis")
230 .style(Style::default().gray())
231 .bounds([0.0, 5.0])
232 .labels(["0".bold(), "2.5".into(), "5.0".bold()]),
233 )
234 .y_axis(
235 Axis::default()
236 .title("Y Axis")
237 .style(Style::default().gray())
238 .bounds([0.0, 5.0])
239 .labels(["0".bold(), "2.5".into(), "5.0".bold()]),
240 )
241 .legend_position(Some(LegendPosition::TopLeft))
242 .hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
243
244 frame.render_widget(chart, area);
245}
246
247fn render_scatter(frame: &mut Frame, area: Rect) {
248 let datasets = vec![
249 Dataset::default()
250 .name("Heavy")
251 .marker(Marker::Dot)
252 .graph_type(GraphType::Scatter)
253 .style(Style::new().yellow())
254 .data(&HEAVY_PAYLOAD_DATA),
255 Dataset::default()
256 .name("Medium".underlined())
257 .marker(Marker::Braille)
258 .graph_type(GraphType::Scatter)
259 .style(Style::new().magenta())
260 .data(&MEDIUM_PAYLOAD_DATA),
261 Dataset::default()
262 .name("Small")
263 .marker(Marker::Dot)
264 .graph_type(GraphType::Scatter)
265 .style(Style::new().cyan())
266 .data(&SMALL_PAYLOAD_DATA),
267 ];
268
269 let chart = Chart::new(datasets)
270 .block(Block::bordered().title(Line::from("Scatter chart").cyan().bold().centered()))
271 .x_axis(
272 Axis::default()
273 .title("Year")
274 .bounds([1960., 2020.])
275 .style(Style::default().fg(Color::Gray))
276 .labels(["1960", "1990", "2020"]),
277 )
278 .y_axis(
279 Axis::default()
280 .title("Cost")
281 .bounds([0., 75000.])
282 .style(Style::default().fg(Color::Gray))
283 .labels(["0", "37 500", "75 000"]),
284 )
285 .hidden_legend_constraints((Constraint::Ratio(1, 2), Constraint::Ratio(1, 2)));
286
287 frame.render_widget(chart, area);
288}
289
290const HEAVY_PAYLOAD_DATA: [(f64, f64); 9] = [
292 (1965., 8200.),
293 (1967., 5400.),
294 (1981., 65400.),
295 (1989., 30800.),
296 (1997., 10200.),
297 (2004., 11600.),
298 (2014., 4500.),
299 (2016., 7900.),
300 (2018., 1500.),
301];
302
303const MEDIUM_PAYLOAD_DATA: [(f64, f64); 29] = [
304 (1963., 29500.),
305 (1964., 30600.),
306 (1965., 177_900.),
307 (1965., 21000.),
308 (1966., 17900.),
309 (1966., 8400.),
310 (1975., 17500.),
311 (1982., 8300.),
312 (1985., 5100.),
313 (1988., 18300.),
314 (1990., 38800.),
315 (1990., 9900.),
316 (1991., 18700.),
317 (1992., 9100.),
318 (1994., 10500.),
319 (1994., 8500.),
320 (1994., 8700.),
321 (1997., 6200.),
322 (1999., 18000.),
323 (1999., 7600.),
324 (1999., 8900.),
325 (1999., 9600.),
326 (2000., 16000.),
327 (2001., 10000.),
328 (2002., 10400.),
329 (2002., 8100.),
330 (2010., 2600.),
331 (2013., 13600.),
332 (2017., 8000.),
333];
334
335const SMALL_PAYLOAD_DATA: [(f64, f64); 23] = [
336 (1961., 118_500.),
337 (1962., 14900.),
338 (1975., 21400.),
339 (1980., 32800.),
340 (1988., 31100.),
341 (1990., 41100.),
342 (1993., 23600.),
343 (1994., 20600.),
344 (1994., 34600.),
345 (1996., 50600.),
346 (1997., 19200.),
347 (1997., 45800.),
348 (1998., 19100.),
349 (2000., 73100.),
350 (2003., 11200.),
351 (2008., 12600.),
352 (2010., 30500.),
353 (2012., 20000.),
354 (2013., 10600.),
355 (2013., 34500.),
356 (2015., 10600.),
357 (2018., 23100.),
358 (2019., 17300.),
359];