1use std::vec::Vec;
2
3#[cfg(feature = "color")]
4use colored::Color;
5#[cfg(feature = "color")]
6use colored::ColoredString;
7#[cfg(feature = "color")]
8use colored::Colorize;
9
10pub struct Config {
11 width: u32,
12 height: u32,
13 offset: u32,
14 caption: String,
15 #[cfg(feature = "color")]
16 caption_color: Color,
17 #[cfg(feature = "color")]
18 axis_color: Color,
19 #[cfg(feature = "color")]
20 label_color: Color,
21 #[cfg(feature = "color")]
22 series_colors: Vec<Color>,
23 #[cfg(feature = "color")]
24 series_legends: Vec<String>,
25}
26
27impl Default for Config {
28 fn default() -> Self {
29 Config {
30 width: 0,
31 height: 0,
32 offset: 0,
33 caption: String::new(),
34 #[cfg(feature = "color")]
35 caption_color: Color::White,
36 #[cfg(feature = "color")]
37 axis_color: Color::White,
38 #[cfg(feature = "color")]
39 label_color: Color::White,
40 #[cfg(feature = "color")]
41 series_colors: vec![],
42 #[cfg(feature = "color")]
43 series_legends: Vec::new(),
44 }
45 }
46}
47
48impl Config {
49 pub fn with_caption(mut self, caption: String) -> Self {
50 self.caption = caption;
51 self
52 }
53
54 pub fn with_height(mut self, height: u32) -> Self {
55 self.height = height;
56 self
57 }
58
59 pub fn with_width(mut self, width: u32) -> Self {
60 self.width = width;
61 self
62 }
63
64 pub fn with_offset(mut self, offset: u32) -> Self {
65 self.offset = offset;
66 self
67 }
68
69 #[cfg(feature = "color")]
70 pub fn with_caption_color(mut self, color: Color) -> Self {
71 self.caption_color = color;
72 self
73 }
74
75 #[cfg(feature = "color")]
76 pub fn with_axis_color(mut self, color: Color) -> Self {
77 self.axis_color = color;
78 self
79 }
80
81 #[cfg(feature = "color")]
82 pub fn with_label_color(mut self, color: Color) -> Self {
83 self.label_color = color;
84 self
85 }
86
87 #[cfg(feature = "color")]
88 pub fn with_series_colors(mut self, colors: Vec<Color>) -> Self {
89 self.series_colors = colors;
90 self
91 }
92
93 #[cfg(feature = "color")]
94 pub fn with_series_legends(mut self, legends: Vec<String>) -> Self {
95 self.series_legends = legends;
96 self
97 }
98}
99
100pub fn plot(series: Vec<f64>, config: Config) -> String {
101 plot_many(vec![series], config)
102}
103
104pub fn plot_many(mut series: Vec<Vec<f64>>, mut config: Config) -> String {
105 let mut len_max = series.iter().map(|s| s.len()).max().unwrap_or(0);
106 if config.width > 0 {
107 series.iter_mut().for_each(|s| {
108 if s.len() < len_max {
109 s.extend(vec![f64::NAN].repeat(len_max - s.len()))
110 }
111 *s = interpolate(s, config.width);
112 });
113 len_max = config.width as usize;
114 }
115
116 let mut min = f64::MAX;
117 let mut max = f64::MIN;
118
119 (min, max) = series.iter().map(|s| min_max(s)).fold(
120 (min, max),
121 |(current_min, current_max), (next_min, next_max)| {
122 (
123 f64::min(next_min, current_min),
124 f64::max(next_max, current_max),
125 )
126 },
127 );
128
129 let interval = (max - min).abs();
130 if config.height == 0 {
131 if interval == 0f64 {
132 config.height = 3;
133 } else if interval <= 1f64 {
134 config.height =
135 (interval * f64::from(10i32.pow((-interval.log10()).ceil() as u32))) as u32;
136 } else {
137 config.height = interval as u32;
138 }
139 }
140
141 if config.offset == 0 {
142 config.offset = 3;
143 }
144
145 let ratio = if interval != 0f64 {
146 f64::from(config.height) / interval
147 } else {
148 1f64
149 };
150
151 let min2 = (min * ratio).round();
152 let max2 = (max * ratio).round();
153
154 let int_min2 = min2 as i32;
155 let int_max2 = max2 as i32;
156
157 let rows = f64::from(int_max2 - int_min2).abs() as i32;
158 let width = len_max as u32 + config.offset;
159
160 let mut plot: Vec<Vec<String>> = Vec::new();
161
162 for _i in 0..=rows {
163 let mut line = Vec::<String>::new();
164 for _j in 0..width {
165 line.push(" ".to_string());
166 }
167 plot.push(line);
168 }
169
170 let mut precision = 2;
171 let log_maximum = if min == 0f64 && max == 0f64 {
172 -1f64
173 } else {
174 f64::max(max.abs(), min.abs()).log10()
175 };
176
177 if log_maximum < 0f64 {
178 if log_maximum % 1f64 != 0f64 {
179 precision += log_maximum.abs() as i32;
180 } else {
181 precision += (log_maximum.abs() - 1f64) as i32;
182 }
183 } else if log_maximum > 2f64 {
184 precision = 0;
185 }
186
187 let max_number_label_length = format!("{:.*}", precision as usize, max).len();
188 let min_number_label_length = format!("{:.*}", precision as usize, min).len();
189
190 let max_label_width = usize::max(max_number_label_length, min_number_label_length);
191
192 for y in int_min2..=int_max2 {
193 let magnitude = if rows > 0 {
194 max - f64::from(y - int_min2) * interval / f64::from(rows)
195 } else {
196 f64::from(y)
197 };
198 let label = format!(
199 "{number:LW$.PREC$}",
200 LW = max_label_width + 1,
201 PREC = precision as usize,
202 number = magnitude
203 );
204 let w = (y - int_min2) as usize;
205 let h = f64::max(f64::from(config.offset) - label.len() as f64, 0f64) as usize;
206 plot[w][h] = label;
207 plot[w][(config.offset - 1) as usize] = "┤".to_string();
208 }
209
210 for series_inner in series {
211 let mut y0;
212 let mut y1;
213 if !series_inner[0].is_nan() {
214 y0 = ((series_inner[0] * ratio).round() - min2) as i32;
215 plot[(rows - y0) as usize][(config.offset - 1) as usize] = "┼".to_string();
216 }
217
218 for x in 0..series_inner.len() - 1 {
219 if series_inner[x].is_nan() && series_inner[x + 1].is_nan() {
220 continue;
221 }
222 if series_inner[x + 1].is_nan() && !series_inner[x].is_nan() {
223 y0 = ((series_inner[x] * ratio).round() - f64::from(int_min2)) as i32;
224 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] = "─".to_string();
225 continue;
226 }
227 if series_inner[x].is_nan() && !series_inner[x + 1].is_nan() {
228 y1 = ((series_inner[x + 1] * ratio).round() - f64::from(int_min2)) as i32;
229 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] = "─".to_string();
230 continue;
231 }
232 y0 = ((series_inner[x] * ratio).round() - f64::from(int_min2)) as i32;
233 y1 = ((series_inner[x + 1] * ratio).round() - f64::from(int_min2)) as i32;
234
235 if y0 == y1 {
236 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] = "─".to_string();
237 } else {
238 if y0 > y1 {
239 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] =
240 "╰".to_string();
241 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
242 "╮".to_string();
243 } else {
244 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] =
245 "╭".to_string();
246 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
247 "╯".to_string();
248 }
249
250 let start = f64::min(f64::from(y0), f64::from(y1)) as i32 + 1;
251 let end = f64::max(f64::from(y0), f64::from(y1)) as i32;
252
253 for y in start..end {
254 plot[(rows - y) as usize][(x as u32 + config.offset) as usize] =
255 "│".to_string();
256 }
257 }
258 }
259 }
260
261 let mut res: String = plot
262 .into_iter()
263 .map(|line| line.join(""))
264 .collect::<Vec<String>>()
265 .join("\n");
266 res.pop();
267 if !config.caption.is_empty() {
268 res.push('\n');
269 res.push_str(
270 std::iter::repeat(" ")
271 .take(config.offset as usize + max_label_width as usize)
272 .collect::<String>()
273 .as_ref(),
274 );
275 if config.caption.len() < len_max {
276 res.push_str(
277 std::iter::repeat(" ")
278 .take((len_max - config.caption.len()) / 2)
279 .collect::<String>()
280 .as_ref(),
281 );
282 }
283 res.push_str(config.caption.as_ref());
284 }
285 res
286}
287
288#[cfg(feature = "color")]
289fn create_legend_item(text: &str, color: colored::Color) -> (String, usize) {
290 use colored::Colorize;
291
292 let colored_box = "■".color(color).to_string(); let default_color = "".normal().to_string(); let legend_item = format!("{}{} {}", colored_box, default_color, text);
295
296 let legend_length = text.chars().count() + 2; (legend_item, legend_length)
300}
301
302#[cfg(feature = "color")]
303fn add_legends(lines: &mut String, config: &Config, len_max: usize, left_pad: usize) {
304 use std::iter;
305
306 lines.push_str("\n\n");
307 lines.push_str(&iter::repeat(" ").take(left_pad).collect::<String>());
308
309 let mut legends_text = String::new();
310 let mut legends_text_len = 0;
311 let right_pad = 3;
312
313 for (i, text) in config.series_legends.iter().enumerate() {
314 let (item, item_len) = create_legend_item(text, config.series_colors[i]);
315 legends_text.push_str(&item);
316 legends_text_len += item_len;
317
318 if i < config.series_legends.len() - 1 {
319 legends_text.push_str(&iter::repeat(" ").take(right_pad).collect::<String>());
320 legends_text_len += right_pad;
321 }
322 }
323
324 if legends_text_len < len_max {
325 lines.push_str(
326 &iter::repeat(" ")
327 .take((len_max - legends_text_len) / 2)
328 .collect::<String>(),
329 );
330 }
331
332 lines.push_str(&legends_text);
333}
334
335#[cfg(feature = "color")]
336pub fn plot_colored(series: Vec<f64>, config: Config) -> ColoredString {
337 plot_many_colored(vec![series], config)
338}
339
340#[cfg(feature = "color")]
341pub fn plot_many_colored(mut series: Vec<Vec<f64>>, mut config: Config) -> ColoredString {
342 let mut len_max = series.iter().map(|s| s.len()).max().unwrap_or(0);
343 if config.width > 0 {
344 series.iter_mut().for_each(|s| {
345 if s.len() < len_max {
346 s.extend(vec![f64::NAN].repeat(len_max - s.len()))
347 }
348 *s = interpolate(s, config.width);
349 });
350 len_max = config.width as usize;
351 }
352
353 let mut min = f64::MAX;
354 let mut max = f64::MIN;
355
356 (min, max) = series.iter().map(|s| min_max(s)).fold(
357 (min, max),
358 |(current_min, current_max), (next_min, next_max)| {
359 (
360 f64::min(next_min, current_min),
361 f64::max(next_max, current_max),
362 )
363 },
364 );
365
366 let interval = (max - min).abs();
367 if config.height == 0 {
368 if interval == 0f64 {
369 config.height = 3;
370 } else if interval <= 1f64 {
371 config.height =
372 (interval * f64::from(10i32.pow((-interval.log10()).ceil() as u32))) as u32;
373 } else {
374 config.height = interval as u32;
375 }
376 }
377
378 if config.offset == 0 {
379 config.offset = 3;
380 }
381
382 let ratio = if interval != 0f64 {
383 f64::from(config.height) / interval
384 } else {
385 1f64
386 };
387
388 let min2 = (min * ratio).round();
389 let max2 = (max * ratio).round();
390
391 let int_min2 = min2 as i32;
392 let int_max2 = max2 as i32;
393
394 let rows = f64::from(int_max2 - int_min2).abs() as i32;
395 let width = len_max as u32 + config.offset;
396
397 let mut plot: Vec<Vec<ColoredString>> = Vec::new();
398
399 for _i in 0..=rows {
400 let mut line = Vec::<ColoredString>::new();
401 for _j in 0..width {
402 line.push(" ".to_string().into());
403 }
404 plot.push(line);
405 }
406
407 let mut precision = 2;
408 let log_maximum = if min == 0f64 && max == 0f64 {
409 -1f64
410 } else {
411 f64::max(max.abs(), min.abs()).log10()
412 };
413
414 if log_maximum < 0f64 {
415 if log_maximum % 1f64 != 0f64 {
416 precision += log_maximum.abs() as i32;
417 } else {
418 precision += (log_maximum.abs() - 1f64) as i32;
419 }
420 } else if log_maximum > 2f64 {
421 precision = 0;
422 }
423
424 let max_number_label_length = format!("{:.*}", precision as usize, max).len();
425 let min_number_label_length = format!("{:.*}", precision as usize, min).len();
426
427 let max_label_width = usize::max(max_number_label_length, min_number_label_length);
428
429 for y in int_min2..=int_max2 {
430 let magnitude = if rows > 0 {
431 max - f64::from(y - int_min2) * interval / f64::from(rows)
432 } else {
433 f64::from(y)
434 };
435 let label = format!(
436 "{number:LW$.PREC$}",
437 LW = max_label_width + 1,
438 PREC = precision as usize,
439 number = magnitude
440 );
441 let w = (y - int_min2) as usize;
442 let h = f64::max(f64::from(config.offset) - label.len() as f64, 0f64) as usize;
443 plot[w][h] = label.color(config.axis_color);
444 plot[w][(config.offset - 1) as usize] = "┤".to_string().color(config.axis_color);
445 }
446
447 for (i, series_inner) in series.iter().enumerate() {
448 let mut y0;
449 let mut y1;
450 if !series_inner[0].is_nan() {
451 y0 = ((series_inner[0] * ratio).round() - min2) as i32;
452 plot[(rows - y0) as usize][(config.offset - 1) as usize] =
453 "┼".to_string().color(config.axis_color);
454 }
455
456 for x in 0..series_inner.len() - 1 {
457 if series_inner[x].is_nan() && series_inner[x + 1].is_nan() {
458 continue;
459 }
460 if series_inner[x + 1].is_nan() && !series_inner[x].is_nan() {
461 y0 = ((series_inner[x] * ratio).round() - f64::from(int_min2)) as i32;
462 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
463 "─".to_string().color(config.series_colors[i]);
464 continue;
465 }
466 if series_inner[x].is_nan() && !series_inner[x + 1].is_nan() {
467 y1 = ((series_inner[x + 1] * ratio).round() - f64::from(int_min2)) as i32;
468 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] =
469 "─".to_string().color(config.series_colors[i]);
470 continue;
471 }
472 y0 = ((series_inner[x] * ratio).round() - f64::from(int_min2)) as i32;
473 y1 = ((series_inner[x + 1] * ratio).round() - f64::from(int_min2)) as i32;
474
475 if y0 == y1 {
476 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
477 "─".to_string().color(config.series_colors[i]);
478 } else {
479 if y0 > y1 {
480 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] =
481 "╰".to_string().color(config.series_colors[i]);
482 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
483 "╮".to_string().color(config.series_colors[i]);
484 } else {
485 plot[(rows - y1) as usize][(x as u32 + config.offset) as usize] =
486 "╭".to_string().color(config.series_colors[i]);
487 plot[(rows - y0) as usize][(x as u32 + config.offset) as usize] =
488 "╯".to_string().color(config.series_colors[i]);
489 }
490
491 let start = f64::min(f64::from(y0), f64::from(y1)) as i32 + 1;
492 let end = f64::max(f64::from(y0), f64::from(y1)) as i32;
493
494 for y in start..end {
495 plot[(rows - y) as usize][(x as u32 + config.offset) as usize] =
496 "│".to_string().color(config.series_colors[i]);
497 }
498 }
499 }
500 }
501
502 let mut res: String = plot
503 .into_iter()
504 .map(|line| line.into_iter().map(|s| s.to_string()).collect::<String>())
505 .collect::<Vec<String>>()
506 .join("\n");
507
508 let mut caption = String::new();
511
512 if !config.caption.is_empty() {
513 caption.push('\n');
514 caption.push_str(
515 std::iter::repeat(" ")
516 .take(config.offset as usize + max_label_width as usize)
517 .collect::<String>()
518 .as_ref(),
519 );
520 if config.caption.len() < len_max {
521 caption.push_str(
522 std::iter::repeat(" ")
523 .take((len_max - config.caption.len()) / 2)
524 .collect::<String>()
525 .as_ref(),
526 );
527 }
528 caption.push_str(
529 config
530 .caption
531 .color(config.caption_color)
532 .to_string()
533 .as_ref(),
534 );
535 }
536 res.push_str(caption.as_ref());
537
538 if config.series_legends.len() > 0 {
539 add_legends(
540 &mut res,
541 &config,
542 len_max,
543 config.offset as usize + max_label_width,
544 )
545 }
546 res.into()
547}
548
549fn interpolate(series: &[f64], count: u32) -> Vec<f64> {
550 let mut result = Vec::new();
551 let spring_factor = (series.len() - 1) as f64 / f64::from(count - 1);
552 result.push(series[0]);
553 for i in 1..count - 1 {
554 let spring = f64::from(i) * spring_factor;
555 let before = spring.floor();
556 let after = spring.ceil();
557 let at_point = spring - before;
558 result.push(linear_interpolate(
559 series[before as usize],
560 series[after as usize],
561 at_point,
562 ))
563 }
564 result.push(series[series.len() - 1]);
565 result
566}
567
568fn linear_interpolate(before: f64, after: f64, at_point: f64) -> f64 {
569 before + (after - before) * at_point
570}
571
572fn min_max(series: &[f64]) -> (f64, f64) {
573 let min = series
574 .iter()
575 .fold(std::f64::MAX, |accu, &x| if x < accu { x } else { accu });
576 let max = series
577 .iter()
578 .fold(std::f64::MIN, |accu, &x| if x > accu { x } else { accu });
579 (min, max)
580}
581
582#[cfg(test)]
583#[rustfmt::skip]
584mod tests {
585
586 macro_rules! graph_eq {
587 ($fname:ident ? [$($series:expr),*] => $rhs:expr) => {
588 #[test]
589 fn $fname(){
590 let res = crate::plot(vec![$(f64::from($series),)*], crate::Config::default());
591 assert_eq!(res, $rhs);
592 }};
593 ($fname:ident ? [$($series:expr),*] ? $config:expr => $rhs:expr) => {
594 #[test]
595 fn $fname(){
596 let res = crate::plot(vec![$(f64::from($series),)*], $config);
597 assert_eq!(res, $rhs);
598 }};
599 }
600
601 graph_eq!(test_ones ? [1, 1, 1, 1, 1] => " 1.00 ┼────");
602 graph_eq!(test_zeros ? [0, 0, 0, 0, 0] => " 0.00 ┼────");
603 graph_eq!(test_three ? [2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1] => " 11.00 ┤ ╭╮
604 10.00 ┤ ││
605 9.00 ┤ ││
606 8.00 ┤ ││
607 7.00 ┤ ╭╯│╭╮
608 6.00 ┤ │ │││
609 5.00 ┤ ╭╯ │││
610 4.00 ┤ │ │││
611 3.00 ┤ │ ╰╯│
612 2.00 ┼╮ ╭╮│ │
613 1.00 ┤╰─╯││ ╰
614 0.00 ┤ ││
615 -1.00 ┤ ││
616 -2.00 ┤ ╰╯ ");
617
618 graph_eq!(test_four ? [2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 4, 5, 6, 9, 4, 0, 6, 1, 5, 3, 6, 2] ?
619 crate::Config::default().with_caption("Plot using asciigraph.".to_string())
620 => " 11.00 ┤ ╭╮
621 10.00 ┤ ││
622 9.00 ┤ ││ ╭╮
623 8.00 ┤ ││ ││
624 7.00 ┤ ╭╯│╭╮ ││
625 6.00 ┤ │ │││ ╭╯│ ╭╮ ╭╮
626 5.00 ┤ ╭╯ │││╭╯ │ ││╭╮││
627 4.00 ┤ │ ││╰╯ ╰╮││││││
628 3.00 ┤ │ ╰╯ ││││╰╯│
629 2.00 ┼╮ ╭╮│ ││││ ╰
630 1.00 ┤╰─╯││ ││╰╯
631 0.00 ┤ ││ ╰╯
632 -1.00 ┤ ││
633 -2.00 ┤ ╰╯
634 Plot using asciigraph.");
635
636 graph_eq!(test_five ? [ 2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 4, 5, 6, 9, 4, 0, 6, 1, 5, 3, 6, 2] ?
637 crate::Config::default().with_caption("Plot using asciigraph.".to_string())
638 => " 11.00 ┤ ╭╮
639 10.00 ┤ ││
640 9.00 ┤ ││ ╭╮
641 8.00 ┤ ││ ││
642 7.00 ┤ ╭╯│╭╮ ││
643 6.00 ┤ │ │││ ╭╯│ ╭╮ ╭╮
644 5.00 ┤ ╭╯ │││╭╯ │ ││╭╮││
645 4.00 ┤ │ ││╰╯ ╰╮││││││
646 3.00 ┤ │ ╰╯ ││││╰╯│
647 2.00 ┼╮ ╭╮│ ││││ ╰
648 1.00 ┤╰─╯││ ││╰╯
649 0.00 ┤ ││ ╰╯
650 -1.00 ┤ ││
651 -2.00 ┤ ╰╯
652 Plot using asciigraph." );
653
654 graph_eq!(test_six ? [0.2, 0.1, 0.2, 2, -0.9, 0.7, 0.91, 0.3, 0.7, 0.4, 0.5] ?
655 crate::Config::default().with_caption("Plot using asciigraph.".to_string())
656 => " 2.00 ┤ ╭╮ ╭╮
657 0.55 ┼──╯│╭╯╰───
658 -0.90 ┤ ╰╯
659 Plot using asciigraph." );
660
661 graph_eq!(test_seven ? [2, 1, 1, 2, -2, 5, 7, 11, 3, 7, 1] ?
662 crate::Config::default().with_height(4).with_offset(3)
663 => " 11.00 ┤ ╭╮
664 7.75 ┤ ╭─╯│╭╮
665 4.50 ┼╮ ╭╮│ ╰╯│
666 1.25 ┤╰─╯││ ╰
667 -2.00 ┤ ╰╯ "
668 );
669
670 graph_eq!(test_eight ? [0.453, 0.141, 0.951, 0.251, 0.223, 0.581, 0.771, 0.191, 0.393, 0.617, 0.478]
671 => " 0.95 ┤ ╭╮
672 0.85 ┤ ││ ╭╮
673 0.75 ┤ ││ ││
674 0.65 ┤ ││ ╭╯│ ╭╮
675 0.55 ┤ ││ │ │ │╰
676 0.44 ┼╮││ │ │╭╯
677 0.34 ┤│││ │ ││
678 0.24 ┤││╰─╯ ╰╯
679 0.14 ┤╰╯ ");
680
681 graph_eq!(test_nine ? [0.01, 0.004, 0.003, 0.0042, 0.0083, 0.0033, 0.0079]
682 => " 0.010 ┼╮
683 0.009 ┤│
684 0.008 ┤│ ╭╮╭
685 0.007 ┤│ │││
686 0.006 ┤│ │││
687 0.005 ┤│ │││
688 0.004 ┤╰╮╭╯││
689 0.003 ┤ ╰╯ ╰╯"
690 );
691
692 graph_eq!(test_ten ? [192, 431, 112, 449, -122, 375, 782, 123, 911, 1711, 172] ? crate::Config::default().with_height(10)
693 => " 1711 ┤ ╭╮
694 1528 ┤ ││
695 1344 ┤ ││
696 1161 ┤ ││
697 978 ┤ ╭╯│
698 794 ┤ ╭╮│ │
699 611 ┤ │││ │
700 428 ┤╭╮╭╮╭╯││ │
701 245 ┼╯╰╯││ ╰╯ ╰
702 61 ┤ ││
703 -122 ┤ ╰╯ ");
704
705 graph_eq!(test_eleven ? [0.3189989805, 0.149949026, 0.30142492354, 0.195129182935, 0.3142492354,
706 0.1674974513, 0.3142492354, 0.1474974513, 0.3047974513] ?
707 crate::Config::default().with_width(30).with_height(5).with_caption("Plot with custom height & width.".to_string())
708 => " 0.32 ┼╮ ╭─╮ ╭╮ ╭
709 0.29 ┤╰╮ ╭─╮ ╭╯ │ ╭╯│ │
710 0.26 ┤ │ ╭╯ ╰╮ ╭╯ ╰╮ ╭╯ ╰╮ ╭╯
711 0.23 ┤ ╰╮ ╭╯ ╰╮│ ╰╮╭╯ ╰╮ ╭╯
712 0.20 ┤ ╰╮│ ╰╯ ╰╯ │╭╯
713 0.16 ┤ ╰╯ ╰╯
714 Plot with custom height & width."
715 );
716
717 graph_eq!(test_twelve ? [0, 0, 0, 0, 1.5, 0, 0, -0.5, 9, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0,
718 0, 0, 0, 0, 1.5, 0, 0, -0.5, 8, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0,
719 0, 0, 0, 0, 1.5, 0, 0, -0.5, 10, -3, 0, 0, 1, 2, 1, 0, 0, 0, 0] ?
720 crate::Config::default().with_offset(10).with_height(10).with_caption("I'm a doctor, not an engineer.".to_string())
721 => " 10.00 ┤ ╭╮
722 8.70 ┤ ╭╮ ││
723 7.40 ┤ ││ ╭╮ ││
724 6.10 ┤ ││ ││ ││
725 4.80 ┤ ││ ││ ││
726 3.50 ┤ ││ ││ ││
727 2.20 ┤ ││ ╭╮ ││ ╭╮ ││ ╭╮
728 0.90 ┤ ╭╮ ││ ╭╯╰╮ ╭╮ ││ ╭╯╰╮ ╭╮ ││ ╭╯╰╮
729 -0.40 ┼───╯╰──╯│╭─╯ ╰───────╯╰──╯│╭─╯ ╰───────╯╰──╯│╭─╯ ╰───
730 -1.70 ┤ ││ ││ ││
731 -3.00 ┤ ╰╯ ╰╯ ╰╯
732 I'm a doctor, not an engineer.");
733
734 graph_eq!(test_thirteen ? [-5, -2, -3, -4, 0, -5, -6, -7, -8, 0, -9, -3, -5, -2, -9, -3, -1]
735 => " 0.00 ┤ ╭╮ ╭╮
736 -1.00 ┤ ││ ││ ╭
737 -2.00 ┤╭╮ ││ ││ ╭╮ │
738 -3.00 ┤│╰╮││ ││╭╮││╭╯
739 -4.00 ┤│ ╰╯│ │││││││
740 -5.00 ┼╯ ╰╮ │││╰╯││
741 -6.00 ┤ ╰╮ │││ ││
742 -7.00 ┤ ╰╮│││ ││
743 -8.00 ┤ ╰╯││ ││
744 -9.00 ┤ ╰╯ ╰╯ ");
745
746 graph_eq!(test_fourteen ? [-0.000018527, -0.021, -0.00123, 0.00000021312,
747 -0.0434321234, -0.032413241234, 0.0000234234] ?
748 crate::Config::default().with_height(5).with_width(45)
749 => " 0.000 ┼─╮ ╭────────╮ ╭
750 -0.008 ┤ ╰──╮ ╭──╯ ╰─╮ ╭─╯
751 -0.017 ┤ ╰─────╯ ╰╮ ╭─╯
752 -0.025 ┤ ╰─╮ ╭─╯
753 -0.034 ┤ ╰╮ ╭────╯
754 -0.042 ┤ ╰───╯ "
755 );
756
757 graph_eq!(test_fifteen ? [57.76, 54.04, 56.31, 57.02, 59.5, 52.63, 52.97, 56.44, 56.75, 52.96, 55.54,
758 55.09, 58.22, 56.85, 60.61, 59.62, 59.73, 59.93, 56.3, 54.69, 55.32, 54.03, 50.98, 50.48, 54.55, 47.49,
759 55.3, 46.74, 46, 45.8, 49.6, 48.83, 47.64, 46.61, 54.72, 42.77, 50.3, 42.79, 41.84, 44.19, 43.36, 45.62,
760 45.09, 44.95, 50.36, 47.21, 47.77, 52.04, 47.46, 44.19, 47.22, 45.55, 40.65, 39.64, 37.26, 40.71, 42.15,
761 36.45, 39.14, 36.62]
762 => " 60.61 ┤ ╭╮ ╭╮
763 59.60 ┤ ╭╮ │╰─╯│
764 58.60 ┤ ││ ╭╮│ │
765 57.59 ┼╮ ╭╯│ │││ │
766 56.58 ┤│╭╯ │ ╭─╮ │╰╯ ╰╮
767 55.58 ┤││ │ │ │╭─╯ │╭╮ ╭╮
768 54.57 ┤╰╯ │ │ ││ ╰╯╰╮ ╭╮││ ╭╮
769 53.56 ┤ │╭╯ ╰╯ │ ││││ ││
770 52.56 ┤ ╰╯ │ ││││ ││ ╭╮
771 51.55 ┤ ╰╮││││ ││ ││
772 50.54 ┤ ╰╯│││ ││╭╮ ╭╮ ││
773 49.54 ┤ │││ ╭─╮ ││││ ││ ││
774 48.53 ┤ │││ │ │ ││││ ││ ││
775 47.52 ┤ ╰╯│ │ ╰╮││││ │╰─╯╰╮╭╮
776 46.52 ┤ ╰─╮│ ╰╯│││ │ │││
777 45.51 ┤ ╰╯ │││ ╭──╯ ││╰╮
778 44.50 ┤ │││ ╭╮│ ╰╯ │
779 43.50 ┤ ││╰╮│╰╯ │
780 42.49 ┤ ╰╯ ╰╯ │ ╭╮
781 41.48 ┤ │ ││
782 40.48 ┤ ╰╮ ╭╯│
783 39.47 ┤ ╰╮│ │╭╮
784 38.46 ┤ ││ │││
785 37.46 ┤ ╰╯ │││
786 36.45 ┤ ╰╯╰"
787 );
788
789 #[test]
790 fn test_min_max() {
791 assert_eq!(
792 (-2f64, 11f64),
793 crate::min_max(&[2f64, 1f64, 1f64, 2f64, -2f64, 5f64, 7f64, 11f64, 3f64, 7f64, 1f64])
794 );
795 }
796
797 #[test]
798 fn test_plot_many(){
799 let res = super::plot_many(vec![vec![0f64,1.0,2.0,3.0,3.0,3.0,2.0,0.0], vec![5.0,4.0,2.0,1.0, 4.0, 6.0,6.0]], super::Config::default());
800 let exp = " 6.00 ┤ ╭─
801 5.00 ┼╮ │
802 4.00 ┤╰╮ ╭╯
803 3.00 ┤ │╭│─╮
804 2.00 ┤ ╰╮│ ╰╮
805 1.00 ┤╭╯╰╯ │
806 0.00 ┼╯ ╰";
807 assert_eq!(res, exp);
808 }
809
810 #[test]
811 fn test_plot_many_two(){
812 let res = super::plot_many(vec![vec![0.0f64, 1.0, 0.0], vec![2.0, 3.0, 4.0, 3.0, 2.0], vec![4.0, 5.0, 6.0, 7.0, 6.0, 5.0,4.0]], super::Config::default().with_width(21));
813 let exp = " 7.00 ┤ ╭──╮
814 6.00 ┤ ╭───╯ ╰───╮
815 5.00 ┤ ╭──╯ ╰──╮
816 4.00 ┼─╯ ╭───╮ ╰─
817 3.00 ┤ ╭──╯ ╰──╮
818 2.00 ┼─╯ ╰──
819 1.00 ┤ ╭───╮
820 0.00 ┼─╯ ╰─ ";
821 assert_eq!(res, exp);
822 }
824
825 #[test]
826 fn test_plot_many_three(){
827 let res = super::plot_many(vec![vec![0.0f64, 0.0, 2.0, 2.0, f64::NAN], vec![1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0], vec![f64::NAN, f64::NAN, f64::NAN, 0.0, 0.0, 2.0, 2.0]], super::Config::default());
828 let exp = " 2.00 ┤ ╭──╭─
829 1.00 ┼────│─
830 0.00 ┼─╯──╯ ";
831 assert_eq!(res, exp);
832 }
833
834 #[test]
835 fn test_plot_many_four(){
836
837 let res = super::plot_many(vec![vec![0.1f64, 0.2, 0.3, f64::NAN, 0.5, 0.6, 0.7, f64::NAN, f64::NAN, 0.9, 1.0]], super::Config::default());
838 let exp = " 1.00 ┤ ╭
839 0.90 ┤ ─╯
840 0.80 ┤
841 0.70 ┤ ╭─
842 0.60 ┤ ╭╯
843 0.50 ┤ ─╯
844 0.40 ┤
845 0.30 ┤ ╭─
846 0.20 ┤╭╯
847 0.10 ┼╯ ";
848 assert_eq!(res, exp);
849 }
850
851}