1use crate::{
2 ast::{Function, Number},
3 calc::{calc_operand, Env, TopLevelEnv},
4};
5
6use num::iter::range_step_from;
7
8use thiserror::Error;
9
10use std::cmp::PartialEq;
11use std::fmt::Debug;
12
13#[derive(Error, Debug, PartialEq, Eq)]
14pub enum GraphError {
15 #[error("Unknown function `{0}` to plot")]
16 UnknownFunction(String),
17}
18
19struct ArgEnv<'a> {
20 name: &'a str,
21 value: Number,
22 env: &'a dyn Env,
23}
24
25impl<'a> Env for ArgEnv<'a> {
26 fn get(&self, sym: &str) -> Option<&Number> {
27 if sym == self.name {
28 Some(&self.value)
29 } else {
30 self.env.get(sym)
31 }
32 }
33
34 fn get_fun(&self, fun: &str) -> Option<&Function> {
35 self.env.get_fun(fun)
36 }
37}
38
39#[derive(Debug, PartialEq)]
40pub struct Graph {
41 env: TopLevelEnv,
42 fun: Function,
43}
44
45impl Graph {
46 pub fn new(name: &str, env: &TopLevelEnv) -> Result<Graph, GraphError> {
47 let env = env.clone();
48 let graph = Graph {
49 fun: env
50 .get_fun(name)
51 .ok_or_else(|| GraphError::UnknownFunction(name.to_string()))?
52 .clone(),
53 env,
54 };
55
56 Ok(graph)
57 }
58
59 fn x_name(&self) -> &str {
60 match self.fun {
61 Function::Custom(ref fun) => &fun.args[0],
62 Function::BuildIn(ref fun) => &fun.arg,
63 }
64 }
65
66 fn calc(&self, x: Number) -> Option<Number> {
67 match self.fun {
68 Function::Custom(ref fun) => {
69 let call_env = ArgEnv {
70 name: self.x_name(),
71 value: x,
72 env: &self.env,
73 };
74 calc_operand(&fun.body, &call_env).ok()
75 }
76 Function::BuildIn(ref fun) => Some((fun.body)(x)),
77 }
78 }
79
80 pub fn plot(&self, area: &Area, screen: &Area) -> Result<Plot, GraphError> {
81 Plot::new(self, area, screen)
82 }
83}
84
85#[derive(Debug, PartialEq, Clone, Copy)]
86pub struct Range {
87 pub min: Number,
88 pub max: Number,
89}
90
91impl Range {
92 pub fn new(min: Number, max: Number) -> Range {
93 if min >= max {
94 panic!("min {:?} must be smaller than max {:?}", min, max);
95 }
96 Range { min, max }
97 }
98
99 pub fn contains(&self, pos: Number) -> bool {
100 (self.min..self.max).contains(&pos)
101 }
102
103 pub fn get_distance(&self) -> Number {
104 self.max - self.min
105 }
106
107 pub fn project_inclusive(&self, pixel: Number, to: &Range) -> Option<Number> {
108 if self.contains(pixel) {
109 Some(self.project(pixel, to))
110 } else {
111 None
112 }
113 }
114
115 pub fn project(&self, pixel: Number, to: &Range) -> Number {
116 to.min + (((pixel - self.min) / self.get_distance()) * to.get_distance())
117 }
118
119 pub fn move_by(&mut self, delta: Number) {
120 self.min += delta;
121 self.max += delta;
122 }
123
124 pub fn zoom_by(&mut self, factor: Number) {
125 let distance = self.get_distance();
126 let new_distance = distance * factor;
127 let diff = (new_distance - distance) / 2.;
128 self.min -= diff;
129 self.max += diff;
130 }
131}
132
133#[derive(Debug, PartialEq, Clone, Copy)]
134pub struct Area {
135 pub x: Range,
136 pub y: Range,
137}
138
139impl Area {
140 pub fn new(x_min: Number, y_min: Number, x_max: Number, y_max: Number) -> Area {
141 Area {
142 x: Range::new(x_min, x_max),
143 y: Range::new(y_min, y_max),
144 }
145 }
146
147 pub fn move_by(&mut self, x_delta: Number, y_delta: Number) {
148 self.x.move_by(x_delta);
149 self.y.move_by(y_delta);
150 }
151
152 pub fn zoom_by(&mut self, factor: Number) {
153 self.x.zoom_by(factor);
154 self.y.zoom_by(factor);
155 }
156}
157
158#[derive(Debug, PartialEq, Copy, Clone)]
159pub struct Tic {
160 pub pos: Number,
161 pub label: Number,
162}
163
164impl Tic {
165 pub fn new(pos: Number, label: Number) -> Tic {
166 Tic { pos, label }
167 }
168
169 pub fn create_tics(screen: &Range, area: &Range) -> Vec<Tic> {
170 let width = area.max - area.min;
171 let step = 10f64.powf((width.log10() - 1.0).round());
172 if area.contains(0.0) {
173 let left: Vec<Tic> = range_step_from(0f64, -step)
174 .take_while(|label| label > &area.min)
175 .map(|label| Tic::new(area.project_inclusive(label, screen).unwrap(), label))
176 .collect();
177
178 let right: Vec<Tic> = range_step_from(step, step)
179 .take_while(|label| label < &area.max)
180 .map(|label| Tic::new(area.project_inclusive(label, screen).unwrap(), label))
181 .collect();
182
183 left.iter().rev().chain(right.iter()).copied().collect()
184 } else {
185 let start = (area.min / step).ceil() * step;
186
187 range_step_from(start, step)
188 .take_while(|label| label < &area.max)
189 .map(|label| Tic::new(area.project_inclusive(label, screen).unwrap(), label))
190 .collect()
191 }
192 }
193}
194
195#[derive(Debug, PartialEq)]
196pub struct Axis {
197 pub pos: Number,
198 pub tics: Vec<Tic>,
199}
200
201impl Axis {
202 pub fn new(pos: Option<Number>, screen: &Range, area: &Range) -> Option<Axis> {
203 pos.map(|pos| Axis {
204 pos,
205 tics: Tic::create_tics(screen, area),
206 })
207 }
208}
209
210#[derive(Debug, PartialEq)]
211pub struct Plot {
212 pub points: Vec<Option<Number>>,
213 pub screen: Area,
214 pub x_axis: Option<Axis>,
215 pub y_axis: Option<Axis>,
216}
217
218impl Plot {
219 pub fn new(graph: &Graph, area: &Area, screen: &Area) -> Result<Plot, GraphError> {
220 let points = ((screen.x.min as i32)..(screen.x.max as i32))
221 .map(|w| {
222 let x = screen.x.project_inclusive(w as f64, &area.x).unwrap();
223 graph.calc(x).map(|y| area.y.project(y, &screen.y))
224 })
225 .collect();
226 let x_axis = Axis::new(area.y.project_inclusive(0., &screen.y), &screen.x, &area.x);
227 let y_axis = Axis::new(area.x.project_inclusive(0., &screen.x), &screen.y, &area.y);
228
229 Ok(Plot {
230 points,
231 screen: *screen,
232 x_axis,
233 y_axis,
234 })
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::ast::{CustomFunction, Operand, Operation, Term};
242 use crate::calc::TopLevelEnv;
243 use assert_approx_eq::assert_approx_eq;
244
245 #[test]
246 fn function_arg_x() {
247 let mut env = TopLevelEnv::default();
248 env.put("x".to_string(), -19.0).unwrap();
249 let name = "x";
250 let value = 42.0;
251 let env = ArgEnv {
252 name,
253 value,
254 env: &env,
255 };
256 assert_eq!(Some(&42.0), env.get("x"));
257 }
258
259 #[test]
260 fn function_arg_y() {
261 let mut env = TopLevelEnv::default();
262 env.put("y".to_string(), -19.0).unwrap();
263 let name = "x";
264 let value = 42.0;
265 let env = ArgEnv {
266 name,
267 value,
268 env: &env,
269 };
270 assert_eq!(Some(&-19.0), env.get("y"));
271 }
272
273 #[test]
274 fn function_call() {
275 let fun = Function::Custom(CustomFunction {
276 args: vec!["x".to_string()],
277 body: Operand::Symbol("x".to_string()),
278 });
279 let env = TopLevelEnv::default();
280 let graph = Graph { fun: fun, env };
281 assert_eq!(Some(1.0), graph.calc(1.0));
282 }
283
284 #[test]
285 #[should_panic(expected = "min 4.0 must be smaller than max 3.0")]
286 fn range_construct_failure() {
287 let _ = Range::new(4., 3.);
288 }
289
290 #[test]
291 fn range_distance_f64() {
292 assert_eq!(4.0, Range::new(10.0, 14.0).get_distance());
293 }
294
295 #[test]
296 fn range_project_plot_to_screen() {
297 let plot = Range::new(-100., 100.);
298 let screen = Range::new(0., 400.);
299
300 assert_eq!(Some(200.0), plot.project_inclusive(0., &screen));
301 assert_eq!(Some(300.0), plot.project_inclusive(50., &screen));
302 assert_eq!(Some(100.0), plot.project_inclusive(-50., &screen));
303 }
304
305 #[test]
306 fn range_project_plot_to_screen_out_of_range() {
307 let plot = Range::new(-100., 100.);
308 let screen = Range::new(0., 400.);
309
310 assert_eq!(None, plot.project_inclusive(-101., &screen));
311 assert_eq!(None, plot.project_inclusive(100., &screen));
312 }
313
314 #[test]
315 fn range_project_screen_to_plot() {
316 let screen = Range::new(0., 400.);
317 let plot = Range::new(-100., 100.);
318
319 assert_eq!(Some(-100.0), screen.project_inclusive(0., &plot));
320 assert_eq!(Some(-50.0), screen.project_inclusive(100., &plot));
321 assert_eq!(Some(0.0), screen.project_inclusive(200., &plot));
322 assert_eq!(Some(50.0), screen.project_inclusive(300., &plot));
323 assert_eq!(Some(99.5), screen.project_inclusive(399., &plot));
324 }
325
326 #[test]
327 fn range_project_screen_to_plot_out_of_range() {
328 let screen = Range::new(0., 400.);
329 let plot = Range::new(-100., 100.);
330
331 assert_eq!(None, screen.project_inclusive(-1., &plot));
332 assert_eq!(None, screen.project_inclusive(400., &plot));
333 }
334
335 #[test]
336 fn construct_plot() {
337 let mut env = TopLevelEnv::default();
338 let term = {
339 let lhs = Operand::Symbol("x".to_string());
340 let rhs = Operand::Number(2.0);
341 let op = Operation::Mul;
342 Term { lhs, rhs, op }
343 };
344 let body = Operand::Term(Box::new(term));
345 let fun = Function::Custom(CustomFunction {
346 args: vec!["x".to_string()],
347 body,
348 });
349 env.put_fun("f".to_string(), fun);
350 let graph = Graph::new("f", &env).unwrap();
351 let area = Area::new(-100., -100., 100., 100.);
352 let screen = Area::new(0., 0., 40., 40.);
353 let plot = graph.plot(&area, &screen).unwrap();
354
355 assert_eq!(20., plot.x_axis.unwrap().pos);
356 assert_eq!(20., plot.y_axis.unwrap().pos);
357 assert_eq!(40, plot.points.len());
358 assert_eq!(Some(-20.), plot.points[0]);
359 assert_eq!(Some(18.), plot.points[19]);
360 assert_eq!(Some(58.), plot.points[39]);
361 }
362
363 #[test]
364 fn range_move_by_positive() {
365 let mut r = Range::new(0., 10.);
366 r.move_by(2.);
367 assert_approx_eq!(2., r.min);
368 assert_approx_eq!(12., r.max);
369 }
370
371 #[test]
372 fn range_move_by_negative() {
373 let mut r = Range::new(2., 12.);
374 r.move_by(-5.);
375 assert_approx_eq!(-3., r.min);
376 assert_approx_eq!(7., r.max);
377 }
378
379 #[test]
380 fn area_move_by() {
381 let mut a = Area::new(0., 0., 10., 10.);
382 a.move_by(2., -3.);
383 assert_approx_eq!(2., a.x.min);
384 assert_approx_eq!(12., a.x.max);
385 assert_approx_eq!(-3., a.y.min);
386 assert_approx_eq!(7., a.y.max);
387 }
388
389 #[test]
390 fn range_zoom_by_out() {
391 let mut r = Range::new(2., 12.);
392 r.zoom_by(1.2);
393 assert_approx_eq!(1., r.min);
394 assert_approx_eq!(13., r.max);
395 }
396
397 #[test]
398 fn range_zoom_by_in() {
399 let mut r = Range::new(2., 12.);
400 r.zoom_by(0.8);
401 assert_approx_eq!(3., r.min);
402 assert_approx_eq!(11., r.max);
403 }
404
405 #[test]
406 fn create_tics_with_zero() {
407 use float_cmp::approx_eq;
408
409 let act = Tic::create_tics(&Range::new(-100., 100.), &Range::new(-5., 15.));
410 let exp: Vec<Tic> = range_step_from(-90., 10.)
411 .zip(range_step_from(-4., 1.))
412 .take(19)
413 .map(|(pos, label)| Tic::new(pos, label))
414 .collect();
415
416 assert_eq!(exp.len(), act.len());
417 assert!(exp.iter().zip(act.iter()).all(|(exp, act)| approx_eq!(
418 f64,
419 exp.pos,
420 act.pos,
421 epsilon = 0.00001
422 ) && approx_eq!(
423 f64,
424 exp.label,
425 act.label,
426 epsilon = 0.00001
427 )));
428 }
429
430 #[test]
431 fn create_tics_above_zero() {
432 use float_cmp::approx_eq;
433
434 let act = Tic::create_tics(&Range::new(0., 400.), &Range::new(3., 19.));
435 let exp: Vec<Tic> = range_step_from(0., 25.)
436 .zip(range_step_from(3., 1.))
437 .take(16)
438 .map(|(pos, label)| Tic::new(pos, label))
439 .collect();
440
441 assert_eq!(exp.len(), act.len());
442 println!("{:?}", act);
443 assert!(exp.iter().zip(act.iter()).all(|(exp, act)| approx_eq!(
444 f64,
445 exp.pos,
446 act.pos,
447 epsilon = 0.00001
448 ) && approx_eq!(
449 f64,
450 exp.label,
451 act.label,
452 epsilon = 0.00001
453 )));
454 }
455
456 #[test]
457 fn create_tics_below_zero() {
458 use float_cmp::approx_eq;
459
460 let act = Tic::create_tics(&Range::new(0., 400.), &Range::new(-19., -3.));
461 let exp: Vec<Tic> = range_step_from(0., 25.)
462 .zip(range_step_from(-19., 1.))
463 .take(16)
464 .map(|(pos, label)| Tic::new(pos, label))
465 .collect();
466
467 assert_eq!(exp.len(), act.len());
468 println!("{:?}", act);
469 assert!(exp.iter().zip(act.iter()).all(|(exp, act)| approx_eq!(
470 f64,
471 exp.pos,
472 act.pos,
473 epsilon = 0.00001
474 ) && approx_eq!(
475 f64,
476 exp.label,
477 act.label,
478 epsilon = 0.00001
479 )));
480 }
481}