matplotrust/
lib.rs

1//                                         ______          _         
2//                   _         _       _   | ___ \        | |        
3//   _ __ ___   __ _| |_ _ __ | | ___ | |_ | |_/ /   _ ___| |_       
4//  | '_ ` _ \ / _` | __| '_ \| |/ _ \| __||    / | | / __| __|      
5//  | | | | | | (_| | |_| |_) | | (_) | |_ | |\ \ |_| \__ \ |_       
6//  |_| |_| |_|\__,_|\__| .__/|_|\___/ \__|\_| \_\__,_|___/\__|      
7//                      |_|                                          
8extern crate tempfile;
9extern crate image;
10extern crate base64;
11
12use std::fs::File;
13use std::fs;
14use std::io::{Write, Read, Seek, SeekFrom};
15use std::process::Command;
16use tempfile::NamedTempFile;
17use std::fmt;
18use image::GenericImage;
19use image::Pixels;
20use image::Pixel;
21use base64::encode;
22
23#[cfg(test)]
24#[macro_use]
25extern crate probability;
26
27pub enum Markers {
28    Point,
29    Pixel,
30    Circle,
31    Triangle_Down,
32    Triangle_Up,
33    Triangle_Left,
34    Triangle_Right,
35    Tri_Down,
36    Tri_Up,
37    Tri_Left,
38    Tri_Right,
39    Square,
40    Pentagon,
41    Star,
42    Hexagon1,
43    Hexagon2,
44    Plus,
45    X ,
46    Diamond,
47    Thin_Diamond,
48    VLine,
49    HLine,
50}
51
52trait AsString {
53    fn as_str(&self) -> &str;
54}
55
56impl Markers {
57    pub fn as_str(&self) -> &str {
58        match self {
59            &Markers::Point => ".", 	// point marker
60            &Markers::Pixel => ",", 	// pixel marker
61            &Markers::Circle => "o", 	// circle marker
62            &Markers::Triangle_Down => "v",  // triangle_down marker
63            &Markers::Triangle_Up => "^", 	// triangle_up marker
64            &Markers::Triangle_Left => "<",  //triangle_left marker
65            &Markers::Triangle_Right => ">", // triangle_right marker
66            &Markers::Tri_Down => "1",  // tri_down marker
67            &Markers::Tri_Up => "2", // tri_up marker
68            &Markers::Tri_Left => "3", // tri_left marker
69            &Markers::Tri_Right => "4", // tri_right marker
70            &Markers::Square => "s", // square marker
71            &Markers::Pentagon => "p", // pentagon marker
72            &Markers::Star => "*", // star marker
73            &Markers::Hexagon1 => "h", // hexagon1 marker
74            &Markers::Hexagon2 => "H", // hexagon2 marker
75            &Markers::Plus => "+", // plus marker
76            &Markers::X => "x", // x marker
77            &Markers::Diamond => "D", // diamond marker
78            &Markers::Thin_Diamond => "d", // thin_diamond marker
79            &Markers::VLine => "|", // vline marker
80            &Markers::HLine => "_", // hline marker
81        }
82    }
83}
84
85pub enum LineStyle {
86    Dot,
87    DashDot,
88    Dash,
89    Fill,
90}
91
92impl LineStyle {
93    pub fn as_str(&self) -> &str {
94        match self {
95            &LineStyle::Dot => ":",
96            &LineStyle::DashDot => "-.",
97            &LineStyle::Dash => "--",
98            &LineStyle::Fill => "-",
99        }
100    }
101}
102
103pub struct Figure {
104    script: String
105}
106
107impl Figure {
108
109    pub fn new() -> Figure {
110        return Figure {
111            script: "import matplotlib\nmatplotlib.use('agg')\nimport matplotlib.pyplot as plt\nfrom matplotlib.lines import Line2D\n\n".to_string()
112        }   
113    }
114
115    pub fn add_plot(&mut self, p: String) {
116        self.script += &p;
117    }
118
119    pub fn save(&mut self, output: &str, path: Option<&str>) -> (String, String, String) {
120        self.script += &format!("plt.savefig('{}')\n", output);
121        // create a temporary file
122        let mut tmpfile: NamedTempFile = tempfile::NamedTempFile::new().unwrap();
123        tmpfile.write_all(self.script.as_bytes());
124
125        let python_path = match path {
126            Some(s) => s,
127            None => "/usr/local/bin/python3"
128        };
129
130        fs::metadata(python_path).expect("python binary not found at /usr/local/bin/python3");
131        let mut echo_hello = Command::new(python_path);
132        echo_hello.arg(tmpfile.path());
133        echo_hello.output().expect("failed to execute process");
134
135        return (self.script.to_string(), output.to_string(), tmpfile.path().to_str().unwrap().to_string());
136    }
137
138    pub fn as_base64(&mut self, path: Option<&str>) {
139        // create a temporary file for storing the image
140        let mut tmpfile: NamedTempFile = tempfile::NamedTempFile::new().unwrap();
141        let filename = tmpfile.path().to_str().unwrap().to_string() + &".png".to_string();
142        println!("tmpfile = {:?}", filename);
143        self.save(&filename, path);
144        // read the file back as a Base-64 PNG
145        let mut buffer = Vec::new();
146        let mut file = File::open(&filename).unwrap();
147        let _out = file.read_to_end(&mut buffer);
148        println!("EVCXR_BEGIN_CONTENT image/png\n{}\nEVCXR_END_CONTENT", base64::encode(&buffer));
149    }
150
151}
152
153pub struct LinePlotOptions {
154    pub marker: Option<Markers>,
155    pub lineStyle: Option<LineStyle>,
156    pub colour: Option<String>
157}
158
159impl LinePlotOptions {
160    pub fn new() -> LinePlotOptions {
161        return LinePlotOptions {
162            marker: None,
163            lineStyle: None,
164            colour: None
165        }
166    }
167}
168
169pub struct ScatterPlotOptions {
170    pub marker: Option<Markers>,
171    pub alpha: Option<f64>,
172}
173
174impl ScatterPlotOptions {
175    pub fn new() -> ScatterPlotOptions {
176        return ScatterPlotOptions {
177            marker: None,
178            alpha: None,
179        }
180    }
181}
182
183impl fmt::Display for LinePlotOptions {
184    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
185        let mut options : Vec<String> = Vec::new();
186        match self.marker {
187            Some(ref m) => {options.push(format!("marker='{}'", m.as_str()));}
188            None => {}
189        }
190        match self.lineStyle {
191            Some(ref v) => {options.push(format!("linestyle='{}'", v.as_str()));}
192            None => {}
193        }
194        match self.colour {
195            Some(ref v) => {options.push(format!("c='{}'", v.as_str()));}
196            None => {}
197        }
198        fmt.write_str(&options.join(", "));
199        Ok(())
200    }
201}
202
203impl fmt::Display for ScatterPlotOptions {
204    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
205        let mut options : Vec<String> = Vec::new();
206        match self.marker {
207            Some(ref m) => {options.push(format!("marker='{}'", m.as_str()));}
208            None => {}
209        }
210        match self.alpha {
211            Some(ref m) => {options.push(format!("alpha={}", m.to_string()));}
212            None => {}
213        }
214        fmt.write_str(&options.join(", "));
215        Ok(())
216    }
217}
218
219fn convert_list_str<T>(x: Vec<T>) -> String where T: ToString {
220    let mut result:Vec<String> = Vec::new();
221    for _x in x { result.push(_x.to_string()); }
222    return "[".to_string() + &result.join(", ") + &"]".to_string();
223}
224
225pub fn line_plot<U, T>(x: Vec<U>, y: Vec<T>, options: Option<LinePlotOptions>) -> String where U: ToString, T: ToString {
226    let xs = convert_list_str::<U>(x);
227    let ys = convert_list_str::<T>(y);
228    match options {
229        Some(opt) => {
230            return format!("plt.plot({},{},{})\n", xs, ys, opt);
231        },
232        None => {
233            return format!("plt.plot({},{})\n", xs, ys);
234        }
235    }
236}
237
238pub fn scatter_plot<U, T>(x: Vec<U>, y: Vec<T>, options: Option<ScatterPlotOptions>) -> String where U: ToString, T: ToString {
239    let xs = convert_list_str::<U>(x);
240    let ys = convert_list_str::<T>(y);
241        match options {
242        Some(opt) => {
243            return format!("plt.scatter({},{},{})\n", xs, ys, opt);
244        },
245        None => {
246            return format!("plt.scatter({},{})\n", xs, ys);
247        }
248    }
249    
250}
251
252pub fn histogram<U>(x: Vec<U>, bins: Option<u32>) -> String where U: ToString {
253    let xs = convert_list_str::<U>(x);
254    return format!("plt.hist({}, bins={})\n", xs, bins.unwrap_or(20));
255}
256
257pub fn horizontal_line<U>(y: U, options: Option<LinePlotOptions>) -> String where U: ToString {
258    match options {
259        Some(opt) => {
260            return format!("plt.axhline(y={},{})\n", y.to_string(), opt);
261        },
262        None => {
263            return format!("plt.axhline(x={})\n", y.to_string());
264        }
265    }
266}
267
268pub fn vertical_line<U>(x: U, options: Option<LinePlotOptions>) -> String where U: ToString {
269    match options {
270        Some(opt) => {
271            return format!("plt.axvline(x={},{})\n", x.to_string(), opt);
272        },
273        None => {
274            return format!("plt.axvline(x={})\n", x.to_string());
275        }
276    }
277}
278
279pub fn line<U, T>(start : (U, T), end : (U, T), options : Option<LinePlotOptions>) -> String where U : ToString, T : ToString {
280    match options {
281        Some(opt) => {
282            return format!("ax=plt.gca()\nax.add_line(Line2D([{},{}],[{},{}],{}))\n", start.0.to_string(), end.0.to_string(), start.1.to_string(), end.1.to_string(), opt);
283        },
284        None => {
285            return format!("ax=plt.gca()\nax.add_line(Line2D([{},{}],[{},{}]))\n", start.0.to_string(), end.0.to_string(), start.1.to_string(), end.1.to_string());
286        }
287    }
288}
289
290#[cfg(test)]
291mod lineplot {
292    use super::*;
293    use probability::distribution::Gaussian;
294    use probability::sampler::Independent;
295    use probability::source;
296    use ::LineStyle::Dash;
297
298    #[test]
299    fn create_lineplot_basic() {
300        let x = vec![1, 2, 3, 4];
301        let y = vec![0.1, 0.2, 0.5, 0.3];
302        let lp = line_plot::<i32, f64>(x, y, None);
303        let mut figure = Figure::new();
304        figure.add_plot(lp);
305        figure.save("./examples/figures/lineplot_basic.png", None);
306    }
307
308     #[test]
309    fn create_lineplot_basic_markers() {
310        let x = vec![1, 2, 3, 4];
311        let y = vec![0.1, 0.2, 0.5, 0.3];
312        let mut options = LinePlotOptions::new();
313        options.marker = Some(Markers::Diamond);
314        let lp = line_plot::<i32, f64>(x, y, Some(options));
315        let mut figure = Figure::new();
316        figure.add_plot(lp);
317        figure.save("./examples/figures/lineplot_basic_markers.png", None);
318    }
319
320     #[test]
321    fn create_lineplot_basic_linestyle() {
322        let x = vec![1, 2, 3, 4];
323        let y = vec![0.1, 0.2, 0.5, 0.3];
324        let mut options = LinePlotOptions::new();
325        options.marker = Some(Markers::Diamond);
326        options.lineStyle = Some(LineStyle::DashDot);
327        let lp = line_plot::<i32, f64>(x, y, Some(options));
328        let mut figure = Figure::new();
329        figure.add_plot(lp);
330        figure.save("./examples/figures/lineplot_basic_linestyle.png", None);
331    }
332
333    #[test]
334    fn create_scatterplot_basic() {
335        let x = vec![1, 2, 3, 4];
336        let y = vec![0.1, 0.2, 0.5, 0.3];
337        let lp = scatter_plot::<i32, f64>(x, y, None);
338        let mut figure = Figure::new();
339        figure.add_plot(lp);
340        figure.save("./examples/figures/scatterplot_basic.png", None);
341    }
342
343    #[test]
344    fn create_scatterplot_marker() {
345        let x = vec![1, 2, 3, 4];
346        let y = vec![0.1, 0.2, 0.5, 0.3];
347        let mut options = ScatterPlotOptions::new();
348        options.marker = Some(Markers::Diamond);
349        let lp = scatter_plot::<i32, f64>(x, y, Some(options));
350        let mut figure = Figure::new();
351        figure.add_plot(lp);
352        figure.save("./examples/figures/scatterplot_marker.png", None);
353    }
354    #[test]
355    fn create_scatterplot_marker_alpha() {
356        let x = vec![1, 2, 3, 4];
357        let y = vec![0.1, 0.2, 0.5, 0.3];
358        let mut options = ScatterPlotOptions::new();
359        options.marker = Some(Markers::Diamond);
360        options.alpha = Some(0.1);
361        let lp = scatter_plot::<i32, f64>(x, y, Some(options));
362        let mut figure = Figure::new();
363        figure.add_plot(lp);
364        figure.save("./examples/figures/scatterplot_marker_alpha.png", None);
365    }
366
367    #[test]
368    fn create_histogram_default_bins() {
369        let mut source = source::default();
370        let gaussian = Gaussian::new(0.0, 2.0);
371        let mut sampler = Independent(&gaussian, &mut source);
372        let x = sampler.take(500).collect::<Vec<_>>();
373        let plot = histogram::<f64>(x, None);
374        let mut figure = Figure::new();
375        figure.add_plot(plot);
376        figure.save("./examples/figures/histogram_default_bins.png", None);
377    }
378
379    #[test]
380    fn create_histogram_custom_bins() {
381        let mut source = source::default();
382        let gaussian = Gaussian::new(0.0, 2.0);
383        let mut sampler = Independent(&gaussian, &mut source);
384        let x = sampler.take(500).collect::<Vec<_>>();
385        let plot = histogram::<f64>(x, Some(100));
386        let mut figure = Figure::new();
387        figure.add_plot(plot);
388        figure.save("./examples/figures/histogram_custom_bins.png", None);
389    }
390
391    #[test]
392    fn create_histogram_to_b64() {
393        let mut source = source::default();
394        let gaussian = Gaussian::new(0.0, 2.0);
395        let mut sampler = Independent(&gaussian, &mut source);
396        let x = sampler.take(500).collect::<Vec<_>>();
397        let plot = histogram::<f64>(x, Some(100));
398        let mut figure = Figure::new();
399        figure.add_plot(plot);
400        figure.as_base64(None);
401    }
402
403    fn mean(numbers: &Vec<f64>) -> f64 {
404
405        let sum: f64 = numbers.iter().sum();
406
407        sum / numbers.len() as f64
408
409    }
410
411    #[test]
412    fn create_histogram_vertical_mean() {
413        let mut source = source::default();
414        let gaussian = Gaussian::new(0.0, 2.0);
415        let mut sampler = Independent(&gaussian, &mut source);
416        let x = sampler.take(500).collect::<Vec<_>>();
417        let plot = histogram::<f64>(x.clone(), Some(100));
418        let mut figure = Figure::new();
419        figure.add_plot(plot);
420        let mean = mean(&(x.clone()));
421        let mut mean_line_opts = LinePlotOptions::new();
422        mean_line_opts.lineStyle = Some(Dash);
423        mean_line_opts.colour = Some("black".to_string());
424        let mean_line = vertical_line(mean, Some(mean_line_opts));
425        figure.add_plot(mean_line);
426        figure.save("./examples/figures/histogram_mean_line.png", None);
427    }
428
429    #[test]
430    fn create_horizontal_line() {
431        let mut source = source::default();
432        let gaussian = Gaussian::new(0.0, 2.0);
433        let mut sampler = Independent(&gaussian, &mut source);
434        let x = (0..500).collect();
435        let y = sampler.take(500).collect::<Vec<_>>();
436        let plot = scatter_plot::<i32, f64>(x, y.clone(), None);
437        let mut figure = Figure::new();
438        figure.add_plot(plot);
439        let mean = mean(&(y.clone()));
440        let mut mean_line_opts = LinePlotOptions::new();
441        mean_line_opts.lineStyle = Some(Dash);
442        mean_line_opts.colour = Some("black".to_string());
443        let mean_line = horizontal_line(mean, Some(mean_line_opts));
444        figure.add_plot(mean_line);
445        print!("{:?}", figure.save("./examples/figures/horizontal_line.png", None));
446    }
447
448    #[test]
449    fn line_segment() {
450        let x = vec![1, 2, 3, 4];
451        let y = vec![0.1, 0.2, 0.5, 0.3];
452        let mut options = ScatterPlotOptions::new();
453        options.marker = Some(Markers::Diamond);
454        let lp = scatter_plot::<i32, f64>(x, y, Some(options));
455        let mut figure = Figure::new();
456        figure.add_plot(lp);
457        let mut line_opt = LinePlotOptions::new();
458        line_opt.colour = Some("red".to_string());
459        line_opt.lineStyle = Some(LineStyle::Dash);
460        figure.add_plot(line::<i32, f64>((1, 0.1), (4, 0.3), Some(line_opt)));
461        figure.save("./examples/figures/line_segment.png", None);
462        print!("{:?}", figure.script);
463
464    }
465
466}