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}