plotpy/
plot.rs

1use super::{call_python3, generate_list_quoted, vector_to_array, AsVector, Legend, StrError, SuperTitleParams};
2use num_traits::Num;
3use std::ffi::OsStr;
4use std::fmt::Write;
5use std::fs::{self, File};
6use std::io::Write as IoWrite;
7use std::path::Path;
8
9const DEFAULT_PYTHON_EXE: &str = "python3";
10
11/// Defines the trait used by Plot to add graph entities
12pub trait GraphMaker {
13    /// Returns the text buffer with Python3 commands
14    fn get_buffer<'a>(&'a self) -> &'a String;
15
16    /// Clear the text buffer with Python commands
17    fn clear_buffer(&mut self);
18}
19
20/// Driver structure that calls Python
21///
22/// # Examples
23///
24/// ## Drawing curves
25///
26/// ```
27/// use plotpy::{linspace, Curve, Plot, StrError};
28///
29/// fn main() -> Result<(), StrError> {
30///     // generate (x,y) points
31///     let n = 11;
32///     let x = linspace(-1.0, 1.0, n);
33///     let y1 = x.clone();
34///     let y2: Vec<_> = x.iter().map(|v| f64::abs(*v)).collect();
35///     let y3: Vec<_> = x.iter().map(|v| f64::exp(1.0 + *v) - 1.0).collect();
36///     let y4: Vec<_> = x.iter().map(|v| f64::sqrt(1.0 + *v)).collect();
37///
38///     // configure and draw curves
39///     let mut curve1 = Curve::new();
40///     let mut curve2 = Curve::new();
41///     let mut curve3 = Curve::new();
42///     let mut curve4 = Curve::new();
43///     curve1.set_label("y = x");
44///     curve2.set_label("y = |x|").set_line_color("#cd0000");
45///     curve3.set_label("y = exp(1+x)-1").set_line_color("#e79955");
46///     curve4.set_label("y = sqrt(1+x)").set_line_color("#b566ab");
47///     curve1.draw(&x, &y1);
48///     curve2.draw(&x, &y2);
49///     curve3.draw(&x, &y3);
50///     curve4.draw(&x, &y4);
51///
52///     // configure plot
53///     let mut plot = Plot::new();
54///     plot.set_super_title("FOUR CURVES", None).set_gaps(0.35, 0.5);
55///
56///     // add curve to subplot
57///     plot.set_subplot(2, 2, 1)
58///         .set_title("first")
59///         .add(&curve1)
60///         .grid_labels_legend("x", "y")
61///         .set_equal_axes(true);
62///
63///     // add curve to subplot
64///     plot.set_subplot(2, 2, 2)
65///         .set_title("second")
66///         .add(&curve2)
67///         .grid_labels_legend("x", "y");
68///
69///     // add curve to subplot
70///     plot.set_subplot(2, 2, 3)
71///         .set_title("third")
72///         .add(&curve3)
73///         .set_range(-1.0, 1.0, 0.0, 6.0)
74///         .grid_labels_legend("x", "y");
75///
76///     // add curve to subplot
77///     plot.set_subplot(2, 2, 4)
78///         .set_title("fourth")
79///         .add(&curve4)
80///         .grid_labels_legend("x", "y");
81///
82///     // save figure
83///     plot.save("/tmp/plotpy/doc_tests/doc_plot.svg")?;
84///     Ok(())
85/// }
86/// ```
87///
88/// ![doc_plot.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_plot.svg)
89///
90/// ## Drawing an image from data and setting the ticks
91///
92/// See [Matplotlib example](https://matplotlib.org/stable/gallery/images_contours_and_fields/image_annotated_heatmap.html)
93///
94/// ```
95/// use plotpy::{Image, Plot, Text, StrError};
96///
97/// fn main() -> Result<(), StrError> {
98///     // data
99///     let vegetables = [
100///         "cucumber",
101///         "tomato",
102///         "lettuce",
103///         "asparagus",
104///         "potato",
105///         "wheat",
106///         "barley",
107///     ];
108///     let farmers = [
109///         "Farmer Joe",
110///         "Upland Bros.",
111///         "Smith Gardening",
112///         "Agrifun",
113///         "Organiculture",
114///         "BioGoods Ltd.",
115///         "Cornylee Corp.",
116///     ];
117///     let harvest = [
118///         [0.8, 2.4, 2.5, 3.9, 0.0, 4.0, 0.0],
119///         [2.4, 0.0, 4.0, 1.0, 2.7, 0.0, 0.0],
120///         [1.1, 2.4, 0.8, 4.3, 1.9, 4.4, 0.0],
121///         [0.6, 0.0, 0.3, 0.0, 3.1, 0.0, 0.0],
122///         [0.7, 1.7, 0.6, 2.6, 2.2, 6.2, 0.0],
123///         [1.3, 1.2, 0.0, 0.0, 0.0, 3.2, 5.1],
124///         [0.1, 2.0, 0.0, 1.4, 0.0, 1.9, 6.3],
125///     ];
126///
127///     // draw image
128///     let mut img = Image::new();
129///     img.draw(&harvest);
130///
131///     // set tick labels
132///     let mut plot = Plot::new();
133///     let ticks: Vec<_> = (0..vegetables.len()).into_iter().collect();
134///     plot.add(&img)
135///         .set_rotation_ticks_x(45.0)
136///         .set_ticks_x_labels(&ticks, &farmers)
137///         .set_ticks_y_labels(&ticks, &vegetables);
138///
139///     // add text
140///     let mut text = Text::new();
141///     text.set_color("white").set_align_horizontal("center");
142///     for i in 0..vegetables.len() {
143///         for j in 0..farmers.len() {
144///             text.draw(j as f64, i as f64, harvest[i][j].to_string().as_str());
145///         }
146///     }
147///     plot.add(&text);
148///
149///     // save figure
150///     plot.save("/tmp/plotpy/doc_tests/doc_plot_2.svg")?;
151///     Ok(())
152/// }
153/// ```
154///
155/// ![doc_plot_2.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_plot_2.svg)
156///
157/// See also integration tests in the [tests directory](https://github.com/cpmech/plotpy/tree/main/tests)
158pub struct Plot {
159    show_errors: bool,              // show python errors, if any
160    buffer: String,                 // buffer
161    save_tight: bool,               // option for savefig: enable bbox_inches='tight'
162    save_pad_inches: Option<f64>,   // option for savefig: add some padding when save_tight==true
163    save_transparent: Option<bool>, // option for savefig: make it transparent
164    python_exe: String,             // `python3` or simply `python` (e.g., on Windows)
165}
166
167impl Plot {
168    /// Creates new Plot object
169    pub fn new() -> Self {
170        Plot {
171            show_errors: false,
172            buffer: String::new(),
173            save_tight: true,
174            save_pad_inches: None,
175            save_transparent: None,
176            python_exe: DEFAULT_PYTHON_EXE.to_string(),
177        }
178    }
179
180    /// Adds new graph entity
181    pub fn add(&mut self, graph: &dyn GraphMaker) -> &mut Self {
182        self.buffer.push_str(graph.get_buffer());
183        self
184    }
185
186    /// Tells matplotlib to try to figure out the tight bounding box of the figure (default = true)
187    pub fn set_save_tight(&mut self, tight: bool) -> &mut Self {
188        self.save_tight = tight;
189        self
190    }
191
192    /// Sets the padding around the figure when the 'tight' layout is enabled during saving
193    ///
194    /// This option may circumvent *rare* problems when matplotlib fails to compute the best bounding box
195    /// (e.g., when the labels of 3D plots are ignored)
196    pub fn set_save_pad_inches(&mut self, pad_inches: f64) -> &mut Self {
197        self.save_pad_inches = Some(pad_inches);
198        self
199    }
200
201    /// Sets the transparency during saving
202    pub fn set_save_transparent(&mut self, transparent: bool) -> &mut Self {
203        self.save_transparent = Some(transparent);
204        self
205    }
206
207    /// Calls Python and saves the python script and figure
208    ///
209    /// # Input
210    ///
211    /// * `figure_path` -- may be a String, &str, or Path
212    ///
213    /// # Notes
214    ///
215    /// 1. You may want to call [Plot::set_show_errors()] to enable the
216    ///    display of Python errors (if any)
217    pub fn save<S>(&self, figure_path: &S) -> Result<(), StrError>
218    where
219        S: AsRef<OsStr> + ?Sized,
220    {
221        self.run(figure_path, false)
222    }
223
224    /// Calls Python, saves the python script and figure, and shows the plot window
225    ///
226    /// # Input
227    ///
228    /// * `figure_path` -- may be a String, &str, or Path
229    ///
230    /// # Notes
231    ///
232    /// 1. You may want to call [Plot::set_show_errors()] to enable the
233    ///    display of Python errors (if any)
234    /// 2. This function will also save a figure as [Plot::save()] does
235    pub fn show<S>(&self, figure_path: &S) -> Result<(), StrError>
236    where
237        S: AsRef<OsStr> + ?Sized,
238    {
239        self.run(figure_path, true)
240    }
241
242    /// Calls Python, saves the python script and figure, and shows the result in a Jupyter notebook
243    ///
244    /// **Important:** This function requires [evcxr_jupyter](https://github.com/evcxr/evcxr).
245    ///
246    /// # Input
247    ///
248    /// * `figure_path` -- may be a String, &str or Path
249    ///
250    /// # Notes
251    ///
252    /// 1. You may want to call [Plot::set_show_errors()] to enable the
253    ///    display of Python errors (if any)
254    /// 2. This function will also save a figure as [Plot::save()] does
255    /// 3. This method only works in a Jupyter Notebook
256    pub fn show_in_jupyter<S>(&self, figure_path: &S) -> Result<(), StrError>
257    where
258        S: AsRef<OsStr> + ?Sized,
259    {
260        self.run(figure_path, false)?;
261        let fig_path = Path::new(figure_path);
262        match fs::read_to_string(fig_path) {
263            Ok(figure) => println!("EVCXR_BEGIN_CONTENT text/html\n{}\nEVCXR_END_CONTENT", figure),
264            Err(_) => return Err("Failed to read the SVG figure, please check it."),
265        }
266        Ok(())
267    }
268
269    /// Clears the current axes
270    pub fn clear_current_axes(&mut self) -> &mut Self {
271        self.buffer.push_str("plt.gca().cla()\n");
272        self
273    }
274
275    /// Clears current figure
276    pub fn clear_current_figure(&mut self) -> &mut Self {
277        self.buffer.push_str("plt.clf()\n");
278        self
279    }
280
281    /// Adds legend to plot (see Legend for further options)
282    pub fn legend(&mut self) -> &mut Self {
283        let mut legend = Legend::new();
284        legend.draw();
285        self.add(&legend)
286    }
287
288    /// Adds grid and labels
289    pub fn grid_and_labels(&mut self, xlabel: &str, ylabel: &str) -> &mut Self {
290        write!(
291            &mut self.buffer,
292            "plt.gca().set_axisbelow(True)\n\
293             plt.grid(linestyle='--',color='grey',zorder=-1000)\n\
294             plt.gca().set_xlabel(r'{}')\n\
295             plt.gca().set_ylabel(r'{}')\n",
296            xlabel, ylabel
297        )
298        .unwrap();
299        self
300    }
301
302    /// Adds grid, labels, and legend
303    pub fn grid_labels_legend(&mut self, xlabel: &str, ylabel: &str) -> &mut Self {
304        write!(
305            &mut self.buffer,
306            "plt.gca().set_axisbelow(True)\n\
307             plt.grid(linestyle='--',color='grey',zorder=-1000)\n\
308             plt.gca().set_xlabel(r'{}')\n\
309             plt.gca().set_ylabel(r'{}')\n",
310            xlabel, ylabel
311        )
312        .unwrap();
313        self.legend()
314    }
315
316    /// Enables the display of python errors (if any)
317    pub fn set_show_errors(&mut self, option: bool) -> &mut Self {
318        self.show_errors = option;
319        self
320    }
321
322    /// Configures 3D subplots
323    ///
324    /// # Input
325    ///
326    /// * `row` -- number of rows in the subplot_3d grid
327    /// * `col` -- number of columns in the subplot_3d grid
328    /// * `index` -- activate current 3D subplot; **indices start at one** (1-based)
329    pub fn set_subplot_3d(&mut self, row: usize, col: usize, index: usize) -> &mut Self {
330        write!(&mut self.buffer, "\nsubplot_3d({},{},{})\n", row, col, index).unwrap();
331        self
332    }
333
334    /// Configures subplots
335    ///
336    /// # Input
337    ///
338    /// * `row` -- number of rows in the subplot grid
339    /// * `col` -- number of columns in the subplot grid
340    /// * `index` -- activate current subplot; **indices start at one** (1-based)
341    pub fn set_subplot(&mut self, row: usize, col: usize, index: usize) -> &mut Self {
342        write!(&mut self.buffer, "\nplt.subplot({},{},{})\n", row, col, index).unwrap();
343        self
344    }
345
346    /// Configures subplots using GridSpec
347    ///
348    /// # Input
349    ///
350    /// * `grid_handle` -- an identifier for GridSpec to be used later with [Plot::set_subplot_grid]
351    /// * `row` -- number of rows in the grid
352    /// * `col` -- number of columns in the grid
353    /// * `options` -- (may be empty) Comma separated options. Example `"wspace=0,hspace=0.35"`.
354    ///    See <https://matplotlib.org/stable/api/_as_gen/matplotlib.gridspec.GridSpec.html>
355    pub fn set_gridspec(&mut self, grid_handle: &str, row: usize, col: usize, options: &str) -> &mut Self {
356        write!(
357            &mut self.buffer,
358            "grid_{}=plt.GridSpec({},{},{})\n",
359            grid_handle, row, col, options
360        )
361        .unwrap();
362        self
363    }
364
365    /// Sets a subplot configured via GridSpec
366    ///
367    /// See function [Plot::set_gridspec]
368    ///
369    /// # Input
370    ///
371    /// * `grid_handle` -- an identifier for GridSpec defined by [Plot::set_gridspec]
372    /// * `i_range` -- the **zero-based** row index or range such as "0" or "0:2"
373    /// * `j_range` -- the **zero-based** column index or range such as "0" or "0:2"
374    pub fn set_subplot_grid(&mut self, grid_handle: &str, i_range: &str, j_range: &str) -> &mut Self {
375        write!(
376            &mut self.buffer,
377            "\nplt.subplot(grid_{}[{},{}])\n",
378            grid_handle, i_range, j_range
379        )
380        .unwrap();
381        self
382    }
383
384    /// Sets the rotation of ticks along the x-axis
385    pub fn set_rotation_ticks_x(&mut self, rotation: f64) -> &mut Self {
386        write!(
387            &mut self.buffer,
388            "plt.gca().tick_params(axis='x',rotation={})\n",
389            rotation
390        )
391        .unwrap();
392        self
393    }
394
395    /// Sets the rotation of ticks along the y-axis
396    pub fn set_rotation_ticks_y(&mut self, rotation: f64) -> &mut Self {
397        write!(
398            &mut self.buffer,
399            "plt.gca().tick_params(axis='y',rotation={})\n",
400            rotation
401        )
402        .unwrap();
403        self
404    }
405
406    /// Aligns the labels when using subplots
407    pub fn set_align_labels(&mut self) -> &mut Self {
408        write!(&mut self.buffer, "plt.gcf().align_labels()\n").unwrap();
409        self
410    }
411
412    /// Adds a title to the plot or sub-plot
413    ///
414    /// # Notes
415    ///
416    /// 1. Single quotation marks are replaced by the [UTF-8 Right Single Quotation Mark](https://www.compart.com/en/unicode/U+2019)
417    ///    because the Python script already uses the single quotation mark. Note that cannot use the raw notation `"""` because
418    ///    otherwise some TeX formula wouldn't work; notably the ones starting with `\v` such as `\varepsilon`
419    pub fn set_title(&mut self, title: &str) -> &mut Self {
420        let t = title.replace("'", "’");
421        write!(&mut self.buffer, "plt.title(r'{}')\n", t).unwrap();
422        self
423    }
424
425    /// Adds a title to all sub-plots
426    ///
427    /// # Notes
428    ///
429    /// 1. Single quotation marks are replaced by the [UTF-8 Right Single Quotation Mark](https://www.compart.com/en/unicode/U+2019)
430    ///    because the Python script already uses the single quotation mark. Note that cannot use the raw notation `"""` because
431    ///    otherwise some TeX formula wouldn't work; notably the ones starting with `\v` such as `\varepsilon`
432    /// 2. The TeX handling in the super-title string is more limited than in the title string
433    ///    because we cannot use Python's raw string notation (`r''`) here. The reason is that we want
434    ///    the super-title to be wrapped if it is too long and only non-raw strings can do that.
435    pub fn set_super_title(&mut self, title: &str, params: Option<&SuperTitleParams>) -> &mut Self {
436        let t = title.replace("'", "’").replace("\\n", "'+'\\n'+r'");
437        match params {
438            Some(p) => write!(&mut self.buffer, "st=plt.suptitle(r'{}'{})\n", t, p.options()).unwrap(),
439            None => write!(&mut self.buffer, "st=plt.suptitle(r'{}')\n", t).unwrap(),
440        }
441        write!(&mut self.buffer, "add_to_ea(st)\n").unwrap();
442        self
443    }
444
445    /// Sets the horizontal gap between subplots
446    pub fn set_horizontal_gap(&mut self, value: f64) -> &mut Self {
447        write!(&mut self.buffer, "plt.subplots_adjust(wspace={})\n", value).unwrap();
448        self
449    }
450
451    /// Sets the vertical gap between subplots
452    pub fn set_vertical_gap(&mut self, value: f64) -> &mut Self {
453        write!(&mut self.buffer, "plt.subplots_adjust(hspace={})\n", value).unwrap();
454        self
455    }
456
457    /// Sets the horizontal and vertical gap between subplots
458    pub fn set_gaps(&mut self, horizontal: f64, vertical: f64) -> &mut Self {
459        write!(
460            &mut self.buffer,
461            "plt.subplots_adjust(wspace={},hspace={})\n",
462            horizontal, vertical
463        )
464        .unwrap();
465        self
466    }
467
468    /// Sets same scale for both axes
469    pub fn set_equal_axes(&mut self, equal: bool) -> &mut Self {
470        if equal {
471            self.buffer.push_str("set_equal_axes()\n");
472        } else {
473            self.buffer.push_str("plt.gca().axes.set_aspect('auto')\n");
474        }
475        self
476    }
477
478    /// Sets the figure size in inches
479    pub fn set_figure_size_inches(&mut self, width: f64, height: f64) -> &mut Self {
480        write!(&mut self.buffer, "plt.gcf().set_size_inches({},{})\n", width, height).unwrap();
481        self
482    }
483
484    /// Sets the figure size in points
485    #[rustfmt::skip]
486    pub fn set_figure_size_points(&mut self, width: f64, height: f64) -> &mut Self {
487        const FACTOR: f64 = 72.27;
488        write!(&mut self.buffer, "plt.gcf().set_size_inches({},{})\n", width / FACTOR, height / FACTOR).unwrap();
489        self
490    }
491
492    /// Sets an option to hide the ticks along the x axis
493    pub fn set_hide_xticks(&mut self) -> &mut Self {
494        write!(&mut self.buffer, "plt.gca().set_xticklabels([])\n").unwrap();
495        self
496    }
497
498    /// Sets an option to hide the ticks along the y axis
499    pub fn set_hide_yticks(&mut self) -> &mut Self {
500        write!(&mut self.buffer, "plt.gca().set_yticklabels([])\n").unwrap();
501        self
502    }
503
504    /// Sets an option to hide the ticks along the z axis
505    pub fn set_hide_zticks(&mut self) -> &mut Self {
506        write!(&mut self.buffer, "plt.gca().set_zticklabels([])\n").unwrap();
507        self
508    }
509
510    /// Sets an option to hide/show all axes
511    pub fn set_hide_axes(&mut self, hide: bool) -> &mut Self {
512        let option = if hide { "off" } else { "on" };
513        write!(&mut self.buffer, "plt.axis('{}')\n", option).unwrap();
514        self
515    }
516
517    /// Sets an option to hide/show the 3D grid and panes (make them transparent)
518    ///
519    /// **Important:** This function must be called after adding all 3D surfaces/curves to the plot.
520    pub fn set_hide_3d_grid(&mut self, hide: bool) -> &mut Self {
521        if hide {
522            self.buffer.push_str(
523                "plt.gca().xaxis.pane.set_color((1.0, 1.0, 1.0, 0.0))\n\
524                 plt.gca().yaxis.pane.set_color((1.0, 1.0, 1.0, 0.0))\n\
525                 plt.gca().zaxis.pane.set_color((1.0, 1.0, 1.0, 0.0))\n\
526                 plt.gca().grid(False)\n",
527            );
528        } else {
529            self.buffer.push_str("plt.gca().grid(True)\n");
530        }
531        self
532    }
533
534    /// Sets axes limits
535    pub fn set_range_3d(&mut self, xmin: f64, xmax: f64, ymin: f64, ymax: f64, zmin: f64, zmax: f64) -> &mut Self {
536        write!(
537            &mut self.buffer,
538            "plt.gca().set_xlim({},{})\n\
539             plt.gca().set_ylim({},{})\n\
540             plt.gca().set_zlim({},{})\n",
541            xmin, xmax, ymin, ymax, zmin, zmax,
542        )
543        .unwrap();
544        self
545    }
546
547    /// Sets axes limits
548    pub fn set_range(&mut self, xmin: f64, xmax: f64, ymin: f64, ymax: f64) -> &mut Self {
549        write!(&mut self.buffer, "plt.axis([{},{},{},{}])\n", xmin, xmax, ymin, ymax).unwrap();
550        self
551    }
552
553    /// Sets axes limits from vector
554    pub fn set_range_from_vec(&mut self, limits: &[f64]) -> &mut Self {
555        write!(
556            &mut self.buffer,
557            "plt.axis([{},{},{},{}])\n",
558            limits[0], limits[1], limits[2], limits[3]
559        )
560        .unwrap();
561        self
562    }
563
564    /// Sets minimum x
565    pub fn set_xmin(&mut self, xmin: f64) -> &mut Self {
566        write!(&mut self.buffer, "plt.gca().set_xlim([{},None])\n", xmin).unwrap();
567        self
568    }
569
570    /// Sets maximum x
571    pub fn set_xmax(&mut self, xmax: f64) -> &mut Self {
572        write!(&mut self.buffer, "plt.gca().set_xlim([None,{}])\n", xmax).unwrap();
573        self
574    }
575
576    /// Sets minimum y
577    pub fn set_ymin(&mut self, ymin: f64) -> &mut Self {
578        write!(&mut self.buffer, "plt.gca().set_ylim([{},None])\n", ymin).unwrap();
579        self
580    }
581
582    /// Sets maximum y
583    pub fn set_ymax(&mut self, ymax: f64) -> &mut Self {
584        write!(&mut self.buffer, "plt.gca().set_ylim([None,{}])\n", ymax).unwrap();
585        self
586    }
587
588    /// Sets minimum z
589    pub fn set_zmin(&mut self, zmin: f64) -> &mut Self {
590        write!(&mut self.buffer, "plt.gca().set_zlim([{},None])\n", zmin).unwrap();
591        self
592    }
593
594    /// Sets maximum z
595    pub fn set_zmax(&mut self, zmax: f64) -> &mut Self {
596        write!(&mut self.buffer, "plt.gca().set_zlim([None,{}])\n", zmax).unwrap();
597        self
598    }
599
600    /// Sets x-range (i.e. limits)
601    pub fn set_xrange(&mut self, xmin: f64, xmax: f64) -> &mut Self {
602        write!(&mut self.buffer, "plt.gca().set_xlim([{},{}])\n", xmin, xmax).unwrap();
603        self
604    }
605
606    /// Sets y-range (i.e. limits)
607    pub fn set_yrange(&mut self, ymin: f64, ymax: f64) -> &mut Self {
608        write!(&mut self.buffer, "plt.gca().set_ylim([{},{}])\n", ymin, ymax).unwrap();
609        self
610    }
611
612    /// Sets z-range (i.e. limits)
613    pub fn set_zrange(&mut self, zmin: f64, zmax: f64) -> &mut Self {
614        write!(&mut self.buffer, "plt.gca().set_zlim([{},{}])\n", zmin, zmax).unwrap();
615        self
616    }
617
618    /// Sets number of ticks along x
619    pub fn set_num_ticks_x(&mut self, num: usize) -> &mut Self {
620        if num == 0 {
621            self.buffer.push_str("plt.gca().get_xaxis().set_ticks([])\n");
622        } else {
623            write!(
624                &mut self.buffer,
625                "plt.gca().get_xaxis().set_major_locator(tck.MaxNLocator({}))\n",
626                num
627            )
628            .unwrap();
629        }
630        self
631    }
632
633    /// Sets number of ticks along y
634    pub fn set_num_ticks_y(&mut self, num: usize) -> &mut Self {
635        if num == 0 {
636            self.buffer.push_str("plt.gca().get_yaxis().set_ticks([])\n");
637        } else {
638            write!(
639                &mut self.buffer,
640                "plt.gca().get_yaxis().set_major_locator(tck.MaxNLocator({}))\n",
641                num
642            )
643            .unwrap();
644        }
645        self
646    }
647
648    /// Sets number of ticks along z
649    pub fn set_num_ticks_z(&mut self, num: usize) -> &mut Self {
650        if num == 0 {
651            self.buffer.push_str("plt.gca().get_zaxis().set_ticks([])\n");
652        } else {
653            write!(
654                &mut self.buffer,
655                "plt.gca().get_zaxis().set_major_locator(tck.MaxNLocator({}))\n",
656                num
657            )
658            .unwrap();
659        }
660        self
661    }
662
663    /// Sets the number and format of x-ticks
664    ///
665    /// # Input
666    ///
667    /// * `major_every` -- step for major ticks (ignored if ≤ 0.0)
668    /// * `minor_every` -- step for major ticks (ignored if ≤ 0.0)
669    /// * `major_number_format` -- C-style number format for major ticks; e.g. "%.2f" (ignored if empty "")
670    ///    See [matplotlib FormatStrFormatter](https://matplotlib.org/stable/api/ticker_api.html#matplotlib.ticker.FormatStrFormatter)
671    #[rustfmt::skip]
672    pub fn set_ticks_x(&mut self, major_every: f64, minor_every: f64, major_number_format: &str) -> &mut Self {
673        if major_every > 0.0 {
674            write!(&mut self.buffer, "major_locator = tck.MultipleLocator({})\n", major_every).unwrap();
675            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / {}\n", major_every).unwrap();
676            write!(&mut self.buffer, "if n_ticks < major_locator.MAXTICKS * 0.9:\n").unwrap();
677            write!(&mut self.buffer, "    plt.gca().xaxis.set_major_locator(major_locator)\n").unwrap();
678        }
679        if minor_every > 0.0 {
680            write!(&mut self.buffer, "minor_locator = tck.MultipleLocator({})\n", minor_every).unwrap();
681            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / {}\n", minor_every).unwrap();
682            write!(&mut self.buffer, "if n_ticks < minor_locator.MAXTICKS * 0.9:\n").unwrap();
683            write!(&mut self.buffer, "    plt.gca().xaxis.set_minor_locator(minor_locator)\n").unwrap();
684        }
685        if major_number_format != "" {
686            write!(&mut self.buffer, "major_formatter = tck.FormatStrFormatter(r'{}')\n", major_number_format).unwrap();
687            write!(&mut self.buffer, "plt.gca().xaxis.set_major_formatter(major_formatter)\n").unwrap();
688        }
689        self
690    }
691
692    /// Sets the number and format of y-ticks
693    ///
694    /// # Input
695    ///
696    /// * `major_every` -- step for major ticks (ignored if ≤ 0.0)
697    /// * `minor_every` -- step for major ticks (ignored if ≤ 0.0)
698    /// * `major_number_format` -- C-style number format for major ticks; e.g. "%.2f" (ignored if empty "")
699    ///    See [matplotlib FormatStrFormatter](https://matplotlib.org/stable/api/ticker_api.html#matplotlib.ticker.FormatStrFormatter)
700    #[rustfmt::skip]
701    pub fn set_ticks_y(&mut self, major_every: f64, minor_every: f64, major_number_format: &str) -> &mut Self {
702        if major_every > 0.0 {
703            write!(&mut self.buffer, "major_locator = tck.MultipleLocator({})\n", major_every).unwrap();
704            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / {}\n", major_every).unwrap();
705            write!(&mut self.buffer, "if n_ticks < major_locator.MAXTICKS * 0.9:\n").unwrap();
706            write!(&mut self.buffer, "    plt.gca().yaxis.set_major_locator(major_locator)\n").unwrap();
707        }
708        if minor_every > 0.0 {
709            write!(&mut self.buffer, "minor_locator = tck.MultipleLocator({})\n", minor_every).unwrap();
710            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / {}\n", minor_every).unwrap();
711            write!(&mut self.buffer, "if n_ticks < minor_locator.MAXTICKS * 0.9:\n").unwrap();
712            write!(&mut self.buffer, "    plt.gca().yaxis.set_minor_locator(minor_locator)\n").unwrap();
713        }
714        if major_number_format != "" {
715            write!(&mut self.buffer, "major_formatter = tck.FormatStrFormatter(r'{}')\n", major_number_format).unwrap();
716            write!(&mut self.buffer, "plt.gca().yaxis.set_major_formatter(major_formatter)\n").unwrap();
717        }
718        self
719    }
720
721    /// Sets the ticks and labels along x
722    pub fn set_ticks_x_labels<'a, S, T, U>(&mut self, ticks: &'a T, labels: &[S]) -> &mut Self
723    where
724        S: std::fmt::Display,
725        T: AsVector<'a, U>,
726        U: 'a + std::fmt::Display + Num,
727    {
728        assert_eq!(ticks.vec_size(), labels.len());
729        vector_to_array(&mut self.buffer, "tx", ticks);
730        generate_list_quoted(&mut self.buffer, "lx", labels);
731        write!(
732            &mut self.buffer,
733            "plt.gca().set_xticks(tx)\nplt.gca().set_xticklabels(lx)\n"
734        )
735        .unwrap();
736        self
737    }
738
739    /// Sets the ticks and labels along y
740    pub fn set_ticks_y_labels<'a, S, T, U>(&mut self, ticks: &'a T, labels: &[S]) -> &mut Self
741    where
742        S: std::fmt::Display,
743        T: AsVector<'a, U>,
744        U: 'a + std::fmt::Display + Num,
745    {
746        assert_eq!(ticks.vec_size(), labels.len());
747        vector_to_array(&mut self.buffer, "ty", ticks);
748        generate_list_quoted(&mut self.buffer, "ly", labels);
749        write!(
750            &mut self.buffer,
751            "plt.gca().set_yticks(ty)\nplt.gca().set_yticklabels(ly)\n"
752        )
753        .unwrap();
754        self
755    }
756
757    /// Sets the fontsize of the ticks for the x-axis
758    pub fn set_ticks_x_fontsize(&mut self, fontsize: f64) -> &mut Self {
759        write!(
760            &mut self.buffer,
761            "plt.gca().tick_params(axis='x',labelsize={})\n",
762            fontsize,
763        )
764        .unwrap();
765        self
766    }
767
768    /// Sets the fontsize of the ticks for the y-axis
769    pub fn set_ticks_y_fontsize(&mut self, fontsize: f64) -> &mut Self {
770        write!(
771            &mut self.buffer,
772            "plt.gca().tick_params(axis='y',labelsize={})\n",
773            fontsize,
774        )
775        .unwrap();
776        self
777    }
778
779    /// Sets the fontsize of the ticks for the z-axis
780    pub fn set_ticks_z_fontsize(&mut self, fontsize: f64) -> &mut Self {
781        write!(
782            &mut self.buffer,
783            "plt.gca().tick_params(axis='z',labelsize={})\n",
784            fontsize,
785        )
786        .unwrap();
787        self
788    }
789
790    /// Writes the function multiple_of_pi_formatter to buffer
791    #[inline]
792    fn write_multiple_of_pi_formatter(&mut self) {
793        write!(
794            &mut self.buffer,
795            "def multiple_of_pi_formatter(x, pos):\n\
796             \x20\x20\x20\x20den = 2\n\
797             \x20\x20\x20\x20num = int(np.rint(den*x/np.pi))\n\
798             \x20\x20\x20\x20com = np.gcd(num,den)\n\
799             \x20\x20\x20\x20(num,den) = (int(num/com),int(den/com))\n\
800             \x20\x20\x20\x20if den==1:\n\
801             \x20\x20\x20\x20\x20\x20\x20\x20if num==0: return r'$0$'\n\
802             \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\pi$'\n\
803             \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$-\\pi$'\n\
804             \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$%s\\pi$'%num\n\
805             \x20\x20\x20\x20else:\n\
806             \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\frac{{\\pi}}{{%s}}$'%den\n\
807             \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$\\frac{{-\\pi}}{{%s}}$'%den\n\
808             \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$\\frac{{%s\\pi}}{{%s}}$'%(num,den)\n"
809        )
810        .unwrap();
811    }
812
813    /// Sets the x-ticks to multiples of pi
814    ///
815    /// # Input
816    ///
817    /// * `minor_every` -- step for major ticks (ignored if ≤ 0.0). Example `PI / 12.0`
818    ///
819    /// **Note:** This function sets the major ticks as `PI / 2.0`.
820    #[rustfmt::skip]
821    pub fn set_ticks_x_multiple_of_pi(&mut self, minor_every: f64) -> &mut Self {
822        write!(&mut self.buffer, "major_locator = tck.MultipleLocator(np.pi/2.0)\n").unwrap();
823        write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / (np.pi/2.0)\n").unwrap();
824        write!(&mut self.buffer, "if n_ticks < major_locator.MAXTICKS * 0.9:\n").unwrap();
825        write!(&mut self.buffer, "    plt.gca().xaxis.set_major_locator(major_locator)\n").unwrap();
826        if minor_every > 0.0 {
827            write!(&mut self.buffer, "minor_locator = tck.MultipleLocator({})\n", minor_every).unwrap();
828            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / {}\n", minor_every).unwrap();
829            write!(&mut self.buffer, "if n_ticks < minor_locator.MAXTICKS * 0.9:\n").unwrap();
830            write!(&mut self.buffer, "    plt.gca().xaxis.set_minor_locator(minor_locator)\n").unwrap();
831        }
832        self.write_multiple_of_pi_formatter();
833        write!(&mut self.buffer, "major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n").unwrap();
834        write!(&mut self.buffer, "plt.gca().xaxis.set_major_formatter(major_formatter)\n").unwrap();
835        self
836    }
837
838    /// Sets the y-ticks to multiples of pi
839    ///
840    /// # Input
841    ///
842    /// * `minor_every` -- step for major ticks (ignored if ≤ 0.0). Example `PI / 12.0`
843    ///
844    /// **Note:** This function sets the major ticks as `PI / 2.0`.
845    #[rustfmt::skip]
846    pub fn set_ticks_y_multiple_of_pi(&mut self, minor_every: f64) -> &mut Self {
847        write!(&mut self.buffer, "major_locator = tck.MultipleLocator(np.pi/2.0)\n").unwrap();
848        write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / (np.pi/2.0)\n").unwrap();
849        write!(&mut self.buffer, "if n_ticks < major_locator.MAXTICKS * 0.9:\n").unwrap();
850        write!(&mut self.buffer, "    plt.gca().yaxis.set_major_locator(major_locator)\n").unwrap();
851        if minor_every > 0.0 {
852            write!(&mut self.buffer, "minor_locator = tck.MultipleLocator({})\n", minor_every).unwrap();
853            write!(&mut self.buffer, "n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / {}\n", minor_every).unwrap();
854            write!(&mut self.buffer, "if n_ticks < minor_locator.MAXTICKS * 0.9:\n").unwrap();
855            write!(&mut self.buffer, "    plt.gca().yaxis.set_minor_locator(minor_locator)\n").unwrap();
856        }
857        self.write_multiple_of_pi_formatter();
858        write!(&mut self.buffer, "major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n").unwrap();
859        write!(&mut self.buffer, "plt.gca().yaxis.set_major_formatter(major_formatter)\n").unwrap();
860        self
861    }
862
863    /// Sets a log10 x-scale
864    ///
865    /// # Note
866    ///
867    /// `set_log_x(true)` must be called before adding curves.
868    pub fn set_log_x(&mut self, log: bool) -> &mut Self {
869        if log {
870            self.buffer.push_str("plt.gca().set_xscale('log')\n");
871        } else {
872            self.buffer.push_str("plt.gca().set_xscale('linear')\n");
873        }
874        self
875    }
876
877    /// Sets a log10 y-scale
878    ///
879    /// # Note
880    ///
881    /// `set_log_y(true)` must be called before adding curves.
882    pub fn set_log_y(&mut self, log: bool) -> &mut Self {
883        if log {
884            self.buffer.push_str("plt.gca().set_yscale('log')\n");
885        } else {
886            self.buffer.push_str("plt.gca().set_yscale('linear')\n");
887        }
888        self
889    }
890
891    /// Sets the label for the x-axis
892    pub fn set_label_x(&mut self, label: &str) -> &mut Self {
893        write!(&mut self.buffer, "plt.gca().set_xlabel(r'{}')\n", label).unwrap();
894        self
895    }
896
897    /// Sets the label for the y-axis
898    pub fn set_label_y(&mut self, label: &str) -> &mut Self {
899        write!(&mut self.buffer, "plt.gca().set_ylabel(r'{}')\n", label).unwrap();
900        self
901    }
902
903    /// Sets the label for the z-axis
904    pub fn set_label_z(&mut self, label: &str) -> &mut Self {
905        write!(&mut self.buffer, "plt.gca().set_zlabel(r'{}')\n", label).unwrap();
906        self
907    }
908
909    /// Sets the fontsize of the label for the x-axis
910    pub fn set_label_x_fontsize(&mut self, fontsize: f64) -> &mut Self {
911        write!(&mut self.buffer, "plt.gca().xaxis.label.set_fontsize({})\n", fontsize,).unwrap();
912        self
913    }
914
915    /// Sets the fontsize of the label for the y-axis
916    pub fn set_label_y_fontsize(&mut self, fontsize: f64) -> &mut Self {
917        write!(&mut self.buffer, "plt.gca().yaxis.label.set_fontsize({})\n", fontsize,).unwrap();
918        self
919    }
920
921    /// Sets the fontsize of the label for the z-axis
922    pub fn set_label_z_fontsize(&mut self, fontsize: f64) -> &mut Self {
923        write!(&mut self.buffer, "plt.gca().zaxis.label.set_fontsize({})\n", fontsize,).unwrap();
924        self
925    }
926
927    /// Sets the color of the label for the x-axis
928    pub fn set_label_x_color(&mut self, color: &str) -> &mut Self {
929        write!(
930            &mut self.buffer,
931            "plt.gca().xaxis.label.set_color('{}')\n\
932             plt.gca().tick_params(axis='x',labelcolor='{}')\n",
933            color, color
934        )
935        .unwrap();
936        self
937    }
938
939    /// Sets the color of the label for the y-axis
940    pub fn set_label_y_color(&mut self, color: &str) -> &mut Self {
941        write!(
942            &mut self.buffer,
943            "plt.gca().yaxis.label.set_color('{}')\n\
944             plt.gca().tick_params(axis='y',labelcolor='{}')\n",
945            color, color
946        )
947        .unwrap();
948        self
949    }
950
951    /// Sets the color of the label for the z-axis
952    pub fn set_label_z_color(&mut self, color: &str) -> &mut Self {
953        write!(
954            &mut self.buffer,
955            "plt.gca().zaxis.label.set_color('{}')\n\
956             plt.gca().tick_params(axis='z',labelcolor='{}')\n",
957            color, color
958        )
959        .unwrap();
960        self
961    }
962
963    /// Sets the label for the y-axis on a twin-x graph
964    ///
965    /// **Warning:** The curve with a twin-x graph must be added first
966    pub fn set_label_y_twinx(&mut self, label: &str) -> &mut Self {
967        write!(
968            &mut self.buffer,
969            "if 'ax_twinx' in locals():\n\
970             \x20\x20\x20\x20ax_twinx.set_ylabel(r'{}')\n",
971            label,
972        )
973        .unwrap();
974        self
975    }
976
977    /// Sets the color of the label for the y-axis on a twin-x graph
978    ///
979    /// **Warning:** The curve with a twin-x graph must be added first
980    pub fn set_label_y_twinx_color(&mut self, color: &str) -> &mut Self {
981        write!(
982            &mut self.buffer,
983            "if 'ax_twinx' in locals():\n\
984             \x20\x20\x20\x20ax_twinx.yaxis.label.set_color('{}')\n\
985             \x20\x20\x20\x20ax_twinx.tick_params(axis='y',labelcolor='{}')\n",
986            color, color
987        )
988        .unwrap();
989        self
990    }
991
992    /// Sets the label for the x-axis and the padding
993    pub fn set_label_x_and_pad(&mut self, label: &str, pad: f64) -> &mut Self {
994        write!(
995            &mut self.buffer,
996            "plt.gca().set_xlabel(r'{}',labelpad={})\n",
997            label, pad
998        )
999        .unwrap();
1000        self
1001    }
1002
1003    /// Sets the label for the y-axis and the padding
1004    pub fn set_label_y_and_pad(&mut self, label: &str, pad: f64) -> &mut Self {
1005        write!(
1006            &mut self.buffer,
1007            "plt.gca().set_ylabel(r'{}',labelpad={})\n",
1008            label, pad
1009        )
1010        .unwrap();
1011        self
1012    }
1013
1014    /// Sets the label for the z-axis and the padding
1015    pub fn set_label_z_and_pad(&mut self, label: &str, pad: f64) -> &mut Self {
1016        write!(
1017            &mut self.buffer,
1018            "plt.gca().set_zlabel(r'{}',labelpad={})\n",
1019            label, pad
1020        )
1021        .unwrap();
1022        self
1023    }
1024
1025    /// Sets the labels for the x and y axes
1026    pub fn set_labels(&mut self, xlabel: &str, ylabel: &str) -> &mut Self {
1027        write!(
1028            &mut self.buffer,
1029            "plt.gca().set_xlabel(r'{}')\nplt.gca().set_ylabel(r'{}')\n",
1030            xlabel, ylabel
1031        )
1032        .unwrap();
1033        self
1034    }
1035
1036    /// Sets the labels for the x, y, and z axes
1037    pub fn set_labels_3d(&mut self, xlabel: &str, ylabel: &str, zlabel: &str) -> &mut Self {
1038        write!(
1039            &mut self.buffer,
1040            "plt.gca().set_xlabel(r'{}')\nplt.gca().set_ylabel(r'{}')\nplt.gca().set_zlabel(r'{}')\n",
1041            xlabel, ylabel, zlabel
1042        )
1043        .unwrap();
1044        self
1045    }
1046
1047    /// Sets inverted x-axis
1048    pub fn set_inv_x(&mut self) -> &mut Self {
1049        write!(&mut self.buffer, "plt.gca().invert_xaxis()\n").unwrap();
1050        self
1051    }
1052
1053    /// Sets inverted y-axis
1054    pub fn set_inv_y(&mut self) -> &mut Self {
1055        write!(&mut self.buffer, "plt.gca().invert_yaxis()\n").unwrap();
1056        self
1057    }
1058
1059    /// Sets camera in 3d graph. Sets the elevation and azimuth of the axes.
1060    ///
1061    /// # Input
1062    ///
1063    /// * `elevation` -- is the elevation angle in the z plane
1064    /// * `azimuth` -- is the azimuth angle in the x,y plane
1065    ///
1066    /// | view plane | elev | azim |
1067    /// |------------|------|------|
1068    /// | XY         | 90   | -90  |
1069    /// | XZ         | 0    | -90  |
1070    /// | YZ         | 0    | 0    |
1071    /// | -XY        | -90  | 90   |
1072    /// | -XZ        | 0    | 90   |
1073    /// | -YZ        | 0    | 180  |
1074    ///
1075    /// See <https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.view_init.html>
1076    pub fn set_camera(&mut self, elevation: f64, azimuth: f64) -> &mut Self {
1077        write!(
1078            &mut self.buffer,
1079            "plt.gca().view_init(elev={},azim={})\n",
1080            elevation, azimuth
1081        )
1082        .unwrap();
1083        self
1084    }
1085
1086    /// Sets option to hide (or show) frame borders
1087    pub fn set_frame_border(&mut self, left: bool, right: bool, bottom: bool, top: bool) -> &mut Self {
1088        if left {
1089            self.buffer.push_str("plt.gca().spines['left'].set_visible(True)\n");
1090        } else {
1091            self.buffer.push_str("plt.gca().spines['left'].set_visible(False)\n");
1092        }
1093        if right {
1094            self.buffer.push_str("plt.gca().spines['right'].set_visible(True)\n");
1095        } else {
1096            self.buffer.push_str("plt.gca().spines['right'].set_visible(False)\n");
1097        }
1098        if bottom {
1099            self.buffer.push_str("plt.gca().spines['bottom'].set_visible(True)\n");
1100        } else {
1101            self.buffer.push_str("plt.gca().spines['bottom'].set_visible(False)\n");
1102        }
1103        if top {
1104            self.buffer.push_str("plt.gca().spines['top'].set_visible(True)\n");
1105        } else {
1106            self.buffer.push_str("plt.gca().spines['top'].set_visible(False)\n");
1107        }
1108        self
1109    }
1110
1111    /// Sets visibility of all frame borders
1112    pub fn set_frame_borders(&mut self, show_all: bool) -> &mut Self {
1113        self.set_frame_border(show_all, show_all, show_all, show_all)
1114    }
1115
1116    /// Draws an infinite horizontal line at y
1117    pub fn set_horiz_line(&mut self, y: f64, color: &str, line_style: &str, line_width: f64) -> &mut Self {
1118        let opt = format!(",color='{}',linestyle='{}',linewidth={}", color, line_style, line_width);
1119        self.buffer.push_str(&format!("plt.axhline({}{})\n", y, &opt));
1120        self
1121    }
1122
1123    /// Draws an infinite vertical line at x
1124    pub fn set_vert_line(&mut self, x: f64, color: &str, line_style: &str, line_width: f64) -> &mut Self {
1125        let opt = format!(",color='{}',linestyle='{}',linewidth={}", color, line_style, line_width);
1126        self.buffer.push_str(&format!("plt.axvline({}{})\n", x, &opt));
1127        self
1128    }
1129
1130    /// Draws infinite horizontal and vertical lines at (x, y)
1131    pub fn set_cross(&mut self, x: f64, y: f64, color: &str, line_style: &str, line_width: f64) -> &mut Self {
1132        let opt = format!(",color='{}',linestyle='{}',linewidth={}", color, line_style, line_width);
1133        self.buffer
1134            .push_str(&format!("plt.axhline({}{})\nplt.axvline({}{})\n", y, &opt, x, &opt));
1135        self
1136    }
1137
1138    /// Writes extra python commands
1139    pub fn extra(&mut self, commands: &str) -> &mut Self {
1140        self.buffer.write_str(commands).unwrap();
1141        self
1142    }
1143
1144    /// Sets the Python3 executable command
1145    ///
1146    /// The default is `python3`
1147    pub fn set_python_exe(&mut self, python_exe: &str) -> &mut Self {
1148        self.python_exe = python_exe.to_string();
1149        self
1150    }
1151
1152    /// Run python
1153    fn run<S>(&self, figure_path: &S, show: bool) -> Result<(), StrError>
1154    where
1155        S: AsRef<OsStr> + ?Sized,
1156    {
1157        // update commands
1158        let fig_path = Path::new(figure_path);
1159        let mut txt = "plt.savefig(fn".to_string();
1160        if self.save_tight {
1161            txt.push_str(",bbox_inches='tight',bbox_extra_artists=EXTRA_ARTISTS");
1162        }
1163        if let Some(pad) = self.save_pad_inches {
1164            txt.push_str(format!(",pad_inches={}", pad).as_str());
1165        }
1166        if let Some(transparent) = self.save_transparent {
1167            if transparent {
1168                txt.push_str(",transparent=True");
1169            }
1170        }
1171        txt.push_str(")\n");
1172        if show {
1173            txt.push_str("\nplt.show()\n");
1174        };
1175        let commands = format!("{}\nfn=r'{}'\n{}", self.buffer, fig_path.to_string_lossy(), txt);
1176
1177        // call python
1178        let mut path = Path::new(figure_path).to_path_buf();
1179        path.set_extension("py");
1180        let output = call_python3(&self.python_exe, &commands, &path)?;
1181
1182        // handle error => write log file
1183        if output != "" {
1184            let mut log_path = Path::new(figure_path).to_path_buf();
1185            log_path.set_extension("log");
1186            let mut log_file = File::create(log_path).map_err(|_| "cannot create log file")?;
1187            log_file
1188                .write_all(output.as_bytes())
1189                .map_err(|_| "cannot write to log file")?;
1190            if self.show_errors {
1191                println!("{}", output);
1192            }
1193            return Err("python3 failed; please see the log file");
1194        }
1195        Ok(())
1196    }
1197}
1198
1199////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1200
1201#[cfg(test)]
1202mod tests {
1203    use crate::SuperTitleParams;
1204
1205    use super::Plot;
1206    use std::fs::File;
1207    use std::io::{BufRead, BufReader};
1208    use std::path::Path;
1209
1210    const OUT_DIR: &str = "/tmp/plotpy/unit_tests";
1211
1212    #[test]
1213    fn new_plot_works() {
1214        let plot = Plot::new();
1215        assert_eq!(plot.buffer.len(), 0);
1216    }
1217
1218    #[test]
1219    fn save_works() {
1220        let plot = Plot::new();
1221        assert_eq!(plot.buffer.len(), 0);
1222        let path = Path::new(OUT_DIR).join("save_works.svg");
1223        plot.save(&path).unwrap();
1224        let file = File::open(&path).map_err(|_| "cannot open file").unwrap();
1225        let buffered = BufReader::new(file);
1226        let lines_iter = buffered.lines();
1227        assert!(lines_iter.count() > 20);
1228    }
1229
1230    #[test]
1231    fn show_in_jupyter_works() {
1232        let plot = Plot::new();
1233        let path = Path::new(OUT_DIR).join("show_works.svg");
1234        plot.save(&path).unwrap();
1235        let result = plot.show_in_jupyter(&path).unwrap();
1236        assert_eq!(result, ());
1237    }
1238
1239    #[test]
1240    fn save_str_works() {
1241        let plot = Plot::new();
1242        assert_eq!(plot.buffer.len(), 0);
1243        let path = "/tmp/plotpy/unit_tests/save_str_works.svg";
1244        plot.save(&path).unwrap();
1245        let file = File::open(&path).map_err(|_| "cannot open file").unwrap();
1246        let buffered = BufReader::new(file);
1247        let lines_iter = buffered.lines();
1248        assert!(lines_iter.count() > 20);
1249    }
1250
1251    #[test]
1252    fn show_errors_works() {
1253        const WRONG: usize = 0;
1254        let mut plot = Plot::new();
1255        plot.set_show_errors(true);
1256        plot.set_subplot(1, 1, WRONG);
1257        let path = Path::new(OUT_DIR).join("show_errors_works.svg");
1258        assert_eq!(plot.save(&path).err(), Some("python3 failed; please see the log file"));
1259    }
1260
1261    #[test]
1262    fn subplot_3d_works() {
1263        let mut plot = Plot::new();
1264        plot.set_subplot_3d(3, 2, 1);
1265        let b: &str = "\nsubplot_3d(3,2,1)\n";
1266        assert_eq!(plot.buffer, b);
1267    }
1268
1269    #[test]
1270    fn subplot_functions_work() {
1271        let mut plot = Plot::new();
1272        plot.set_super_title("all subplots", None)
1273            .set_subplot(2, 2, 1)
1274            .set_horizontal_gap(0.1)
1275            .set_vertical_gap(0.2)
1276            .set_gaps(0.3, 0.4);
1277        let b: &str = "st=plt.suptitle(r'all subplots')\n\
1278                       add_to_ea(st)\n\
1279                       \nplt.subplot(2,2,1)\n\
1280                         plt.subplots_adjust(wspace=0.1)\n\
1281                         plt.subplots_adjust(hspace=0.2)\n\
1282                         plt.subplots_adjust(wspace=0.3,hspace=0.4)\n";
1283        assert_eq!(plot.buffer, b);
1284    }
1285
1286    #[test]
1287    fn super_title_works() {
1288        let mut params = SuperTitleParams::new();
1289        params
1290            .set_x(123.3)
1291            .set_y(456.7)
1292            .set_align_horizontal("left")
1293            .set_align_vertical("bottom")
1294            .set_fontsize(12.0)
1295            .set_fontweight(10.0);
1296        let mut plot = Plot::new();
1297        plot.set_super_title("all subplots", Some(&params));
1298        let b: &str =
1299            "st=plt.suptitle(r'all subplots',x=123.3,y=456.7,ha='left',va='bottom',fontsize=12,fontweight=10)\n\
1300                       add_to_ea(st)\n";
1301        assert_eq!(plot.buffer, b);
1302    }
1303
1304    #[test]
1305    fn grid_functions_work() {
1306        let mut plot = Plot::new();
1307        plot.grid_and_labels("xx", "yy").grid_labels_legend("xx", "yy").legend();
1308        let b: &str = "plt.gca().set_axisbelow(True)\n\
1309                       plt.grid(linestyle='--',color='grey',zorder=-1000)\n\
1310                       plt.gca().set_xlabel(r'xx')\n\
1311                       plt.gca().set_ylabel(r'yy')\n\
1312                       plt.gca().set_axisbelow(True)\n\
1313                       plt.grid(linestyle='--',color='grey',zorder=-1000)\n\
1314                       plt.gca().set_xlabel(r'xx')\n\
1315                       plt.gca().set_ylabel(r'yy')\n\
1316                       h,l=plt.gca().get_legend_handles_labels()\n\
1317                       if len(h)>0 and len(l)>0:\n\
1318                       \x20\x20\x20\x20leg=plt.legend(handlelength=3,ncol=1,loc='best')\n\
1319                       \x20\x20\x20\x20add_to_ea(leg)\n\
1320                       h,l=plt.gca().get_legend_handles_labels()\n\
1321                       if len(h)>0 and len(l)>0:\n\
1322                       \x20\x20\x20\x20leg=plt.legend(handlelength=3,ncol=1,loc='best')\n\
1323                       \x20\x20\x20\x20add_to_ea(leg)\n";
1324        assert_eq!(plot.buffer, b);
1325    }
1326
1327    #[test]
1328    fn set_title_and_super_title_handle_quotes() {
1329        let mut p1 = Plot::new();
1330        p1.set_title("Without Quotes").set_super_title("Hi", None);
1331        let b: &str = "plt.title(r'Without Quotes')\n\
1332                       st=plt.suptitle(r'Hi')\n\
1333                       add_to_ea(st)\n";
1334        assert_eq!(p1.buffer, b);
1335
1336        let mut p2 = Plot::new();
1337        p2.set_title("Developer's Plot").set_super_title("Dev's", None);
1338        let b: &str = "plt.title(r'Developer’s Plot')\n\
1339                       st=plt.suptitle(r'Dev’s')\n\
1340                       add_to_ea(st)\n";
1341        assert_eq!(p2.buffer, b);
1342
1343        let mut p3 = Plot::new();
1344        p3.set_title("\"Look at This\"").set_super_title("\"Dev's\"", None);
1345        let b: &str = "plt.title(r'\"Look at This\"')\n\
1346                       st=plt.suptitle(r'\"Dev’s\"')\n\
1347                       add_to_ea(st)\n";
1348        assert_eq!(p3.buffer, b);
1349    }
1350
1351    #[test]
1352    fn set_super_title_handles_math_and_newline() {
1353        let mut plot = Plot::new();
1354        plot.set_super_title("$\\alpha$\\n$\\beta$", None);
1355        let b: &str = "st=plt.suptitle(r'$\\alpha$'+'\\n'+r'$\\beta$')\n\
1356                       add_to_ea(st)\n";
1357        assert_eq!(plot.buffer, b);
1358    }
1359
1360    #[test]
1361    fn set_functions_work() {
1362        let mut plot = Plot::new();
1363        plot.set_show_errors(true)
1364            .set_equal_axes(true)
1365            .set_equal_axes(false)
1366            .set_hide_axes(true)
1367            .set_range_3d(-1.0, 1.0, -1.0, 1.0, -1.0, 1.0)
1368            .set_range(-1.0, 1.0, -1.0, 1.0)
1369            .set_range_from_vec(&[0.0, 1.0, 0.0, 1.0])
1370            .set_xrange(-80.0, 800.0)
1371            .set_yrange(13.0, 130.0)
1372            .set_zrange(44.0, 444.0)
1373            .set_xmin(-3.0)
1374            .set_xmax(8.0)
1375            .set_ymin(-7.0)
1376            .set_ymax(33.0)
1377            .set_zmin(12.0)
1378            .set_zmax(34.0)
1379            .set_num_ticks_x(0)
1380            .set_num_ticks_x(8)
1381            .set_num_ticks_y(0)
1382            .set_num_ticks_y(5)
1383            .set_log_x(true)
1384            .set_log_y(true)
1385            .set_log_x(false)
1386            .set_log_y(false)
1387            .set_label_x("x-label")
1388            .set_label_y("y-label")
1389            .set_labels("x", "y")
1390            .set_camera(1.0, 10.0)
1391            .set_ticks_x(1.5, 0.5, "%.2f")
1392            .set_ticks_y(0.5, 0.1, "%g")
1393            .set_figure_size_inches(2.0, 2.0)
1394            .set_figure_size_points(7227.0, 7227.0)
1395            .clear_current_axes()
1396            .clear_current_figure();
1397        let b: &str = "set_equal_axes()\n\
1398                       plt.gca().axes.set_aspect('auto')\n\
1399                       plt.axis('off')\n\
1400                       plt.gca().set_xlim(-1,1)\n\
1401                       plt.gca().set_ylim(-1,1)\n\
1402                       plt.gca().set_zlim(-1,1)\n\
1403                       plt.axis([-1,1,-1,1])\n\
1404                       plt.axis([0,1,0,1])\n\
1405                       plt.gca().set_xlim([-80,800])\n\
1406                       plt.gca().set_ylim([13,130])\n\
1407                       plt.gca().set_zlim([44,444])\n\
1408                       plt.gca().set_xlim([-3,None])\n\
1409                       plt.gca().set_xlim([None,8])\n\
1410                       plt.gca().set_ylim([-7,None])\n\
1411                       plt.gca().set_ylim([None,33])\n\
1412                       plt.gca().set_zlim([12,None])\n\
1413                       plt.gca().set_zlim([None,34])\n\
1414                       plt.gca().get_xaxis().set_ticks([])\n\
1415                       plt.gca().get_xaxis().set_major_locator(tck.MaxNLocator(8))\n\
1416                       plt.gca().get_yaxis().set_ticks([])\n\
1417                       plt.gca().get_yaxis().set_major_locator(tck.MaxNLocator(5))\n\
1418                       plt.gca().set_xscale('log')\n\
1419                       plt.gca().set_yscale('log')\n\
1420                       plt.gca().set_xscale('linear')\n\
1421                       plt.gca().set_yscale('linear')\n\
1422                       plt.gca().set_xlabel(r'x-label')\n\
1423                       plt.gca().set_ylabel(r'y-label')\n\
1424                       plt.gca().set_xlabel(r'x')\n\
1425                       plt.gca().set_ylabel(r'y')\n\
1426                       plt.gca().view_init(elev=1,azim=10)\n\
1427                       major_locator = tck.MultipleLocator(1.5)\n\
1428                       n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / 1.5\n\
1429                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1430                       \x20\x20\x20\x20plt.gca().xaxis.set_major_locator(major_locator)\n\
1431                       minor_locator = tck.MultipleLocator(0.5)\n\
1432                       n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / 0.5\n\
1433                       if n_ticks < minor_locator.MAXTICKS * 0.9:\n\
1434                       \x20\x20\x20\x20plt.gca().xaxis.set_minor_locator(minor_locator)\n\
1435                       major_formatter = tck.FormatStrFormatter(r'%.2f')\n\
1436                       plt.gca().xaxis.set_major_formatter(major_formatter)\n\
1437                       major_locator = tck.MultipleLocator(0.5)\n\
1438                       n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / 0.5\n\
1439                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1440                       \x20\x20\x20\x20plt.gca().yaxis.set_major_locator(major_locator)\n\
1441                       minor_locator = tck.MultipleLocator(0.1)\n\
1442                       n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / 0.1\n\
1443                       if n_ticks < minor_locator.MAXTICKS * 0.9:\n\
1444                       \x20\x20\x20\x20plt.gca().yaxis.set_minor_locator(minor_locator)\n\
1445                       major_formatter = tck.FormatStrFormatter(r'%g')\n\
1446                       plt.gca().yaxis.set_major_formatter(major_formatter)\n\
1447                       plt.gcf().set_size_inches(2,2)\n\
1448                       plt.gcf().set_size_inches(100,100)\n\
1449                       plt.gca().cla()\n\
1450                       plt.clf()\n";
1451        assert_eq!(plot.buffer, b);
1452        assert_eq!(plot.show_errors, true);
1453    }
1454
1455    #[test]
1456    fn set_functions_work_2() {
1457        let mut plot = Plot::new();
1458        plot.set_ticks_x_multiple_of_pi(0.0);
1459        let b: &str = "major_locator = tck.MultipleLocator(np.pi/2.0)\n\
1460                       n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / (np.pi/2.0)\n\
1461                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1462                       \x20\x20\x20\x20plt.gca().xaxis.set_major_locator(major_locator)\n\
1463                       def multiple_of_pi_formatter(x, pos):\n\
1464                       \x20\x20\x20\x20den = 2\n\
1465                       \x20\x20\x20\x20num = int(np.rint(den*x/np.pi))\n\
1466                       \x20\x20\x20\x20com = np.gcd(num,den)\n\
1467                       \x20\x20\x20\x20(num,den) = (int(num/com),int(den/com))\n\
1468                       \x20\x20\x20\x20if den==1:\n\
1469                       \x20\x20\x20\x20\x20\x20\x20\x20if num==0: return r'$0$'\n\
1470                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\pi$'\n\
1471                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$-\\pi$'\n\
1472                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$%s\\pi$'%num\n\
1473                       \x20\x20\x20\x20else:\n\
1474                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\frac{\\pi}{%s}$'%den\n\
1475                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$\\frac{-\\pi}{%s}$'%den\n\
1476                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$\\frac{%s\\pi}{%s}$'%(num,den)\n\
1477                       major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n\
1478                       plt.gca().xaxis.set_major_formatter(major_formatter)\n";
1479        assert_eq!(plot.buffer, b);
1480
1481        let mut plot = Plot::new();
1482        plot.set_ticks_y_multiple_of_pi(0.0);
1483        let b: &str = "major_locator = tck.MultipleLocator(np.pi/2.0)\n\
1484                       n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / (np.pi/2.0)\n\
1485                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1486                       \x20\x20\x20\x20plt.gca().yaxis.set_major_locator(major_locator)\n\
1487                       def multiple_of_pi_formatter(x, pos):\n\
1488                       \x20\x20\x20\x20den = 2\n\
1489                       \x20\x20\x20\x20num = int(np.rint(den*x/np.pi))\n\
1490                       \x20\x20\x20\x20com = np.gcd(num,den)\n\
1491                       \x20\x20\x20\x20(num,den) = (int(num/com),int(den/com))\n\
1492                       \x20\x20\x20\x20if den==1:\n\
1493                       \x20\x20\x20\x20\x20\x20\x20\x20if num==0: return r'$0$'\n\
1494                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\pi$'\n\
1495                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$-\\pi$'\n\
1496                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$%s\\pi$'%num\n\
1497                       \x20\x20\x20\x20else:\n\
1498                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\frac{\\pi}{%s}$'%den\n\
1499                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$\\frac{-\\pi}{%s}$'%den\n\
1500                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$\\frac{%s\\pi}{%s}$'%(num,den)\n\
1501                       major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n\
1502                       plt.gca().yaxis.set_major_formatter(major_formatter)\n";
1503        assert_eq!(plot.buffer, b);
1504
1505        let mut plot = Plot::new();
1506        plot.set_ticks_x_multiple_of_pi(1.0);
1507        let b: &str = "major_locator = tck.MultipleLocator(np.pi/2.0)\n\
1508                       n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / (np.pi/2.0)\n\
1509                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1510                       \x20\x20\x20\x20plt.gca().xaxis.set_major_locator(major_locator)\n\
1511                       minor_locator = tck.MultipleLocator(1)\n\
1512                       n_ticks = (plt.gca().axis()[1] - plt.gca().axis()[0]) / 1\n\
1513                       if n_ticks < minor_locator.MAXTICKS * 0.9:\n\
1514                       \x20\x20\x20\x20plt.gca().xaxis.set_minor_locator(minor_locator)\n\
1515                       def multiple_of_pi_formatter(x, pos):\n\
1516                       \x20\x20\x20\x20den = 2\n\
1517                       \x20\x20\x20\x20num = int(np.rint(den*x/np.pi))\n\
1518                       \x20\x20\x20\x20com = np.gcd(num,den)\n\
1519                       \x20\x20\x20\x20(num,den) = (int(num/com),int(den/com))\n\
1520                       \x20\x20\x20\x20if den==1:\n\
1521                       \x20\x20\x20\x20\x20\x20\x20\x20if num==0: return r'$0$'\n\
1522                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\pi$'\n\
1523                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$-\\pi$'\n\
1524                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$%s\\pi$'%num\n\
1525                       \x20\x20\x20\x20else:\n\
1526                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\frac{\\pi}{%s}$'%den\n\
1527                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$\\frac{-\\pi}{%s}$'%den\n\
1528                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$\\frac{%s\\pi}{%s}$'%(num,den)\n\
1529                       major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n\
1530                       plt.gca().xaxis.set_major_formatter(major_formatter)\n";
1531        assert_eq!(plot.buffer, b);
1532
1533        let mut plot = Plot::new();
1534        plot.set_ticks_y_multiple_of_pi(1.0);
1535        let b: &str = "major_locator = tck.MultipleLocator(np.pi/2.0)\n\
1536                       n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / (np.pi/2.0)\n\
1537                       if n_ticks < major_locator.MAXTICKS * 0.9:\n\
1538                       \x20\x20\x20\x20plt.gca().yaxis.set_major_locator(major_locator)\n\
1539                       minor_locator = tck.MultipleLocator(1)\n\
1540                       n_ticks = (plt.gca().axis()[3] - plt.gca().axis()[2]) / 1\n\
1541                       if n_ticks < minor_locator.MAXTICKS * 0.9:\n\
1542                       \x20\x20\x20\x20plt.gca().yaxis.set_minor_locator(minor_locator)\n\
1543                       def multiple_of_pi_formatter(x, pos):\n\
1544                       \x20\x20\x20\x20den = 2\n\
1545                       \x20\x20\x20\x20num = int(np.rint(den*x/np.pi))\n\
1546                       \x20\x20\x20\x20com = np.gcd(num,den)\n\
1547                       \x20\x20\x20\x20(num,den) = (int(num/com),int(den/com))\n\
1548                       \x20\x20\x20\x20if den==1:\n\
1549                       \x20\x20\x20\x20\x20\x20\x20\x20if num==0: return r'$0$'\n\
1550                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\pi$'\n\
1551                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$-\\pi$'\n\
1552                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$%s\\pi$'%num\n\
1553                       \x20\x20\x20\x20else:\n\
1554                       \x20\x20\x20\x20\x20\x20\x20\x20if num==1: return r'$\\frac{\\pi}{%s}$'%den\n\
1555                       \x20\x20\x20\x20\x20\x20\x20\x20elif num==-1: return r'$\\frac{-\\pi}{%s}$'%den\n\
1556                       \x20\x20\x20\x20\x20\x20\x20\x20else: return r'$\\frac{%s\\pi}{%s}$'%(num,den)\n\
1557                       major_formatter = tck.FuncFormatter(multiple_of_pi_formatter)\n\
1558                       plt.gca().yaxis.set_major_formatter(major_formatter)\n";
1559        assert_eq!(plot.buffer, b);
1560    }
1561
1562    #[test]
1563    fn set_frame_functions_work() {
1564        let mut plot = Plot::new();
1565        plot.set_frame_border(false, false, false, false)
1566            .set_frame_border(true, true, true, true)
1567            .set_frame_borders(false);
1568        let b: &str = "plt.gca().spines['left'].set_visible(False)\n\
1569                       plt.gca().spines['right'].set_visible(False)\n\
1570                       plt.gca().spines['bottom'].set_visible(False)\n\
1571                       plt.gca().spines['top'].set_visible(False)\n\
1572                       plt.gca().spines['left'].set_visible(True)\n\
1573                       plt.gca().spines['right'].set_visible(True)\n\
1574                       plt.gca().spines['bottom'].set_visible(True)\n\
1575                       plt.gca().spines['top'].set_visible(True)\n\
1576                       plt.gca().spines['left'].set_visible(False)\n\
1577                       plt.gca().spines['right'].set_visible(False)\n\
1578                       plt.gca().spines['bottom'].set_visible(False)\n\
1579                       plt.gca().spines['top'].set_visible(False)\n";
1580        assert_eq!(plot.buffer, b);
1581    }
1582
1583    #[test]
1584    fn additional_features_work() {
1585        let mut plot = Plot::new();
1586        plot.set_inv_x()
1587            .set_inv_y()
1588            .set_hide_xticks()
1589            .set_hide_yticks()
1590            .set_hide_zticks()
1591            .set_label_x_and_pad("X IS CLOSER NOW", -15.0)
1592            .set_label_y_and_pad("Y IS CLOSER NOW", -25.0)
1593            .set_label_z_and_pad("Z IS CLOSER NOW", -35.0)
1594            .extra("plt.show()\n");
1595        let b: &str = "plt.gca().invert_xaxis()\n\
1596                       plt.gca().invert_yaxis()\n\
1597                       plt.gca().set_xticklabels([])\n\
1598                       plt.gca().set_yticklabels([])\n\
1599                       plt.gca().set_zticklabels([])\n\
1600                       plt.gca().set_xlabel(r'X IS CLOSER NOW',labelpad=-15)\n\
1601                       plt.gca().set_ylabel(r'Y IS CLOSER NOW',labelpad=-25)\n\
1602                       plt.gca().set_zlabel(r'Z IS CLOSER NOW',labelpad=-35)\n\
1603                       plt.show()\n";
1604        assert_eq!(plot.buffer, b);
1605    }
1606
1607    #[test]
1608    fn gridspec_functions_work() {
1609        let mut plot = Plot::new();
1610        plot.set_gridspec("the_grid", 2, 2, "wspace=0.1,hspace=0.2")
1611            .set_subplot_grid("the_grid", "0:2", "0")
1612            .set_rotation_ticks_x(55.0)
1613            .set_rotation_ticks_y(45.0)
1614            .set_align_labels();
1615        let b: &str = "grid_the_grid=plt.GridSpec(2,2,wspace=0.1,hspace=0.2)\n\
1616                       \nplt.subplot(grid_the_grid[0:2,0])\n\
1617                       plt.gca().tick_params(axis='x',rotation=55)\n\
1618                       plt.gca().tick_params(axis='y',rotation=45)\n\
1619                       plt.gcf().align_labels()\n";
1620        assert_eq!(plot.buffer, b);
1621    }
1622
1623    #[test]
1624    fn set_labels_3d_works() {
1625        let mut plot = Plot::new();
1626        plot.set_label_z("Z").set_labels_3d("X", "Y", "Z");
1627        let b: &str = "plt.gca().set_zlabel(r'Z')\n\
1628                       plt.gca().set_xlabel(r'X')\n\
1629                       plt.gca().set_ylabel(r'Y')\n\
1630                       plt.gca().set_zlabel(r'Z')\n";
1631        assert_eq!(plot.buffer, b);
1632    }
1633
1634    #[test]
1635    fn extra_functionality_works() {
1636        let mut plot = Plot::new();
1637        plot.set_horiz_line(-1.0, "blue", "-", 1.1)
1638            .set_vert_line(-2.0, "green", ":", 1.2)
1639            .set_cross(0.25, 0.75, "red", "--", 3.0);
1640        let b: &str = "plt.axhline(-1,color='blue',linestyle='-',linewidth=1.1)\n\
1641                       plt.axvline(-2,color='green',linestyle=':',linewidth=1.2)\n\
1642                       plt.axhline(0.75,color='red',linestyle='--',linewidth=3)\n\
1643                       plt.axvline(0.25,color='red',linestyle='--',linewidth=3)\n";
1644        assert_eq!(plot.buffer, b);
1645    }
1646
1647    #[test]
1648    fn set_python_exe_works() {
1649        let mut plot = Plot::new();
1650        assert_eq!(plot.python_exe, "python3");
1651        plot.set_python_exe("python");
1652        assert_eq!(plot.python_exe, "python");
1653    }
1654}