tgraph/
multi_graph.rs

1use console_engine::pixel;
2use console_engine::screen::Screen;
3use console_engine::Color;
4use std::fmt;
5use std::iter::successors;
6
7use crate::function::Function;
8use crate::graph::{GraphOptions, GraphWidths};
9use crate::traits::AsF64;
10
11pub struct MultiGraph<X: AsF64, Y: AsF64, F: Fn(X) -> Y> {
12  functions: Vec<Function<X, Y, F>>,
13  widths: GraphWidths,
14  height: u32,
15  graph_height: u32,
16  options: MultiGraphOptions,
17}
18
19pub struct MultiGraphOptions(Vec<GraphOptions>);
20
21impl Default for MultiGraphOptions {
22  fn default() -> MultiGraphOptions {
23    let colors = [
24      Color::Red,
25      Color::Blue,
26      Color::Green,
27      Color::Magenta,
28      Color::Cyan,
29      Color::Yellow,
30      Color::DarkRed,
31      Color::DarkBlue,
32      Color::DarkGreen,
33      Color::DarkMagenta,
34      Color::DarkCyan,
35      Color::DarkYellow,
36    ];
37    MultiGraphOptions(
38      (0..12)
39        .map(|i| GraphOptions {
40          color: colors[i].into(),
41          ..GraphOptions::default()
42        })
43        .collect(),
44    )
45  }
46}
47
48impl<X: AsF64, Y: AsF64, F: Fn(X) -> Y> MultiGraph<X, Y, F> {
49  pub fn new(
50    f: Vec<Function<X, Y, F>>,
51    width: u32,
52    set_height: Option<u32>,
53  ) -> MultiGraph<X, Y, F> {
54    MultiGraph::with_options(f, width, set_height, MultiGraphOptions::default())
55  }
56
57  pub fn new_screen(f: Vec<Function<X, Y, F>>) -> MultiGraph<X, Y, F> {
58    MultiGraph::with_options_screen(f, MultiGraphOptions::default())
59  }
60
61  pub fn with_options(
62    fs: Vec<Function<X, Y, F>>,
63    width: u32,
64    set_height: Option<u32>,
65    options: MultiGraphOptions,
66  ) -> MultiGraph<X, Y, F> {
67    // Get max y
68    let max = fs
69      .iter()
70      .map(|f| {
71        f.rng(0, width)
72          .into_iter()
73          .reduce(f64::max)
74          .unwrap_or_default()
75      })
76      .reduce(f64::max)
77      .unwrap_or_default()
78      .round() as u32;
79    // Get digits of maximum number
80    let max_height_digits = successors(Some(max), |&n| (n >= 10).then(|| n / 10)).count() as u32;
81
82    let height = match set_height {
83      Some(h) => h,
84      None => max + 1,
85    };
86    MultiGraph {
87      functions: fs,
88      widths: GraphWidths {
89        total: width,
90        graph: width - max_height_digits,
91        height_legend: max_height_digits,
92      },
93      height,
94      graph_height: height - 1,
95      options,
96    }
97  }
98
99  pub fn with_options_screen(
100    f: Vec<Function<X, Y, F>>,
101    options: MultiGraphOptions,
102  ) -> MultiGraph<X, Y, F> {
103    let w_screen = console_engine::crossterm::terminal::size().unwrap().0;
104    MultiGraph::with_options(f, w_screen as u32, None, options)
105  }
106
107  pub fn draw(&self) {
108    let mut scr = Screen::new(self.widths.total, self.height);
109
110    self.draw_axis(&mut scr);
111    if self.options.0.get(0).unwrap().height_legend {
112      self.draw_height_legend(&mut scr);
113    }
114    self.draw_functions(&mut scr);
115
116    scr.draw();
117  }
118
119  fn draw_axis(&self, scr: &mut Screen) {
120    // Draw axis
121    scr.h_line(
122      (self.widths.height_legend + 1) as i32,
123      self.graph_height as i32,
124      self.widths.graph as i32,
125      pixel::pxl('_'),
126    );
127    scr.v_line(
128      self.widths.height_legend as i32,
129      0,
130      self.height as i32,
131      pixel::pxl('|'),
132    );
133  }
134
135  fn draw_height_legend(&self, scr: &mut Screen) {
136    for h in 0..=self.graph_height {
137      for (index, digit) in h.to_string().chars().enumerate() {
138        scr.set_pxl(
139          index as i32,
140          (self.graph_height - h) as i32,
141          pixel::pxl(digit),
142        );
143      }
144    }
145  }
146
147  fn draw_functions(&self, scr: &mut Screen) {
148    for (i, f) in self.functions.iter().enumerate() {
149      // Draw points
150      for (x, y) in f
151        .rng_x(0, self.widths.graph)
152        .into_iter()
153        .map(|(x, y)| (x, y.round() as u32))
154      {
155        scr.set_pxl(
156          (x + self.widths.height_legend) as i32,
157          (self.graph_height - y) as i32, // TODO Allow selecting approximation method: round, ceil or cast (as)
158          // Can also put a space (or empty box or something) and color bg
159          pixel::pxl_fg(
160            self.options.0.get(i).unwrap().character.as_char(),
161            self.options.0.get(i).unwrap().color.into(),
162          ),
163        )
164      }
165    }
166  }
167}
168
169impl<X: AsF64, Y: AsF64, F: Fn(X) -> Y> fmt::Display for MultiGraph<X, Y, F> {
170  fn fmt(&self, _f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
171    self.draw();
172    Ok(())
173  }
174}