termplot/
plot.rs

1//! Different types of plots and graphs that can be plotted or graphed onto the view.
2//!
3//! If a given type of plot is not present, creat it with [`DrawView`].
4use crate::{DrawView, View, ViewCanvas};
5use std::ops;
6
7/// A continuous function to be graphed on the figure.
8///
9/// Use this struct to plot continuous functions on the graph.
10///
11/// # Examples
12///
13/// ```rust
14/// use termplot::*;
15///
16/// let graph = plot::Graph::new(|x| x.sin() / x);
17///
18/// let mut plot = Plot::default();
19/// plot.set_domain(Domain(-10.0..10.0))
20///     .set_codomain(Domain(-0.3..1.2))
21///     .set_title("Graph title")
22///     .set_x_label("X axis")
23///     .set_y_label("Y axis")
24///     .set_size(Size::new(50, 25))
25///     .add_plot(Box::new(graph));
26///
27/// println!("{plot}");
28/// ```
29pub struct Graph<F>
30where
31    F: Fn(f64) -> f64,
32{
33    function: F,
34}
35
36impl<F> Graph<F>
37where
38    F: Fn(f64) -> f64,
39{
40    /// Create a new continuous function to be added to the plot.
41    pub fn new(function: F) -> Self {
42        Self { function }
43    }
44}
45
46impl<F> DrawView for Graph<F>
47where
48    F: Fn(f64) -> f64,
49{
50    fn draw(&self, view: &View, canvas: &mut ViewCanvas) {
51        view.domain
52            .iter(view.size.w)
53            .filter_map(|x| {
54                let y = (self.function)(x);
55                match y.is_finite() {
56                    true => Some((x, y)),
57                    false => None,
58                }
59            })
60            .collect::<Vec<_>>()
61            .windows(2)
62            .into_iter()
63            .for_each(|line| {
64                canvas.line(line[0].0, line[0].1, line[1].0, line[1].1);
65            });
66    }
67}
68
69/// A bar in a bar graph or a histogram.
70///
71/// See [`Bars`] or [`Histogram`] for more informations.
72pub(crate) struct Bar {
73    x: f64,
74    height: f64,
75    width: f64,
76}
77
78impl Bar {
79    pub fn new(x: f64, width: f64, height: f64) -> Self {
80        Self { x, height, width }
81    }
82}
83
84impl DrawView for Bar {
85    fn draw(&self, _: &View, canvas: &mut ViewCanvas) {
86        canvas.line(self.x, 0.0, self.x, self.height);
87        canvas.line(self.x + self.width, 0.0, self.x + self.width, self.height);
88        canvas.line(self.x, self.height, self.x + self.width, self.height);
89    }
90}
91
92/// A bars graph.
93///
94/// All bars are 1 unit wide.
95///
96/// # Examples
97///
98/// ```rust
99/// use termplot::*;
100///
101/// let mut plot = Plot::default();
102///
103/// plot.set_domain(Domain(0.0..6.0))
104///     .set_codomain(Domain(0.0..10.0))
105///     .set_title("Graph title")
106///     .set_x_label("X axis")
107///     .set_y_label("Y axis")
108///     .set_size(Size::new(50, 25))
109///     .add_plot(Box::new(plot::Bars::new(
110///         vec![2.0, 5.0, 1.0, 8.0, 9.0, 1.0],
111///     )));
112/// ```
113pub struct Bars {
114    bars: Vec<Bar>,
115}
116
117impl Bars {
118    /// Create a new bars graph.
119    ///
120    /// Each value inside `bars_height` represent a bar of the graph. Each value is the height of
121    /// the corresponding bar.
122    pub fn new(bars_height: Vec<f64>) -> Self {
123        let bars = bars_height
124            .into_iter()
125            .enumerate()
126            .map(|(x, height)| Bar::new(x as f64, 1.0, height))
127            .collect::<Vec<_>>();
128        Self { bars }
129    }
130}
131
132impl DrawView for Bars {
133    fn draw(&self, view: &View, canvas: &mut ViewCanvas) {
134        self.bars.iter().for_each(|bar| bar.draw(view, canvas));
135    }
136}
137
138/// An [histogram](https://en.wikipedia.org/wiki/Histogram) graph. An approximation of the
139/// distribution of data.
140///
141/// # Examples
142///
143/// ```rust
144/// use termplot::*;
145/// use rand::Rng;
146///
147/// let mut rng = rand::thread_rng();
148/// let values: Vec<f64> = (0..100).map(|_| rng.gen_range(0.0f64..10.0f64)).collect();
149///
150/// let mut plot = Plot::default();
151///
152/// plot.set_domain(Domain(0.0..11.0))
153///     .set_codomain(Domain(0.0..45.0))
154///     .set_title("Graph title")
155///     .set_x_label("X axis")
156///     .set_y_label("Y axis")
157///     .set_size(Size::new(50, 25))
158///     .add_plot(Box::new(plot::Histogram::new(
159///         values,
160///         vec![0.0..2.0, 2.0..4.0, 4.0..6.0, 6.0..8.0, 8.0..10.0],
161///     )));
162///
163/// println!("{plot}");
164/// ```
165pub struct Histogram {
166    buckets: Vec<Bar>,
167}
168
169impl Histogram {
170    /// Create an histogram from data and buckets in which the data will be sorted.
171    ///
172    /// For each given value, the value will increment the count of the bucket in which it resides
173    /// inside.
174    pub fn new(values: Vec<f64>, buckets_range: Vec<ops::Range<f64>>) -> Self {
175        let buckets = buckets_range
176            .into_iter()
177            .map(|range| Bar {
178                x: range.start,
179                width: range.end - range.start,
180                height: values.iter().filter(|v| range.contains(v)).count() as f64,
181            })
182            .collect::<Vec<_>>();
183        Self { buckets }
184    }
185
186    /// Create an histogram from data and a number of buckets.
187    ///
188    /// All buckets will have the same width, depending on the range of the min and max value and
189    /// the number of buckets.
190    ///
191    /// For each given value, the value will increment the count of the bucket in which it resides
192    /// inside.
193    pub fn new_with_buckets_count(values: Vec<f64>, count: u32) -> Self {
194        let max = values.iter().copied().fold(f64::NEG_INFINITY, f64::max);
195        let min = values.iter().copied().fold(f64::INFINITY, f64::min);
196        let width = (max - min) / count as f64;
197        let buckets = (0..count)
198            .into_iter()
199            .map(|idx| (min + width * idx as f64)..(min + width * (idx as f64 + 1.0)))
200            .collect::<Vec<ops::Range<f64>>>();
201        Self::new(values, buckets)
202    }
203}
204
205impl DrawView for Histogram {
206    fn draw(&self, view: &View, canvas: &mut ViewCanvas) {
207        self.buckets
208            .iter()
209            .for_each(|bucket| bucket.draw(view, canvas));
210    }
211}