rasciigraph/
lib.rs

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(); // Colored box
293    let default_color = "".normal().to_string(); // Reset to default color
294    let legend_item = format!("{}{} {}", colored_box, default_color, text);
295
296    // Calculate the length of the legend item (accounting for the box and space)
297    let legend_length = text.chars().count() + 2; // 2 for the box and space
298
299    (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    //res.pop();
509
510    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        // assert_eq!(res.as_bytes(), exp.as_bytes());
823    }
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}