1use num::{FromPrimitive, ToPrimitive};
11
12use crate::helper::{
13 axes::add_opt_axes_and_opt_titles,
14 charset::{line_chars::*, NULL_CHR},
15 func_plot_domain::determine_plot_domain,
16 mat_plot_lib::pyplot,
17 math::{max_always, min_always, pad_range, subdivide},
18 file::save_to_file,
19 rendering::RenderableTextBuilder,
20};
21
22#[derive(Clone)]
41pub struct FuncPlotBuilder<'a> {
42 func: Box<&'a dyn Fn(f64) -> f64>,
43 domain: Option<(f64, f64)>,
44 range: Option<(f64, f64)>,
45 domain_padding: Option<f64>,
46 range_padding: Option<f64>,
47 size: Option<(u32, u32)>,
48 title: Option<&'a str>,
49 axes: Option<bool>,
50 precomputed: Option<Vec<(f64, f64)>>,
51}
52
53struct FuncPlot<'a> {
55 func: Box<&'a dyn Fn(f64) -> f64>,
56 domain_and_range: ((f64, f64), (f64, f64)),
57 size: (u32, u32),
58 title: Option<&'a str>,
59 axes: bool,
60 precomputed: &'a Option<Vec<(f64, f64)>>
61}
62
63impl<'a> FuncPlotBuilder<'a> {
64 fn from<'b: 'a>(func: &'b impl Fn(f64) -> f64) -> Self {
66 FuncPlotBuilder {
67 func: Box::new(func),
68 domain: None,
69 range: None,
70 domain_padding: None,
71 range_padding: None,
72 size: None,
73 title: None,
74 axes: None,
75 precomputed: None,
76 }
77 }
78
79 pub fn set_domain(&mut self, domain: (f64, f64)) -> &mut Self {
80 self.domain = Some(domain);
81 self
82 }
83
84 pub fn set_range(&mut self, range: (f64, f64)) -> &mut Self {
85 self.range = Some(range);
86 self
87 }
88
89 pub fn set_domain_padding(&mut self, padding: f64) -> &mut Self {
90 self.domain_padding = Some(padding);
91 self
92 }
93
94 pub fn set_range_padding(&mut self, padding: f64) -> &mut Self {
95 self.range_padding = Some(padding);
96 self
97 }
98
99 pub fn set_size(&mut self, size: (u32, u32)) -> &mut Self {
100 self.size = Some(size);
101 self
102 }
103
104 pub fn set_title<'b : 'a>(&mut self, title: &'b str) -> &mut Self {
105 self.title = Some(title);
106 self
107 }
108
109 pub fn set_axes(&mut self, do_axes: bool) -> &mut Self {
110 self.axes = Some(do_axes);
111 self
112 }
113
114 pub fn enable_precomputation(&mut self) -> &mut Self {
115 self.precomputed = Some(vec![]);
116 self
117 }
118
119 pub fn precompute(&mut self, resolution: u32) {
124 if self.precomputed.is_some() {
125 assert!(self.domain.is_some());
126
127 self.precomputed = Some(
128 subdivide(self.domain.unwrap().0, self.domain.unwrap().1, resolution)
129 .into_iter()
130 .map(|x| (x, (self.func)(x)))
131 .collect::<Vec<(f64, f64)>>()
132 );
133 }
134 }
135
136 fn determine_range(&self, resolution: u32, domain: (f64, f64)) -> (f64, f64) {
137 let y_vals: Vec<f64>;
138
139 match &self.precomputed {
140 Some(vals) => {
141 y_vals = vals
142 .iter()
143 .map(|p| p.1)
144 .collect();
145 }
146 None => {
147 y_vals = subdivide(domain.0, domain.1, resolution)
148 .into_iter()
149 .map(|i| (self.func)(i))
150 .collect();
151 }
152 }
153
154 (min_always(&y_vals,0.), max_always(&y_vals,0.))
155 }
156
157 fn build(&self) -> FuncPlot {
159 let size = self.size.unwrap_or((60, 10));
160 let resolution = size.0;
161
162 let domain = self.domain.unwrap_or_else(|| determine_plot_domain(&*self.func));
163 let range = self.range.unwrap_or_else(|| self.determine_range(resolution, domain));
164
165 let domain = pad_range(domain, self.domain_padding.unwrap_or(0.1));
167 let range = pad_range(range, self.range_padding.unwrap_or(0.1));
168
169 FuncPlot {
170 func: self.func.clone(),
171 domain_and_range: (domain, range),
172 size: size,
173 title: self.title,
174 axes: self.axes.unwrap_or(true),
175 precomputed: &self.precomputed,
176 }
177 }
178
179 pub fn as_string(&self) -> String {
181 self.build().as_string()
182 }
183
184 pub fn print(&self) {
186 self.build().print();
187 }
188
189 pub fn save(&self, path: &str) {
191 save_to_file(&self.build().as_string(), path);
192 }
193
194 pub fn as_image(&self) -> RenderableTextBuilder {
196 RenderableTextBuilder::from(self.build().as_string())
197 }
198
199 pub fn pyplot(&self) {
201 self.build().pyplot(None);
202 }
203
204 pub fn save_pyplot(&self, path: &str) {
206 self.build().pyplot(Some(path));
207 }
208
209 #[allow(dead_code)]
211 pub(crate) fn plot(&self) -> String {
212 self.build().plot()
213 }
214}
215
216impl<'a> FuncPlot<'a> {
217 fn plot(&self) -> String {
218 use rayon::prelude::*;
219
220 let cpux = self.size.0 as f64 / (self.domain_and_range.0.1 - self.domain_and_range.0.0);
222 let cpuy = self.size.1 as f64 / (self.domain_and_range.1.1 - self.domain_and_range.1.0);
223 let ctux = |c: i32| self.domain_and_range.0.0 + (c as f64 + 0.5) / cpux;
224 let utcy = |u: f64| ((self.domain_and_range.1.1 - u) * cpuy - 0.5) as i32;
225
226 let xc_vals: Vec<i32> = (-1..(1 + self.size.0 as i32)).collect();
228 let xu_vals: Vec<f64> = xc_vals.iter().map(|xc| ctux(*xc)).collect();
229 let yu_vals: Vec<f64> = xu_vals.iter().map(|xu| (self.func)(*xu)).collect();
230 let yc_vals: Vec<i32> = yu_vals.iter().map(|yu| utcy(*yu)).collect();
231
232 let mut o = (0..self.size.1).map(|_| (0..self.size.0).map(|_| ' ').collect::<Vec<char>>()).collect::<Vec<Vec<char>>>();
233
234 let mut set_o_char = |x: i32, y: i32, c: char| if 0 <= x && x < self.size.0 as i32 && 0 <= y && y < self.size.1 as i32 {o[y as usize][x as usize] = c};
235
236 for i in 0..self.size.0 as i32 {
237 let xc = xc_vals[(i + 1) as usize];
238 let (ycl, yc, ycr) = (yc_vals[i as usize], yc_vals[(i + 1) as usize], yc_vals[(i + 2) as usize]);
239
240 let rycl = yc - ycl;
241 let rycr = yc - ycr;
242
243 let lowest_surrounding = std::cmp::min(rycl, rycr);
245 if lowest_surrounding < -1 {
246 for char_height_diff in (lowest_surrounding + 1)..0 {
247 set_o_char(xc, yc - char_height_diff, VERTICAL);
248 }
249 }
250
251 let chr =
253 match (rycl.clamp(-1, 1), rycr.clamp(-1, 1)) {
254 (-1, -1) => FLAT_LOW,
255 (0, 0) => FLAT_MED,
256 (1, 1) => FLAT_HIGH,
257
258 (-1, 1) => UP_TWO,
259 (1, -1) => DOWN_TWO,
260
261 (-1, 0) => FLAT_LOW,
262 (0, 1) => FLAT_HIGH,
263 (0, -1) => FLAT_LOW,
264 (1, 0) => FLAT_HIGH,
265
266 (_, _) => NULL_CHR,
267 };
268
269 set_o_char(xc, yc, chr);
270 }
271
272 o.into_par_iter().map(|l| l.into_iter().collect::<String>()).collect::<Vec<String>>().join("\n")
273 }
274
275 fn as_string(&self) -> String {
276 add_opt_axes_and_opt_titles(&self.plot(), self.domain_and_range, self.axes, self.title)
277 }
278
279 fn print(&self) {
280 println!("{}", self.as_string());
281 }
282
283 fn pyplot(&self, path: Option<&str>) {
284 let x_vals: Vec<f64>;
285 let y_vals: Vec<f64>;
286
287 match self.precomputed {
288 Some(vals) => {
289 x_vals = vals.iter().map(|p| p.0).collect();
290 y_vals = vals.iter().map(|p| p.1).collect();
291 }
292 None => {
293 x_vals = subdivide(self.domain_and_range.0.0, self.domain_and_range.0.1, 10 * self.size.0);
294 y_vals = x_vals.iter().map(|x| (self.func)(*x)).collect();
295 }
296 }
297
298 let command = format!("plot({x_vals:?}, {y_vals:?})");
299 pyplot(&command, self.title, Some(self.axes), Some(self.domain_and_range), path);
300 }
301}
302
303pub fn function_plot<'a>(func: &'a impl Fn(f64) -> f64) -> FuncPlotBuilder<'a> {
347 FuncPlotBuilder::from(func)
348}
349
350pub fn as_float_function<'a, U, V>(func: impl Fn(U) -> V) -> impl Fn(f64) -> f64
352where
353 U: FromPrimitive,
354 V: ToPrimitive,
355{
356 move |x: f64| func(U::from_f64(x).unwrap()).to_f64().unwrap()
357}