simple_chart/
chart.rs

1use std::f64;
2
3use BitMap;
4use line;
5use Axis;
6
7const W_ARROW: usize = 4;      //width of arrow
8const W_NUMBER: usize = 4;     //number width in pixel
9const H_NUMBER: usize = 5;     //number height in pixels
10const W_BORDER: usize = 1;     //space around graph width
11const H_ARROW_HALF: usize = 3;
12
13const LEFT_SHIFT: usize = W_BORDER + W_NUMBER + H_NUMBER;
14const RIGHT_SHIFT: usize = W_ARROW;
15
16
17quick_error! {
18    #[derive(Debug)]
19    pub enum GraphError {
20        NotEnoughPoints {
21            description("There are not enough points to display on graph.")
22        }
23        NotEnoughSpace {
24            description("There are not enough width and height to form graph with axis.")
25        }
26        NonUniquePoints {
27            description("There are only one unique point. Can't construct line.")
28        }
29    }
30}
31
32pub type GraphResult = Result<Vec<u8>, GraphError>;
33
34
35pub trait InPoint: Into<Point> + PartialEq {}
36impl<T: Into<Point> + PartialEq> InPoint for T {}
37
38pub trait IterInPoint<P: InPoint>: Iterator<Item = P> + Clone {}
39impl<T, P> IterInPoint<P> for T
40    where T: Iterator<Item = P> + Clone,
41          P: InPoint
42{
43}
44
45#[derive(Clone, Copy)]
46pub struct Point {
47    pub x: f64,
48    pub y: f64,
49}
50
51impl<'a> From<&'a (f64, f64)> for Point {
52    fn from(t: &'a (f64, f64)) -> Point {
53        Point { x: t.0, y: t.1 }
54    }
55}
56
57impl From<(f64, f64)> for Point {
58    fn from(t: (f64, f64)) -> Point {
59        Point { x: t.0, y: t.1 }
60    }
61}
62
63#[derive(PartialEq, Clone, Copy, Debug)]
64pub struct DisplayPoint {
65    pub x: usize,
66    pub y: usize,
67}
68
69
70#[derive(Debug, Clone)]
71pub struct Serie<T: IterInPoint<P, Item = P>, P: InPoint> {
72    pub iter: T,
73    color: String,
74    max_x: f64,
75    max_y: f64,
76    min_x: f64,
77    min_y: f64,
78}
79
80impl<P: InPoint, T: IterInPoint<P>> Serie<T, P> {
81    pub fn new<S: Into<String>>(iter: T, color: S) -> Result<Self, GraphError> {
82
83        let color = color.into();
84
85        if iter.clone().nth(1).is_none() {
86            return Err(GraphError::NotEnoughPoints);
87        }
88
89        let first = iter.clone().nth(0).unwrap();
90        if !iter.clone().skip(1).any(move |p| p != first) {
91            return Err(GraphError::NonUniquePoints);
92        }
93
94        let (max_x, min_x, max_y, min_y) = Self::calculate_max_min(iter.clone());
95
96        Ok(Serie {
97            iter: iter,
98            color: color,
99            max_x: max_x,
100            max_y: max_y,
101            min_x: min_x,
102            min_y: min_y,
103        })
104    }
105
106    fn calculate_max_min(iter: T) -> (f64, f64, f64, f64) {
107        let (mut min_x, mut max_x) = (f64::INFINITY, f64::NEG_INFINITY);
108        let (mut min_y, mut max_y) = (f64::INFINITY, f64::NEG_INFINITY);
109
110        for p in iter {
111            let p = p.into();
112            if p.x > max_x {
113                max_x = p.x;
114            }
115            if p.x < min_x {
116                min_x = p.x;
117            }
118            if p.y > max_y {
119                max_y = p.y;
120            }
121            if p.y < min_y {
122                min_y = p.y;
123            }
124        }
125        (max_x, min_x, max_y, min_y)
126    }
127}
128
129
130#[derive(Debug)]
131pub struct Chart {
132    width: usize,
133    height: usize,
134    background_color: u8,
135    axis_color: u8,
136    pixs: Vec<u8>,
137    picture: BitMap,
138    axis_x: Option<Axis>,
139    axis_y: Option<Axis>,
140}
141
142impl Chart {
143    pub fn new(width: usize,
144               height: usize,
145               background_color: &str,
146               axis_color: &str)
147               -> Result<Self, GraphError> {
148
149        if width < (2 * H_NUMBER + 2 * W_NUMBER + W_ARROW + 2 * W_BORDER) ||
150           height < (2 * H_NUMBER + 2 * W_NUMBER + W_ARROW + 2 * W_BORDER) {
151            return Err(GraphError::NotEnoughSpace);
152        };
153
154        let mut picture = BitMap::new(width, height);
155
156        let background_color_number = picture.add_color(background_color);
157
158        let axis_color_number = picture.add_color(axis_color);
159
160        let size = width * height;
161
162        let pixs = vec![background_color_number;  size];
163
164        Ok(Chart {
165            width: width,
166            height: height,
167            background_color: background_color_number,
168            axis_color: axis_color_number,
169            pixs: pixs,
170            picture: picture,
171            axis_x: None,
172            axis_y: None,
173        })
174    }
175
176    pub fn add_axis_x(self, axis_x: Axis) -> Chart {
177        let new_axis_x = Some(Axis::set_axis_manual(axis_x.min_value,
178                                                    axis_x.max_value,
179                                                    axis_x.interval_count,
180                                                    axis_x.decimal_places,
181                                                    self.width));
182        Chart { axis_x: new_axis_x, ..self }
183    }
184
185
186    pub fn add_axis_y(self, axis_y: Axis) -> Chart {
187        let new_axis_y = Some(Axis::set_axis_manual(axis_y.min_value,
188                                                    axis_y.max_value,
189                                                    axis_y.interval_count,
190                                                    axis_y.decimal_places,
191                                                    self.height)
192            .rotate());
193        Chart { axis_y: new_axis_y, ..self }
194    }
195
196    fn draw_serie<P: InPoint, T: IterInPoint<P>>(&mut self, serie: Serie<T, P>) {
197
198        let func_points = {
199
200            let max_width = self.width - RIGHT_SHIFT;
201
202            let max_height = self.height - RIGHT_SHIFT;
203
204            let function = self.serie_to_points(&serie);
205
206            line::extrapolate(function)
207                .filter(|p| {
208                    p.x >= LEFT_SHIFT && p.x <= max_width && p.y >= LEFT_SHIFT && p.y <= max_height
209                })
210                .collect::<Vec<DisplayPoint>>()
211
212        };
213
214        let points_color_number = self.picture.add_color(&*serie.color);
215
216        self.draw_pixels(func_points, points_color_number);
217    }
218
219
220    fn calc_axis<S, T, P>(&mut self, series: S)
221        where S: Iterator<Item = Serie<T, P>>,
222              T: IterInPoint<P>,
223              P: InPoint
224    {
225        let (mut min_x, mut max_x) = (f64::INFINITY, f64::NEG_INFINITY);
226        let (mut min_y, mut max_y) = (f64::INFINITY, f64::NEG_INFINITY);
227
228        for s in series {
229            if s.max_x > max_x {
230                max_x = s.max_x;
231            }
232            if s.min_x < min_x {
233                min_x = s.min_x;
234            }
235
236            if s.max_y > max_y {
237                max_y = s.max_y;
238            }
239            if s.min_y < min_y {
240                min_y = s.min_y;
241            }
242        }
243
244        if self.axis_x.is_none() {
245            self.axis_x = Some(Axis::set_axis_auto(max_x, min_x, self.width));
246        }
247
248        if self.axis_y.is_none() {
249            self.axis_y = Some(Axis::set_axis_auto(max_y, min_y, self.height).rotate());
250        }
251    }
252
253    pub fn draw<S, T, P>(&mut self, series: S) -> Vec<u8>
254        where S: Iterator<Item = Serie<T, P>> + Clone,
255              T: IterInPoint<P>,
256              P: InPoint
257    {
258
259        if self.axis_x.is_none() || self.axis_y.is_none() {
260            self.calc_axis(series.clone());
261        }
262
263        self.draw_axis();
264
265        for serie in series {
266            self.draw_serie(serie);
267        }
268
269        self.picture.add_pixels(&self.pixs);
270
271        self.picture.as_vec()
272    }
273
274    fn draw_axis(&mut self) {
275
276        let axis_x = self.axis_x.clone().unwrap();
277
278        let axis_y = self.axis_y.clone().unwrap();
279
280        let minor_net = self.get_minor_net(&axis_x, &axis_y);
281
282        let axis_color = self.axis_color;
283
284        self.draw_pixels(axis_x.create_points(), axis_color);
285
286        self.draw_pixels(axis_y.create_points(), axis_color);
287
288        self.draw_pixels(minor_net, axis_color);
289    }
290
291    fn get_minor_net(&self, axis_x: &Axis, axis_y: &Axis) -> Vec<DisplayPoint> {
292        let mut v: Vec<DisplayPoint> = vec![];
293        for i in 0..axis_x.interval_count {
294            let shift = LEFT_SHIFT + ((axis_x.scale_interval_pix * (i as f64)).round() as usize);
295            for j in LEFT_SHIFT..(self.height - H_ARROW_HALF) {
296                if j % 2 != 0 {
297                    v.push(DisplayPoint { x: shift, y: j });
298                }
299            }
300        }
301
302        for i in 0..axis_y.interval_count {
303            let shift = LEFT_SHIFT + ((axis_y.scale_interval_pix * (i as f64)).round() as usize);
304            for j in LEFT_SHIFT..(self.width - H_ARROW_HALF) {
305                if j % 2 != 0 {
306                    v.push(DisplayPoint { x: j, y: shift });
307                }
308            }
309        }
310        v
311    }
312
313    fn serie_to_points<'b, P: InPoint, T: IterInPoint<P>>
314        (&'b mut self,
315         serie: &'b Serie<T, P>)
316         -> Box<Iterator<Item = DisplayPoint> + 'b> {
317
318        let width_available = self.width - LEFT_SHIFT - RIGHT_SHIFT;
319
320        let height_available = self.height - LEFT_SHIFT - RIGHT_SHIFT;
321
322        let axis_x = self.axis_x.clone().unwrap();
323
324        let axis_y = self.axis_y.clone().unwrap();
325
326        let resolution_x: f64 = (axis_x.max_value - axis_x.min_value) / (width_available as f64);
327        let resolution_y: f64 = (axis_y.max_value - axis_y.min_value) / (height_available as f64);
328
329        let serie_iter = serie.iter.clone();
330
331        Box::new(serie_iter.map(move |p| {
332            let p = p.into();
333            let id_x = ((p.x - axis_x.min_value) / resolution_x).round();
334            let id_y = ((p.y - axis_y.min_value) / resolution_y).round();
335
336            let id_x = if id_x < 0f64 {
337                LEFT_SHIFT - 1
338            } else if id_x > (width_available as f64) {
339                width_available + LEFT_SHIFT + 1
340            } else {
341                (id_x as usize) + LEFT_SHIFT
342            };
343
344            let id_y = if id_y < 0f64 {
345                LEFT_SHIFT - 1
346            } else if id_y > (height_available as f64) {
347                height_available + LEFT_SHIFT + 1
348            } else {
349                (id_y as usize) + LEFT_SHIFT
350            };
351
352            DisplayPoint { x: id_x, y: id_y }
353        }))
354
355    }
356
357
358    fn draw_pixels(&mut self, points: Vec<DisplayPoint>, color: u8) {
359        for p in points {
360            let i = p.y * self.width + p.x;
361            self.pixs[i] = color;
362        }
363    }
364}
365
366
367
368#[cfg(test)]
369mod tests {
370    use super::*;
371    use Axis;
372
373    #[test]
374    fn not_enough_space_test() {
375        let result = Chart::new(10, 15, "#ffffff", "#000000");
376        assert_eq!(result.err().unwrap().to_string(),
377                   "There are not enough width and height to form graph with axis.");
378    }
379
380    #[test]
381    fn not_enough_points_test() {
382        let v: Vec<(f64, f64)> = vec![];
383        let result = Serie::new(v.into_iter(), "#0000ff".to_string());
384        assert_eq!(result.err().unwrap().to_string(),
385                   "There are not enough points to display on graph.");
386    }
387
388    #[test]
389    fn one_point_test() {
390        let p = vec![(1f64, 1f64)];
391        let result = Serie::new(p.into_iter(), "#0000ff".to_string());
392        assert_eq!(result.err().unwrap().to_string(),
393                   "There are not enough points to display on graph.");
394    }
395
396    #[test]
397    fn two_identical_point_test() {
398        let p = vec![(1f64, 1f64), (1f64, 1f64)];
399        let result = Serie::new(p.into_iter(), "#0000ff".to_string());
400        assert_eq!(result.err().unwrap().to_string(),
401                   "There are only one unique point. Can't construct line.");
402    }
403
404    #[test]
405    fn can_draw_array() {
406        let p = vec![(1f64, 1f64), (2f64, 2f64), (3f64, 3f64)];
407        let serie = Serie::new(p.into_iter(), "#0000ff".to_string()).unwrap();
408        let mut chart = Chart::new(100, 100, "#ffffff", "#000000").unwrap();
409        let series = vec![serie];
410        let bmp = chart.draw(series.into_iter());
411        for p in bmp {
412            println!("{}", p);
413        }
414    }
415
416    #[test]
417    fn can_draw_axis_manual() {
418        let p = vec![(1f64, 1f64), (2f64, 2f64), (3f64, 3f64)];
419        let serie = Serie::new(p.into_iter(), "#0000ff".to_string()).unwrap();
420        let axis_x = Axis::new(0f64, 2f64, 7, 2);
421        let mut chart = Chart::new(100, 100, "#ffffff", "#000000")
422            .unwrap()
423            .add_axis_x(axis_x);
424        let series = vec![serie];
425        let _ = chart.draw(series.into_iter());
426    }
427}
428
429#[cfg(all(feature = "dev", test))]
430mod bench {
431    extern crate test;
432    use super::*;
433
434    #[bench]
435    fn create_graph_2_points(b: &mut test::Bencher) {
436        b.iter(|| {
437            let p = vec![(1f64, 1f64), (2f64, 2f64), (3f64, 3f64)];
438            let serie = Serie::new(p.into_iter(), "#0000ff".to_string()).unwrap();
439            let mut chart = Chart::new(740, 480, "#ffffff", "#000000").unwrap();
440            let series = vec![serie];
441            let _ = chart.draw(series.into_iter());
442        })
443    }
444
445    #[bench]
446    fn create_graph_1000_points(b: &mut test::Bencher) {
447        b.iter(|| {
448            let p: Vec<_> = formula!(y(x) = {x*x}, x = [0, 1000; 1]).collect();
449            let serie = Serie::new(p.into_iter(), "#0000ff".to_string()).unwrap();
450            let mut chart = Chart::new(740, 480, "#ffffff", "#000000").unwrap();
451            let series = vec![serie];
452            let _ = chart.draw(series.into_iter());
453        })
454    }
455
456    #[bench]
457    #[ignore]
458    fn create_graph_1000000_points(b: &mut test::Bencher) {
459        b.iter(|| {
460            let p: Vec<_> = formula!(y(x) = {x*x}, x = [0, 1000; 0.001]).collect();
461            let serie = Serie::new(p.into_iter(), "#0000ff".to_string()).unwrap();
462            let mut chart = Chart::new(740, 480, "#ffffff", "#000000").unwrap();
463            let series = vec![serie];
464            let _ = chart.draw(series.into_iter());
465        })
466    }
467}