asciigraph/
graph.rs

1use crate::alignment::Alignment;
2use crate::color::{Color, ColorMode};
3use crate::format::NumberFormatter;
4use crate::interval::{Interval, draw_labeled_intervals};
5use crate::lines::Lines;
6use crate::skip_value::SkipValue;
7use hmath::Ratio;
8use std::collections::HashSet;
9use std::sync::Arc;
10
11mod merge;
12mod setters;
13
14pub use merge::*;
15
16#[derive(Clone)]
17pub struct Graph {
18    data: GraphData,
19
20    title: Option<String>,
21    big_title: bool,
22    title_color: Option<Color>,
23
24    plot_width: usize,
25    plot_height: usize,
26
27    block_width: Option<usize>,
28
29    x_label_margin: usize,
30    y_label_margin: usize,
31
32    x_axis_label: Option<String>,
33    y_axis_label: Option<String>,
34
35    labeled_intervals: Vec<Interval>,
36
37    y_min: Option<Ratio>,
38    y_max: Option<Ratio>,
39
40    pretty_y: Option<Ratio>,
41
42    y_label_formatter: Arc<dyn NumberFormatter>,
43
44    // see comments in setters
45    skip_value: SkipValue,
46    skip_skip_range: Option<(Option<Ratio>, Option<Ratio>)>,
47
48    // TODO: Vec<(usize, usize)>?
49    horizontal_break: Option<(usize, usize)>,
50
51    paddings: [usize; 4],
52
53    color_mode: ColorMode,
54    primary_color: Option<Color>,
55}
56
57#[derive(Debug, PartialEq, Clone)]
58enum GraphData {
59    Data1D (Vec<(String, Ratio)>),
60    Data2D {
61        data: Vec<(usize, usize, u16)>,
62        x_labels: Vec<Option<String>>,
63        y_labels: Vec<Option<String>>,
64    },
65    None,
66}
67
68impl GraphData {
69    pub fn unwrap_1d(&self) -> &Vec<(String, Ratio)> {
70        if let GraphData::Data1D(v) = self {
71            v
72        } else {
73            panic!("Unable to unwrap 1d data from {self:?}")
74        }
75    }
76
77    pub fn unwrap_2d(&self) -> ( &Vec<(usize, usize, u16)>, &Vec<Option<String>>, &Vec<Option<String>> ) {
78        if let GraphData::Data2D { data, x_labels, y_labels } = self {
79            (&data, &x_labels, &y_labels)
80        } else {
81            panic!("Unable to unwrap 2d data from {self:?}")
82        }
83    }
84
85    pub fn len(&self) -> usize {
86        match self {
87            GraphData::Data1D(data) => data.len(),
88            GraphData::Data2D { data, .. } => data.len(),
89            GraphData::None => 0,
90        }
91    }
92
93    pub fn is_empty(&self) -> bool {
94        match self {
95            GraphData::Data1D(data) => data.is_empty(),
96            GraphData::Data2D { data, .. } => data.is_empty(),
97            GraphData::None => true,
98        }
99    }
100}
101
102impl Graph {
103    pub fn new(plot_width: usize, plot_height: usize) -> Self {
104        Graph {
105            plot_width,
106            plot_height,
107            ..Default::default()
108        }
109    }
110
111    /// It panics if it's not well-configured. If you're not sure, call `.is_valid` before calling this method
112    pub fn draw(&self) -> String {
113        match &self.data {
114            GraphData::Data1D(_) => self.draw_1d_graph(),
115            GraphData::Data2D { .. } => self.draw_2d_graph(),
116            GraphData::None => panic!("Cannot draw a graph without any data"),
117        }
118    }
119
120    pub(crate) fn get_actual_plot_width(&self) -> usize {
121        match &self.data {
122            GraphData::Data1D(_) => match self.block_width {
123                Some(w) => w * self.data.len(),
124                _ => self.plot_width,
125            },
126            _ => self.plot_width,
127        }
128    }
129
130    /// 1. `self.data` must be set and for 1-D data, it must not be empty.
131    /// 2. If `self.y_min` and `self.y_max` are set, `self.y_max` has to be greater than `self.y_min`.
132    /// 3. If you're using a 2-dimensional data, `data`, `x_labels` and `y_labels` must have the same dimension.
133    /// 4. If there're labeled_intervals, their interval must be valid.
134    pub fn is_valid(&self) -> bool {
135        (match (&self.y_min, &self.y_max) {  // why do I need to wrap it with parenthesis?
136            (Some(n), Some(m)) if n.gt_rat(&m) => false,
137            _ => true
138        }) && match &self.data {
139            GraphData::Data1D(v) => v.len() > 0,
140            GraphData::Data2D { data, x_labels, y_labels } if x_labels.len() > 0 && y_labels.len() > 0 => {
141                let mut x_max = 0;
142                let mut y_max = 0;
143
144                for (x, y, _) in data.iter() {
145
146                    if *x > x_max {
147                        x_max = *x;
148                    }
149
150                    if *y > y_max {
151                        y_max = *y;
152                    }
153
154                }
155
156                x_labels.len() >= x_max && y_labels.len() >= y_max && x_labels.len() == self.plot_width && y_labels.len() == self.plot_height
157            },
158            _ => false,
159        } && {
160            self.labeled_intervals.iter().all(|i| i.is_valid())
161        } && {
162            // TODO
163            true
164        }
165    }
166
167    fn draw_1d_graph(&self) -> String {
168        let mut data = self.data.unwrap_1d().clone();
169
170        let plot_width = self.get_actual_plot_width();
171
172        if data.len() > plot_width * 2 {
173            data = pick_meaningful_values(&data, plot_width);
174        }
175
176        let (data_min, data_max, max_diff) = get_min_max_diff(&data, self.plot_height);
177        let (mut y_min, mut y_max) = unwrap_y_min_max(&self.y_min, &self.y_max, &data_min, &data_max);
178
179        let mut ratio_of_subgraphs = (3, 3);
180
181        let mut skip_range = match &self.skip_value {
182            _ if self.plot_height <= 18 => None,
183            SkipValue::None => None,
184            SkipValue::Automatic => {
185                if !max_diff.is_zero() && y_max.sub_rat(&y_min).div_rat(&max_diff).lt_i32(3) {
186                    let (y_min_, from, to, y_max_, ratio_of_subgraphs_) = get_where_to_skip(data.clone());
187                    ratio_of_subgraphs = ratio_of_subgraphs_;
188
189                    // respect self.y_min and self.y_max if they're explicitly set
190                    if self.y_min.is_none() {
191                        y_min = y_min_;
192                    }
193
194                    if self.y_max.is_none() {
195                        y_max = y_max_;
196                    }
197
198                    let respect_skip_skip_range = if let Some((skip_skip_from, skip_skip_to)) = &self.skip_skip_range {
199                        match (skip_skip_from, skip_skip_to) {
200                            (Some(skip_skip_from), Some(skip_skip_to)) => if skip_skip_from.lt_rat(&from) && skip_skip_to.gt_rat(&to) {
201                                None
202                            } else {
203                                Some((from, to))
204                            },
205                            (None, Some(skip_skip_to)) => if skip_skip_to.gt_rat(&to) {
206                                None
207                            } else {
208                                Some((from, to))
209                            },
210                            (Some(skip_skip_from), None) => if skip_skip_from.lt_rat(&from) {
211                                None
212                            } else {
213                                Some((from, to))
214                            },
215                            // skip_skip_range is (inf, inf), which includes all the numbers
216                            (None, None) => None,
217                        }
218                    } else {
219                        Some((from, to))
220                    };
221
222                    if let Some((from, to)) = respect_skip_skip_range {
223                        // if the explicitly set y_min and y_max are not compatible with the skipped range, it doesn't skip
224                        if y_min.lt_rat(&from) && to.lt_rat(&y_max) {
225                            Some((from, to))
226                        }
227
228                        else {
229                            None
230                        }
231                    }
232
233                    else {
234                        None
235                    }
236                }
237
238                else {
239                    None
240                }
241            },
242            SkipValue::Manual { from, to } => {
243                let mut values_below_skip_range = HashSet::new();
244                let mut values_above_skip_range = HashSet::new();
245
246                for (_, n) in data.iter() {
247
248                    if n.lt_rat(from) {
249                        values_below_skip_range.insert(n.clone());
250                    }
251
252                    else if n.gt_rat(to) {
253                        values_above_skip_range.insert(n.clone());
254                    }
255
256                }
257
258                if values_below_skip_range.len() * 2 > values_above_skip_range.len() * 3 {
259                    ratio_of_subgraphs = (4, 2);
260                }
261
262                else if values_above_skip_range.len() * 2 > values_below_skip_range.len() * 3 {
263                    ratio_of_subgraphs = (2, 4);
264                }
265
266                Some((from.clone(), to.clone()))
267            },
268        };
269
270        if let Some((from, to)) = &skip_range {
271            if from.lt_rat(&y_min) || to.gt_rat(&y_max) {
272                skip_range = None;
273            }
274        }
275
276        let y_labels_len;
277
278        let mut plot = match &skip_range {
279            None => {
280                let (y_min, y_max) = prettify_y_labels(
281                    &y_min,
282                    &y_max,
283                    self.plot_height,
284                    self.pretty_y.as_ref().map(|n| (self.y_min.is_none(), self.y_max.is_none(), n.clone()))
285                );
286
287                let mut plot = plot_1d(
288                    &data,
289                    plot_width,
290                    self.plot_height,
291                    &y_min,
292                    &y_max,
293                    false,  // no_overflow_char
294                    self.primary_color.clone(),
295                );
296                plot = plot.add_border([false, true, true, false]);
297
298                let y_labels = draw_y_labels_1d_plot(
299                    &y_min,
300                    &y_max,
301                    self.plot_height,
302                    self.y_label_margin,
303                    &self.y_label_formatter,
304                );
305                y_labels_len = y_labels.get_width();
306
307                y_labels.merge_horizontally(&plot, Alignment::First)
308            },
309            Some((from, to)) => {
310                let (mut height1, mut height2) = (
311                    self.plot_height * ratio_of_subgraphs.0 / 6,
312                    self.plot_height * ratio_of_subgraphs.1 / 6,
313                );
314
315                // it has to be height1 + height2 + 1 == self.plot_height
316                // 1 is for the delimiter line
317                if height1 > height2 {
318                    height1 += self.plot_height - height1 - height2;
319                    height2 -= 1;
320                }
321
322                else {
323                    height2 += self.plot_height - height1 - height2;
324                    height1 -= 1;
325                }
326
327                // if y_min, y_max, or skip_value is explicitly set by the user, it never touches them
328                // otherwise it tries to adjust them for prettier y_labels
329                let (plot1_y_min, plot1_y_max) = prettify_y_labels(
330                    &y_min,
331                    &from,
332                    height1,
333                    self.pretty_y.as_ref().map(|n| (self.y_min.is_none(), self.skip_value.is_automatic(), n.clone()))
334                );
335
336                let mut plot1 = plot_1d(
337                    &data,
338                    plot_width,
339                    height1,
340                    &plot1_y_min,
341                    &plot1_y_max,
342                    true,  // no_overflow_char
343                    self.primary_color.clone(),
344                );
345                plot1 = plot1.add_border([false, true, true, false]);
346
347                let (plot2_y_min, plot2_y_max) = prettify_y_labels(
348                    &to,
349                    &y_max,
350                    height2,
351                    self.pretty_y.as_ref().map(|n| (self.skip_value.is_automatic(), self.y_max.is_none(), n.clone()))
352                );
353
354                let mut plot2 = plot_1d(
355                    &data,
356                    plot_width,
357                    height2,
358                    &plot2_y_min,
359                    &plot2_y_max,
360                    false,  // no_overflow_char
361                    self.primary_color.clone(),
362                );
363                plot2 = plot2.add_border([false, false, true, false]);
364
365                let mut y_labels1 = draw_y_labels_1d_plot(
366                    &plot1_y_min,
367                    &plot1_y_max,
368                    height1,
369                    self.y_label_margin,
370                    &self.y_label_formatter,
371                );
372                let mut y_labels2 = draw_y_labels_1d_plot(
373                    &plot2_y_min,
374                    &plot2_y_max,
375                    height2,
376                    self.y_label_margin,
377                    &self.y_label_formatter,
378                );
379
380                if y_labels1.get_width() < y_labels2.get_width() {
381                    y_labels1 = y_labels1.add_padding([0, 0, y_labels2.get_width() - y_labels1.get_width(), 0]);
382                }
383
384                else if y_labels2.get_width() < y_labels1.get_width() {
385                    y_labels2 = y_labels2.add_padding([0, 0, y_labels1.get_width() - y_labels2.get_width(), 0]);
386                }
387
388                y_labels_len = y_labels1.get_width();
389                plot1 = y_labels1.merge_horizontally(&plot1, Alignment::First);
390                plot2 = y_labels2.merge_horizontally(&plot2, Alignment::First);
391
392                let mut horizontal_line = Lines::from_string(&"~".repeat(plot1.get_width()), Alignment::First, &ColorMode::None);
393                horizontal_line.set_color_all(self.primary_color.clone());
394
395                plot1 = horizontal_line.merge_vertically(&plot1, Alignment::First);
396
397                plot2.merge_vertically(&plot1, Alignment::First)
398            },
399        };
400
401        let x_labels = draw_x_labels(&data, plot_width, self.x_label_margin);
402        plot = plot.merge_vertically(&x_labels, Alignment::Last);
403
404        if !self.labeled_intervals.is_empty() {
405            let arrows = draw_labeled_intervals(&self.labeled_intervals, plot_width);
406            plot = plot.merge_vertically(&arrows, Alignment::Last);
407        }
408
409        if let Some((from, to)) = self.horizontal_break {
410            let plot_width = plot.get_width();
411            let plot_height = plot.get_height();
412            let left_half = plot.crop(0, 0, y_labels_len + from, plot_height);
413            let right_half = plot.crop(y_labels_len + to, 0, plot_width - y_labels_len - to, plot_height);
414            let break_line = draw_vertial_line(plot_height, self.primary_color.clone());
415
416            plot = left_half.merge_horizontally(&break_line, Alignment::Last);
417            plot = plot.merge_horizontally(&right_half, Alignment::Last);
418        }
419
420        if let Some(xal) = &self.x_axis_label {
421            let mut xal = Lines::from_string(xal, Alignment::First, &ColorMode::None);
422            xal = xal.add_padding([self.plot_height, 0, 0, 0]);
423            plot = plot.merge_horizontally(&xal, Alignment::First);
424        }
425
426        if let Some(yal) = &self.y_axis_label {
427            let yal = Lines::from_string(yal, Alignment::First, &ColorMode::None);
428            plot = yal.merge_vertically(&plot, Alignment::First);
429        }
430
431        if let Some(t) = &self.title {
432            let title = draw_title(t, self.big_title, self.title_color.clone());
433            plot = title.merge_vertically(&plot, Alignment::Center);
434        }
435
436        plot = plot.add_padding(self.paddings);
437
438        plot.to_string(&self.color_mode)
439    }
440
441    fn draw_2d_graph(&self) -> String {
442        let (
443            data, x_labels, y_labels
444        ) = self.data.unwrap_2d();
445
446        let mut plot = plot_2d(&data, self.plot_width, self.plot_height);
447        plot = plot.add_border([false, true, true, false]);
448
449        let x_labels = draw_x_labels(
450            &x_labels.iter().map(
451                |s| (
452                    match s { Some(s) => s.to_string(), _ => String::new() },
453                    ()
454                )
455            ).collect(),
456            self.plot_width,
457            self.x_label_margin
458        );
459        plot = plot.merge_vertically(&x_labels, Alignment::Last);
460
461        let y_labels = draw_y_labels_2d_plot(y_labels);
462        plot = y_labels.merge_horizontally(&plot, Alignment::First);
463
464        if let Some(xal) = &self.x_axis_label {
465            let mut xal = Lines::from_string(xal, Alignment::First, &ColorMode::None);
466            xal = xal.add_padding([self.plot_height, 0, 0, 0]);
467            plot = plot.merge_horizontally(&xal, Alignment::First);
468        }
469
470        if let Some(yal) = &self.y_axis_label {
471            let yal = Lines::from_string(yal, Alignment::First, &ColorMode::None);
472            plot = yal.merge_vertically(&plot, Alignment::First);
473        }
474
475        if let Some(t) = &self.title {
476            let title = draw_title(t, self.big_title, self.title_color.clone());
477            plot = title.merge_vertically(&plot, Alignment::Center);
478        }
479
480        plot = plot.add_padding(self.paddings);
481
482        plot.to_string(&self.color_mode)
483    }
484
485    fn adjust_all_labeled_intervals(&mut self) {
486        let plot_width = self.get_actual_plot_width();
487        let data_len = self.data.len();
488
489        if !self.data.is_empty() {
490            self.labeled_intervals.iter_mut().for_each(
491                |i| i.adjust_coordinate(plot_width, data_len)
492            );
493        }
494    }
495}
496
497fn pick_meaningful_values(data: &Vec<(String, Ratio)>, width: usize) -> Vec<(String, Ratio)> {
498    // a graph with odd-sized width is not supported because of this line
499    let half_width = width / 2;
500
501    let mut last_ind = 0;
502    let mut result = Vec::with_capacity(width);
503
504    for i in 0..half_width {
505        let curr_ind = (i + 1) * data.len() / half_width;
506        let mut min_ind = 0;
507        let mut min_val = &data[last_ind].1;
508        let mut max_ind = 0;
509        let mut max_val = &data[last_ind].1;
510
511        for (ind, (_, val)) in data[last_ind..curr_ind].iter().enumerate() {
512            if val.gt_rat(&max_val) {
513                max_ind = ind;
514                max_val = val;
515            }
516
517            else if val.lt_rat(&min_val) {
518                min_ind = ind;
519                min_val = val;
520            }
521        }
522
523        if min_ind < max_ind {
524            result.push(data[last_ind + min_ind].clone());
525            result.push(data[last_ind + max_ind].clone());
526        }
527
528        else {
529            result.push(data[last_ind + max_ind].clone());
530            result.push(data[last_ind + min_ind].clone());
531        }
532
533        last_ind = curr_ind;
534    }
535
536    result
537}
538
539fn get_where_to_skip(mut data: Vec<(String, Ratio)>) -> (Ratio, Ratio, Ratio, Ratio, (usize, usize)) {
540    let mut curr_max_diff = Ratio::zero();
541    let mut curr_max_diff_ind = 0;
542    data.sort_unstable_by_key(|(_, n)| n.clone());
543
544    for i in 0..(data.len() - 1) {
545        let curr_diff = data[i + 1].1.sub_rat(&data[i].1);
546
547        if curr_diff.gt_rat(&curr_max_diff) {
548            curr_max_diff = curr_diff;
549            curr_max_diff_ind = i;
550        }
551
552    }
553
554    let mut padding1 = data[curr_max_diff_ind].1.sub_rat(&data[0].1).div_i32(16);
555    let mut padding2 = data[data.len() - 1].1.sub_rat(&data[curr_max_diff_ind + 1].1).div_i32(16);
556
557    if padding1.is_zero() {
558        padding1 = data[curr_max_diff_ind + 1].1.sub_rat(&data[curr_max_diff_ind].1).div_i32(16);
559    }
560
561    if padding2.is_zero() {
562        padding2 = data[curr_max_diff_ind + 1].1.sub_rat(&data[curr_max_diff_ind].1).div_i32(16);
563    }
564
565    let mut ratio_of_subgraphs = (3, 3);
566    let values_below_skip_range = data[0..(curr_max_diff_ind + 1)].iter().map(|(_, n)| n).collect::<HashSet<&Ratio>>();
567    let values_above_skip_range = data[(curr_max_diff_ind + 1)..].iter().map(|(_, n)| n).collect::<HashSet<&Ratio>>();
568
569    if values_below_skip_range.len() * 2 > values_above_skip_range.len() * 3 {
570        ratio_of_subgraphs = (4, 2);
571    }
572
573    else if values_above_skip_range.len() * 2 > values_below_skip_range.len() * 3 {
574        ratio_of_subgraphs = (2, 4);
575    }
576
577    (
578        data[0].1.sub_rat(&padding1),
579        data[curr_max_diff_ind].1.add_rat(&padding1),
580        data[curr_max_diff_ind + 1].1.sub_rat(&padding2),
581        data[data.len() - 1].1.add_rat(&padding2),
582        ratio_of_subgraphs,
583    )
584}
585
586fn unwrap_y_min_max(self_y_min: &Option<Ratio>, self_y_max: &Option<Ratio>, data_min: &Ratio, data_max: &Ratio) -> (Ratio, Ratio) {
587    match (&self_y_min, &self_y_max) {
588        (Some(n), Some(m)) => (n.clone(), m.clone()),
589        (Some(n), None) => if n.lt_rat(&data_max) {
590            (n.clone(), data_max.clone())
591        } else {
592            (n.clone(), n.add_i32(1))
593        },
594        (None, Some(n)) => if n.gt_rat(&data_min) {
595            (data_min.clone(), n.clone())
596        } else {
597            (n.sub_i32(1), n.clone())
598        },
599        (None, None) => (data_min.clone(), data_max.clone()),
600    }
601}
602
603fn get_min_max_diff(v: &Vec<(String, Ratio)>, height: usize) -> (Ratio, Ratio, Ratio) {  // (y_min, y_max, max_diff)
604    if v.len() == 0 {
605        return (Ratio::zero(), Ratio::one(), Ratio::zero());
606    }
607
608    let mut data = v.iter().map(|(_, n)| n.clone()).collect::<Vec<Ratio>>();
609    data.sort_unstable();
610
611    let curr_min = &data[0];
612    let curr_max = &data[data.len() - 1];
613    let mut max_diff = Ratio::zero();
614
615    for i in 0..(data.len() - 1) {
616        let diff = data[i + 1].sub_rat(&data[i]);
617
618        if diff.gt_rat(&max_diff) {
619            max_diff = diff;
620        }
621    }
622
623    let mut diff = curr_max.sub_rat(curr_min).div_i32(16);
624
625    if diff.is_zero() {
626        diff = Ratio::from_i32(height as i32).div_i32(4);
627    }
628
629    let min = curr_min.sub_rat(&diff);
630    let max = curr_max.add_rat(&diff);
631
632    (min, max, max_diff)
633}
634
635fn draw_title(title: &str, big_title: bool, title_color: Option<Color>) -> Lines {
636    let mut result = if big_title {
637        Lines::from_string(&asciibox::render_string(title, asciibox::RenderOption::default()), Alignment::First, &ColorMode::None)
638    }
639
640    else {
641        Lines::from_string(title, Alignment::Center, &ColorMode::None)
642    };
643
644    result.set_color_all(title_color);
645
646    result
647}
648
649// no axis
650fn draw_y_labels_2d_plot(y_labels: &Vec<Option<String>>) -> Lines {
651    Lines::from_string(
652        &y_labels.iter().map(
653            |s| match s {
654                Some(s) => s.replace("\n", " "),
655                _ => String::new(),
656            }
657        ).collect::<Vec<String>>().join("\n"),
658        Alignment::Last,
659        &ColorMode::None,
660    )
661}
662
663// no axis
664fn draw_y_labels_1d_plot(
665    y_min: &Ratio,
666    y_max: &Ratio,
667    height: usize,
668    margin: usize,
669    formatter: &Arc<dyn NumberFormatter>,
670) -> Lines {
671    let mut labels = Vec::with_capacity(height);
672    let y_diff = y_max.sub_rat(y_min);
673    let y_label_step = y_diff.div_i32(height as i32);
674    let mut curr_max_width = 0;
675
676    for y in 0..height {
677        if margin > 1 && y % margin != 0 {
678            labels.push(String::new());
679            continue;
680        }
681
682        let curr_y = y_max.sub_rat(&y_label_step.mul_i32(y as i32));
683        let curr_label = formatter.f(&curr_y);
684
685        if curr_label.len() > curr_max_width {
686            curr_max_width = curr_label.len();
687        }
688
689        labels.push(curr_label);
690    }
691
692    Lines::from_string(&labels.join("\n"), Alignment::Last, &ColorMode::None)
693}
694
695// no axis
696fn draw_x_labels<T>(data: &Vec<(String, T)>, width: usize, margin: usize) -> Lines {
697    let mut result = Lines::new(width, 2);
698
699    let mut first_line_filled = 0;
700    let mut second_line_filled = 0;
701    let mut on_first_line = false;
702    let mut last_ind = usize::MAX;
703
704    for x in 0..width {
705        let data_ind = x * data.len() / width;
706
707        if on_first_line && x < first_line_filled || !on_first_line && x < second_line_filled || data_ind == last_ind {
708            continue;
709        }
710
711        let curr_label = &data[data_ind].0;
712        let y_ind = on_first_line as usize;
713
714        if curr_label.len() + x >= width {
715            on_first_line = !on_first_line;
716            continue;
717        }
718
719        for (lab_ind, c) in curr_label.chars().enumerate() {
720            let c = if c == '\n' { ' ' as u16 } else { c as u16 };
721
722            result.set(x + lab_ind, y_ind, c);
723        }
724
725        last_ind = data_ind;
726
727        if on_first_line {
728            first_line_filled = x + curr_label.len() + margin;
729        }
730
731        else {
732            second_line_filled = x + curr_label.len() + margin;
733        }
734
735        on_first_line = !on_first_line;
736    }
737
738    result
739}
740
741// no axis, no labels, only plots
742fn plot_2d(data: &Vec<(usize, usize, u16)>, width: usize, height: usize) -> Lines {
743    let mut result = Lines::new(width, height);
744
745    for (x, y, c) in data.iter() {
746        if *c == '\n' as u16 {
747            result.set(*x, *y, 32);
748        }
749
750        else {
751            result.set(*x, *y, *c);
752        }
753    }
754
755    result
756}
757
758// no axis, no labels, only plots
759fn plot_1d(data: &Vec<(String, Ratio)>, width: usize, height: usize, y_min: &Ratio, y_max: &Ratio, no_overflow_char: bool, overflow_char_color: Option<Color>) -> Lines {
760    let mut result = Lines::new(width, height);
761    let y_diff = y_max.sub_rat(&y_min);
762
763    for x in 0..width {
764        let data_ind = x * data.len() / width;
765        let data_val = &data[data_ind].1;
766        let mut overflow = false;
767
768        // truncate(((y_max - data_val) / y_diff * height * 2).max(0))
769        let mut y_start = match y_max.sub_rat(data_val).div_rat(&y_diff).mul_i32(height as i32).mul_i32(4).truncate_bi().to_i32() {
770            Ok(n) if n < 0 => {
771                overflow = true;
772                0
773            },
774            Ok(n) => n as usize,
775            Err(_) => usize::MAX,
776        };
777
778        let block_type = y_start % 4;
779        y_start /= 4;
780
781        if y_start + 1 > height {
782            continue;
783        }
784
785        for y in y_start..height {
786            result.set(x, y, '█' as u16);
787        }
788
789        if overflow && !no_overflow_char {
790            result.set(x, 0, '^' as u16);
791            result.set_color(x, 0, overflow_char_color.clone());
792        }
793
794        else {
795            result.set(x, y_start, [
796                '█' as u16,
797                '▆' as u16,
798                '▄' as u16,
799                '▂' as u16,
800            ][block_type])
801        }
802
803    }
804
805    result
806}
807
808// if y_min and y_max are (0, 499.8), the output would be very ugly
809// it adjusts numbers in such cases
810//
811// it works when both y_min and y_max are movable
812// -> in order to make all the labels pretty, both end(start) point and interval have to be modified
813fn prettify_y_labels(old_y_min: &Ratio, old_y_max: &Ratio, height: usize, pretty_y_label_info: Option<(bool, bool, Ratio)>) -> (Ratio, Ratio) {
814    if let Some((y_min_movable, y_max_movable, interval)) = pretty_y_label_info {
815        if !y_max_movable || (!y_min_movable && !old_y_min.div_rat(&interval).is_integer()) {
816            (old_y_min.clone(), old_y_max.clone())
817        }
818
819        else {
820            let curr_interval = old_y_max.sub_rat(old_y_min).div_i32(height as i32);
821
822            // curr_interval / interval =
823            // 15/16 ~ 17/16   -> 1, 30/16 ~ 34/16   -> 2, 45/16 ~ 51/16  -> 3,
824            // 60/16 ~ 68/16   -> 4, 75/16 ~ 85/16   -> 5, 90/16 ~ 102/16 -> 6,
825            // 105/16 ~ 119/16 -> 7, 120/16 ~ 136/16 -> 8 ... okay from here
826            let should_be_multiple_of_16 = curr_interval.div_rat(&interval).mul_i32(16).round_bi();
827
828            if let Ok(n) = should_be_multiple_of_16.to_i32() {
829                if n < 15 || (17 < n && n < 30)
830                    || (34 < n && n < 45)
831                    || (51 < n && n < 60)
832                    || (68 < n && n < 75)
833                    || (85 < n && n < 90)
834                    || (102 < n && n < 105)
835                {
836                    return (old_y_min.clone(), old_y_max.clone());
837                }
838
839            }
840
841            // round y_min to the closest multiple of `interval`
842            // round (y_max - y_min) to the closest multiple of `interval` then add it to new `y_min`
843            let new_y_min = old_y_min.div_rat(&interval).round().mul_rat(&interval);
844            let y_diff = old_y_max.sub_rat(&new_y_min);
845            let new_y_diff = y_diff.div_rat(&interval).div_i32(height as i32).round().mul_rat(&interval).mul_i32(height as i32);
846            let new_y_max = new_y_min.add_rat(&new_y_diff);
847
848            (new_y_min, new_y_max)
849        }
850
851    }
852
853    else {
854        (old_y_min.clone(), old_y_max.clone())
855    }
856}
857
858fn draw_vertial_line(height: usize, color: Option<Color>) -> Lines {
859    let mut result = Lines::new(2, height);
860    result.set_color_all(color);
861
862    for i in 0..height {
863        if i & 1 == 0 {
864            result.set(0, i, ')' as u16);
865            result.set(1, i, ')' as u16);
866        }
867
868        else {
869            result.set(0, i, '(' as u16);
870            result.set(1, i, '(' as u16);
871        }
872    }
873
874    result
875}
876
877use std::fmt;
878
879impl fmt::Display for Graph {
880    fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
881        write!(fmt, "{}", self.draw())
882    }
883}