1 use tui::{
2     backend::TestBackend,
3     buffer::Buffer,
4     layout::Rect,
5     style::{Color, Style},
6     symbols,
7     text::Span,
8     widgets::{Axis, Block, Borders, Chart, Dataset, GraphType::Line},
9     Terminal,
10 };
11 
create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>>12 fn create_labels<'a>(labels: &'a [&'a str]) -> Vec<Span<'a>> {
13     labels.iter().map(|l| Span::from(*l)).collect()
14 }
15 
16 #[test]
widgets_chart_can_render_on_small_areas()17 fn widgets_chart_can_render_on_small_areas() {
18     let test_case = |width, height| {
19         let backend = TestBackend::new(width, height);
20         let mut terminal = Terminal::new(backend).unwrap();
21         terminal
22             .draw(|f| {
23                 let datasets = vec![Dataset::default()
24                     .marker(symbols::Marker::Braille)
25                     .style(Style::default().fg(Color::Magenta))
26                     .data(&[(0.0, 0.0)])];
27                 let chart = Chart::new(datasets)
28                     .block(Block::default().title("Plot").borders(Borders::ALL))
29                     .x_axis(
30                         Axis::default()
31                             .bounds([0.0, 0.0])
32                             .labels(create_labels(&["0.0", "1.0"])),
33                     )
34                     .y_axis(
35                         Axis::default()
36                             .bounds([0.0, 0.0])
37                             .labels(create_labels(&["0.0", "1.0"])),
38                     );
39                 f.render_widget(chart, f.size());
40             })
41             .unwrap();
42     };
43     test_case(0, 0);
44     test_case(0, 1);
45     test_case(1, 0);
46     test_case(1, 1);
47     test_case(2, 2);
48 }
49 
50 #[test]
widgets_chart_handles_long_labels()51 fn widgets_chart_handles_long_labels() {
52     let test_case = |x_labels, y_labels, lines| {
53         let backend = TestBackend::new(10, 5);
54         let mut terminal = Terminal::new(backend).unwrap();
55         terminal
56             .draw(|f| {
57                 let datasets = vec![Dataset::default()
58                     .marker(symbols::Marker::Braille)
59                     .style(Style::default().fg(Color::Magenta))
60                     .data(&[(2.0, 2.0)])];
61                 let mut x_axis = Axis::default().bounds([0.0, 1.0]);
62                 if let Some((left_label, right_label)) = x_labels {
63                     x_axis = x_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
64                 }
65                 let mut y_axis = Axis::default().bounds([0.0, 1.0]);
66                 if let Some((left_label, right_label)) = y_labels {
67                     y_axis = y_axis.labels(vec![Span::from(left_label), Span::from(right_label)]);
68                 }
69                 let chart = Chart::new(datasets).x_axis(x_axis).y_axis(y_axis);
70                 f.render_widget(chart, f.size());
71             })
72             .unwrap();
73         let expected = Buffer::with_lines(lines);
74         terminal.backend().assert_buffer(&expected);
75     };
76     test_case(
77         Some(("AAAA", "B")),
78         None,
79         vec![
80             "          ",
81             "          ",
82             "          ",
83             "   ───────",
84             "AAA      B",
85         ],
86     );
87     test_case(
88         Some(("A", "BBBB")),
89         None,
90         vec![
91             "          ",
92             "          ",
93             "          ",
94             " ─────────",
95             "A     BBBB",
96         ],
97     );
98     test_case(
99         Some(("AAAAAAAAAAA", "B")),
100         None,
101         vec![
102             "          ",
103             "          ",
104             "          ",
105             "   ───────",
106             "AAA      B",
107         ],
108     );
109     test_case(
110         Some(("A", "B")),
111         Some(("CCCCCCC", "D")),
112         vec![
113             "D  │      ",
114             "   │      ",
115             "CCC│      ",
116             "   └──────",
117             "   A     B",
118         ],
119     );
120 }
121 
122 #[test]
widgets_chart_can_have_axis_with_zero_length_bounds()123 fn widgets_chart_can_have_axis_with_zero_length_bounds() {
124     let backend = TestBackend::new(100, 100);
125     let mut terminal = Terminal::new(backend).unwrap();
126 
127     terminal
128         .draw(|f| {
129             let datasets = vec![Dataset::default()
130                 .marker(symbols::Marker::Braille)
131                 .style(Style::default().fg(Color::Magenta))
132                 .data(&[(0.0, 0.0)])];
133             let chart = Chart::new(datasets)
134                 .block(Block::default().title("Plot").borders(Borders::ALL))
135                 .x_axis(
136                     Axis::default()
137                         .bounds([0.0, 0.0])
138                         .labels(create_labels(&["0.0", "1.0"])),
139                 )
140                 .y_axis(
141                     Axis::default()
142                         .bounds([0.0, 0.0])
143                         .labels(create_labels(&["0.0", "1.0"])),
144                 );
145             f.render_widget(
146                 chart,
147                 Rect {
148                     x: 0,
149                     y: 0,
150                     width: 100,
151                     height: 100,
152                 },
153             );
154         })
155         .unwrap();
156 }
157 
158 #[test]
widgets_chart_handles_overflows()159 fn widgets_chart_handles_overflows() {
160     let backend = TestBackend::new(80, 30);
161     let mut terminal = Terminal::new(backend).unwrap();
162 
163     terminal
164         .draw(|f| {
165             let datasets = vec![Dataset::default()
166                 .marker(symbols::Marker::Braille)
167                 .style(Style::default().fg(Color::Magenta))
168                 .data(&[
169                     (1_588_298_471.0, 1.0),
170                     (1_588_298_473.0, 0.0),
171                     (1_588_298_496.0, 1.0),
172                 ])];
173             let chart = Chart::new(datasets)
174                 .block(Block::default().title("Plot").borders(Borders::ALL))
175                 .x_axis(
176                     Axis::default()
177                         .bounds([1_588_298_471.0, 1_588_992_600.0])
178                         .labels(create_labels(&["1588298471.0", "1588992600.0"])),
179                 )
180                 .y_axis(
181                     Axis::default()
182                         .bounds([0.0, 1.0])
183                         .labels(create_labels(&["0.0", "1.0"])),
184                 );
185             f.render_widget(
186                 chart,
187                 Rect {
188                     x: 0,
189                     y: 0,
190                     width: 80,
191                     height: 30,
192                 },
193             );
194         })
195         .unwrap();
196 }
197 
198 #[test]
widgets_chart_can_have_empty_datasets()199 fn widgets_chart_can_have_empty_datasets() {
200     let backend = TestBackend::new(100, 100);
201     let mut terminal = Terminal::new(backend).unwrap();
202 
203     terminal
204         .draw(|f| {
205             let datasets = vec![Dataset::default().data(&[]).graph_type(Line)];
206             let chart = Chart::new(datasets)
207                 .block(
208                     Block::default()
209                         .title("Empty Dataset With Line")
210                         .borders(Borders::ALL),
211                 )
212                 .x_axis(
213                     Axis::default()
214                         .bounds([0.0, 0.0])
215                         .labels(create_labels(&["0.0", "1.0"])),
216                 )
217                 .y_axis(
218                     Axis::default()
219                         .bounds([0.0, 1.0])
220                         .labels(create_labels(&["0.0", "1.0"])),
221                 );
222             f.render_widget(
223                 chart,
224                 Rect {
225                     x: 0,
226                     y: 0,
227                     width: 100,
228                     height: 100,
229                 },
230             );
231         })
232         .unwrap();
233 }
234 
235 #[test]
widgets_chart_can_have_a_legend()236 fn widgets_chart_can_have_a_legend() {
237     let backend = TestBackend::new(60, 30);
238     let mut terminal = Terminal::new(backend).unwrap();
239     terminal
240         .draw(|f| {
241             let datasets = vec![
242                 Dataset::default()
243                     .name("Dataset 1")
244                     .style(Style::default().fg(Color::Blue))
245                     .data(&[
246                         (0.0, 0.0),
247                         (10.0, 1.0),
248                         (20.0, 2.0),
249                         (30.0, 3.0),
250                         (40.0, 4.0),
251                         (50.0, 5.0),
252                         (60.0, 6.0),
253                         (70.0, 7.0),
254                         (80.0, 8.0),
255                         (90.0, 9.0),
256                         (100.0, 10.0),
257                     ])
258                     .graph_type(Line),
259                 Dataset::default()
260                     .name("Dataset 2")
261                     .style(Style::default().fg(Color::Green))
262                     .data(&[
263                         (0.0, 10.0),
264                         (10.0, 9.0),
265                         (20.0, 8.0),
266                         (30.0, 7.0),
267                         (40.0, 6.0),
268                         (50.0, 5.0),
269                         (60.0, 4.0),
270                         (70.0, 3.0),
271                         (80.0, 2.0),
272                         (90.0, 1.0),
273                         (100.0, 0.0),
274                     ])
275                     .graph_type(Line),
276             ];
277             let chart = Chart::new(datasets)
278                 .style(Style::default().bg(Color::White))
279                 .block(Block::default().title("Chart Test").borders(Borders::ALL))
280                 .x_axis(
281                     Axis::default()
282                         .bounds([0.0, 100.0])
283                         .title(Span::styled("X Axis", Style::default().fg(Color::Yellow)))
284                         .labels(create_labels(&["0.0", "50.0", "100.0"])),
285                 )
286                 .y_axis(
287                     Axis::default()
288                         .bounds([0.0, 10.0])
289                         .title("Y Axis")
290                         .labels(create_labels(&["0.0", "5.0", "10.0"])),
291                 );
292             f.render_widget(
293                 chart,
294                 Rect {
295                     x: 0,
296                     y: 0,
297                     width: 60,
298                     height: 30,
299                 },
300             );
301         })
302         .unwrap();
303     let mut expected = Buffer::with_lines(vec![
304         "┌Chart Test────────────────────────────────────────────────┐",
305         "│10.0│Y Axis                                    ┌─────────┐│",
306         "│    │  ••                                      │Dataset 1││",
307         "│    │    ••                                    │Dataset 2││",
308         "│    │      ••                                  └─────────┘│",
309         "│    │        ••                                ••         │",
310         "│    │          ••                            ••           │",
311         "│    │            ••                        ••             │",
312         "│    │              ••                    ••               │",
313         "│    │                ••                ••                 │",
314         "│    │                  ••            ••                   │",
315         "│    │                    ••        ••                     │",
316         "│    │                      •••   ••                       │",
317         "│    │                         •••                         │",
318         "│5.0 │                        •• ••                        │",
319         "│    │                      ••     ••                      │",
320         "│    │                   •••         ••                    │",
321         "│    │                 ••              ••                  │",
322         "│    │               ••                  ••                │",
323         "│    │             ••                      ••              │",
324         "│    │           ••                          ••            │",
325         "│    │         ••                              ••          │",
326         "│    │       ••                                  ••        │",
327         "│    │     ••                                      •••     │",
328         "│    │   ••                                           ••   │",
329         "│    │ ••                                               •• │",
330         "│0.0 │•                                              X Axis│",
331         "│    └─────────────────────────────────────────────────────│",
332         "│  0.0                      50.0                     100.0 │",
333         "└──────────────────────────────────────────────────────────┘",
334     ]);
335 
336     // Set expected backgound color
337     for row in 0..30 {
338         for col in 0..60 {
339             expected.get_mut(col, row).set_bg(Color::White);
340         }
341     }
342 
343     // Set expected colors of the first dataset
344     let line1 = vec![
345         (48, 5),
346         (49, 5),
347         (46, 6),
348         (47, 6),
349         (44, 7),
350         (45, 7),
351         (42, 8),
352         (43, 8),
353         (40, 9),
354         (41, 9),
355         (38, 10),
356         (39, 10),
357         (36, 11),
358         (37, 11),
359         (34, 12),
360         (35, 12),
361         (33, 13),
362         (30, 14),
363         (31, 14),
364         (28, 15),
365         (29, 15),
366         (25, 16),
367         (26, 16),
368         (27, 16),
369         (23, 17),
370         (24, 17),
371         (21, 18),
372         (22, 18),
373         (19, 19),
374         (20, 19),
375         (17, 20),
376         (18, 20),
377         (15, 21),
378         (16, 21),
379         (13, 22),
380         (14, 22),
381         (11, 23),
382         (12, 23),
383         (9, 24),
384         (10, 24),
385         (7, 25),
386         (8, 25),
387         (6, 26),
388     ];
389     let legend1 = vec![
390         (49, 2),
391         (50, 2),
392         (51, 2),
393         (52, 2),
394         (53, 2),
395         (54, 2),
396         (55, 2),
397         (56, 2),
398         (57, 2),
399     ];
400     for (col, row) in line1 {
401         expected.get_mut(col, row).set_fg(Color::Blue);
402     }
403     for (col, row) in legend1 {
404         expected.get_mut(col, row).set_fg(Color::Blue);
405     }
406 
407     // Set expected colors of the second dataset
408     let line2 = vec![
409         (8, 2),
410         (9, 2),
411         (10, 3),
412         (11, 3),
413         (12, 4),
414         (13, 4),
415         (14, 5),
416         (15, 5),
417         (16, 6),
418         (17, 6),
419         (18, 7),
420         (19, 7),
421         (20, 8),
422         (21, 8),
423         (22, 9),
424         (23, 9),
425         (24, 10),
426         (25, 10),
427         (26, 11),
428         (27, 11),
429         (28, 12),
430         (29, 12),
431         (30, 12),
432         (31, 13),
433         (32, 13),
434         (33, 14),
435         (34, 14),
436         (35, 15),
437         (36, 15),
438         (37, 16),
439         (38, 16),
440         (39, 17),
441         (40, 17),
442         (41, 18),
443         (42, 18),
444         (43, 19),
445         (44, 19),
446         (45, 20),
447         (46, 20),
448         (47, 21),
449         (48, 21),
450         (49, 22),
451         (50, 22),
452         (51, 23),
453         (52, 23),
454         (53, 23),
455         (54, 24),
456         (55, 24),
457         (56, 25),
458         (57, 25),
459     ];
460     let legend2 = vec![
461         (49, 3),
462         (50, 3),
463         (51, 3),
464         (52, 3),
465         (53, 3),
466         (54, 3),
467         (55, 3),
468         (56, 3),
469         (57, 3),
470     ];
471     for (col, row) in line2 {
472         expected.get_mut(col, row).set_fg(Color::Green);
473     }
474     for (col, row) in legend2 {
475         expected.get_mut(col, row).set_fg(Color::Green);
476     }
477 
478     // Set expected colors of the x axis
479     let x_axis_title = vec![(53, 26), (54, 26), (55, 26), (56, 26), (57, 26), (58, 26)];
480     for (col, row) in x_axis_title {
481         expected.get_mut(col, row).set_fg(Color::Yellow);
482     }
483 
484     terminal.backend().assert_buffer(&expected);
485 }
486