plotpy/
canvas.rs

1use super::{GraphMaker, StrError};
2use crate::conversions::{matrix_to_array, vector_to_array};
3use crate::{AsMatrix, AsVector};
4use num_traits::Num;
5use std::fmt::Write;
6
7/// Defines the poly-curve code
8///
9/// Reference: [Matplotlib](https://matplotlib.org/stable/api/path_api.html)
10#[derive(Clone, Copy, Debug)]
11pub enum PolyCode {
12    /// Move to coordinate (first point)
13    ///
14    /// Matplotlib: Pick up the pen and move to the given vertex.
15    MoveTo,
16
17    /// Segment (next point, need 2 points)
18    ///
19    /// Matplotlib: Draw a line from the current position to the given vertex.
20    LineTo,
21
22    /// Quadratic Bezier (next point, need 3 control points with the first and last points on the curve)
23    ///
24    /// Matplotlib: Draw a quadratic Bezier curve from the current position, with the given control point, to the given end point.
25    Curve3,
26
27    /// Cubic Bezier (next point, need 4 control points with the first and last points on the curve)
28    ///
29    /// Matplotlib: Draw a cubic Bezier curve from the current position, with the given control points, to the given end point.
30    Curve4,
31}
32
33/// Implements functions to draw 2D and 3D features, including poly-lines and Bezier curves
34///
35/// # Examples
36///
37/// ## Drawing functions with polyline set by an array
38///
39/// ```
40/// use plotpy::{Canvas, Plot};
41///
42/// fn main() -> Result<(), &'static str> {
43///     // canvas object and common options
44///     let mut canvas = Canvas::new();
45///     canvas.set_line_width(3.0).set_edge_color("#cd0000").set_face_color("#eeea83");
46///
47///     // draw arc
48///     canvas.draw_arc(0.5, 0.5, 0.4, 195.0, -15.0);
49///
50///     // draw arrow
51///     canvas.set_arrow_scale(50.0).set_arrow_style("fancy");
52///     canvas.draw_arrow(0.4, 0.3, 0.6, 0.5);
53///
54///     // draw circle
55///     canvas.set_face_color("None").set_edge_color("#1f9c25").set_line_width(6.0);
56///     canvas.draw_circle(0.5, 0.5, 0.5);
57///
58///     // draw polyline
59///     canvas.set_line_width(3.0).set_edge_color("blue");
60///     let a = 0.2;
61///     let c = f64::sqrt(3.0) / 2.0;
62///     let p = &[[0.1, 0.5], [0.1 + a, 0.5], [0.1 + a / 2.0, 0.5 + a * c]];
63///     let q = &[[0.9, 0.5], [0.9 - a, 0.5], [0.9 - a / 2.0, 0.5 + a * c]];
64///     canvas.draw_polyline(p, true);
65///     canvas.draw_polyline(q, false);
66///
67///     // add canvas to plot
68///     let mut plot = Plot::new();
69///     plot.set_hide_axes(true)
70///         .set_equal_axes(true)
71///         .set_range(-0.05, 1.05, -0.05, 1.05)
72///         .add(&canvas);
73///
74///     // save figure
75///     plot.save("/tmp/plotpy/doc_tests/doc_canvas.svg")?;
76///     Ok(())
77/// }
78/// ```
79///
80/// ![doc_canvas.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_canvas.svg)
81///
82/// ## Cubic Bezier and use of begin/end functions
83///
84/// ```
85/// use plotpy::{Canvas, Plot, PolyCode, StrError};
86///
87/// fn main() -> Result<(), StrError> {
88///     // codes
89///     let data = [
90///         (3.0, 0.0, PolyCode::MoveTo),
91///         (1.0, 1.5, PolyCode::Curve4),
92///         (0.0, 4.0, PolyCode::Curve4),
93///         (2.5, 3.9, PolyCode::Curve4),
94///         (3.0, 3.8, PolyCode::LineTo),
95///         (3.5, 3.9, PolyCode::LineTo),
96///         (6.0, 4.0, PolyCode::Curve4),
97///         (5.0, 1.5, PolyCode::Curve4),
98///         (3.0, 0.0, PolyCode::Curve4),
99///     ];
100///
101///     // polycurve
102///     let mut canvas = Canvas::new();
103///     canvas.set_face_color("#f88989").set_edge_color("red");
104///     canvas.polycurve_begin();
105///     for (x, y, code) in data {
106///         canvas.polycurve_add(x, y, code);
107///     }
108///     canvas.polycurve_end(true);
109///
110///     // add canvas to plot
111///     let mut plot = Plot::new();
112///     plot.add(&canvas);
113///
114///     // save figure
115///     plot.set_range(1.0, 5.0, 0.0, 4.0)
116///         .set_frame_borders(false)
117///         .set_hide_axes(true)
118///         .set_equal_axes(true)
119///         .set_show_errors(true);
120///     plot.save("/tmp/plotpy/doc_tests/doc_canvas_polycurve.svg")?;
121///     Ok(())
122/// }
123/// ```
124///
125/// ![doc_canvas_polycurve.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_canvas_polycurve.svg)
126///
127/// See also integration tests in the [tests directory](https://github.com/cpmech/plotpy/tree/main/tests)
128pub struct Canvas {
129    // features
130    edge_color: String,  // Edge color (shared)
131    face_color: String,  // Face color (shared)
132    line_width: f64,     // Line width of edge (shared)
133    line_style: String,  // Style of lines (shared)
134    arrow_scale: f64,    // Arrow scale
135    arrow_style: String, // Arrow style
136
137    // text
138    text_color: String,            // Text color
139    text_align_horizontal: String, // Horizontal alignment
140    text_align_vertical: String,   // Vertical alignment
141    text_fontsize: f64,            // Font size
142    text_rotation: f64,            // Text rotation
143
144    // alternative text
145    alt_text_color: String,            // Text color
146    alt_text_align_horizontal: String, // Horizontal alignment
147    alt_text_align_vertical: String,   // Vertical alignment
148    alt_text_fontsize: f64,            // Font size
149    alt_text_rotation: f64,            // Text rotation
150
151    // options
152    stop_clip: bool, // Stop clipping features within margins
153    shading: bool,   // Shading for 3D surfaces (currently used only in draw_triangles_3d). Default = true
154
155    // options for glyph 3D
156    glyph_line_width: f64,     // Line width for 3D glyphs
157    glyph_size: f64,           // Size for 3D glyphs
158    glyph_color_x: String,     // Color for X axis of 3D glyphs
159    glyph_color_y: String,     // Color for X axis of 3D glyphs
160    glyph_color_z: String,     // Color for X axis of 3D glyphs
161    glyph_label_x: String,     // Label for X axis of 3D glyphs
162    glyph_label_y: String,     // Label for X axis of 3D glyphs
163    glyph_label_z: String,     // Label for X axis of 3D glyphs
164    glyph_label_color: String, // Color for labels of 3D glyphs (overrides individual axis colors)
165    glyph_bbox_opt: String,    // Python options for the dictionary setting the bounding box of 3D glyphs' text
166
167    // buffer
168    buffer: String, // buffer
169}
170
171impl Canvas {
172    /// Creates a new Canvas object
173    pub fn new() -> Self {
174        Canvas {
175            // features
176            edge_color: "#427ce5".to_string(),
177            face_color: String::new(),
178            line_width: 0.0,
179            line_style: String::new(),
180            arrow_scale: 0.0,
181            arrow_style: String::new(),
182            // text
183            text_color: "#343434".to_string(),
184            text_align_horizontal: "center".to_string(),
185            text_align_vertical: "center".to_string(),
186            text_fontsize: 10.0,
187            text_rotation: 0.0,
188            // alternative text
189            alt_text_color: "#a81414".to_string(),
190            alt_text_align_horizontal: String::new(),
191            alt_text_align_vertical: String::new(),
192            alt_text_fontsize: 8.0,
193            alt_text_rotation: 45.0,
194            // options
195            stop_clip: false,
196            shading: true,
197            // options for glyph 3D
198            glyph_line_width: 2.0,
199            glyph_size: 1.0,
200            glyph_color_x: "red".to_string(),
201            glyph_color_y: "green".to_string(),
202            glyph_color_z: "blue".to_string(),
203            glyph_label_x: "X".to_string(),
204            glyph_label_y: "Y".to_string(),
205            glyph_label_z: "Z".to_string(),
206            glyph_label_color: String::new(),
207            glyph_bbox_opt: "boxstyle='circle,pad=0.1',facecolor='white',edgecolor='None'".to_string(),
208            // buffer
209            buffer: String::new(),
210        }
211    }
212
213    /// Draws arc (2D only)
214    pub fn draw_arc<T>(&mut self, xc: T, yc: T, r: T, ini_angle: T, fin_angle: T)
215    where
216        T: std::fmt::Display + Num,
217    {
218        let opt = self.options_shared();
219        write!(
220            &mut self.buffer,
221            "p=pat.Arc(({},{}),2*{},2*{},theta1={},theta2={},angle=0{})\n\
222             plt.gca().add_patch(p)\n",
223            xc, yc, r, r, ini_angle, fin_angle, &opt
224        )
225        .unwrap();
226    }
227
228    /// Draws arrow (2D only)
229    pub fn draw_arrow<T>(&mut self, xi: T, yi: T, xf: T, yf: T)
230    where
231        T: std::fmt::Display + Num,
232    {
233        let opt_shared = self.options_shared();
234        let opt_arrow = self.options_arrow();
235        write!(
236            &mut self.buffer,
237            "p=pat.FancyArrowPatch(({},{}),({},{})\
238                    ,shrinkA=0,shrinkB=0\
239                    ,path_effects=[pff.Stroke(joinstyle='miter')]\
240                    {}{})\n\
241             plt.gca().add_patch(p)\n",
242            xi, yi, xf, yf, &opt_shared, &&opt_arrow,
243        )
244        .unwrap();
245    }
246
247    /// Draws circle (2D only)
248    pub fn draw_circle<T>(&mut self, xc: T, yc: T, r: T)
249    where
250        T: std::fmt::Display + Num,
251    {
252        let opt = self.options_shared();
253        write!(
254            &mut self.buffer,
255            "p=pat.Circle(({},{}),{}{})\n\
256             plt.gca().add_patch(p)\n",
257            xc, yc, r, &opt
258        )
259        .unwrap();
260    }
261
262    /// Draws triangles (2D only)
263    ///
264    /// Using <https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.triplot.html>
265    pub fn draw_triangles<'a, T, U, C>(&mut self, xx: &'a T, yy: &'a T, connectivity: &'a C) -> &mut Self
266    where
267        T: AsVector<'a, U>,
268        U: 'a + std::fmt::Display + Num,
269        C: AsMatrix<'a, usize>,
270    {
271        vector_to_array(&mut self.buffer, "xx", xx);
272        vector_to_array(&mut self.buffer, "yy", yy);
273        matrix_to_array(&mut self.buffer, "triangles", connectivity);
274        let opt = self.options_triangles();
275        write!(&mut self.buffer, "plt.triplot(xx,yy,triangles{})\n", &opt).unwrap();
276        self
277    }
278
279    /// Draws triangles (3D only)
280    ///
281    /// Using <https://matplotlib.org/stable/api/_as_gen/mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf.html#mpl_toolkits.mplot3d.axes3d.Axes3D.plot_trisurf>
282    ///
283    /// Note: There is no way to set shading and facecolor at the same time.
284    pub fn draw_triangles_3d<'a, T, U, C>(&mut self, xx: &'a T, yy: &'a T, zz: &'a T, connectivity: &'a C) -> &mut Self
285    where
286        T: AsVector<'a, U>,
287        U: 'a + std::fmt::Display + Num,
288        C: AsMatrix<'a, usize>,
289    {
290        // write arrays
291        vector_to_array(&mut self.buffer, "xx", xx);
292        vector_to_array(&mut self.buffer, "yy", yy);
293        vector_to_array(&mut self.buffer, "zz", zz);
294        matrix_to_array(&mut self.buffer, "triangles", connectivity);
295
296        // Issue when setting facecolor directly:
297        //
298        // In matplotlib 3.6+, passing facecolors as a parameter directly to plot_trisurf() doesn't work.
299        // The solution is:
300        // * Create the surface first with shade=False
301        // * Then use set_facecolor() to apply the colors after the surface is created
302        //
303        // Also, there is no way to set shading and facecolor at the same time.
304
305        // get options without facecolor
306        let opt = self.options_triangles_3d();
307
308        // write Python command
309        let shade = if self.shading { "True" } else { "False" };
310        write!(
311            &mut self.buffer,
312            "poly_collection=ax3d().plot_trisurf(xx,yy,zz,triangles=triangles,shade={}{})\n",
313            shade, &opt
314        )
315        .unwrap();
316
317        // set facecolor if specified
318        if self.face_color != "" {
319            write!(
320                &mut self.buffer,
321                "colors=np.array(['{}']*len(triangles))\n\
322                poly_collection.set_facecolor(colors)\n",
323                self.face_color
324            )
325            .unwrap();
326        }
327
328        // done
329        self
330    }
331
332    /// Begins drawing a polycurve (straight segments, quadratic Bezier, and cubic Bezier) (2D only)
333    ///
334    /// # Warning
335    ///
336    /// You must call [Canvas::polycurve_add] next, followed by [Canvas::polycurve_end] when finishing adding points.
337    /// Otherwise, Python/Matplotlib will fail.
338    pub fn polycurve_begin(&mut self) -> &mut Self {
339        write!(&mut self.buffer, "dat=[",).unwrap();
340        self
341    }
342
343    /// Adds point to a polycurve (straight segments, quadratic Bezier, and cubic Bezier) (2D only)
344    ///
345    /// # Warning
346    ///
347    /// You must call [Canvas::polycurve_begin] first, otherwise Python/Matplotlib will fail.
348    /// Afterwards, you must call [Canvas::polycurve_end] when finishing adding points.
349    pub fn polycurve_add<T>(&mut self, x: T, y: T, code: PolyCode) -> &mut Self
350    where
351        T: std::fmt::Display + Num,
352    {
353        let keyword = match code {
354            PolyCode::MoveTo => "MOVETO",
355            PolyCode::LineTo => "LINETO",
356            PolyCode::Curve3 => "CURVE3",
357            PolyCode::Curve4 => "CURVE4",
358        };
359        write!(&mut self.buffer, "[pth.Path.{},({},{})],", keyword, x, y).unwrap();
360        self
361    }
362
363    /// Ends drawing a polycurve (straight segments, quadratic Bezier, and cubic Bezier) (2D only)
364    ///
365    /// # Warning
366    ///
367    /// This function must be the last one called after [Canvas::polycurve_begin] and [Canvas::polycurve_add].
368    /// Otherwise, Python/Matplotlib will fail.
369    pub fn polycurve_end(&mut self, closed: bool) -> &mut Self {
370        if closed {
371            write!(&mut self.buffer, "[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
372        }
373        let opt = self.options_shared();
374        write!(
375            &mut self.buffer,
376            "]\n\
377            cmd,pts=zip(*dat)\n\
378            h=pth.Path(pts,cmd)\n\
379            p=pat.PathPatch(h{})\n\
380            plt.gca().add_patch(p)\n",
381            &opt
382        )
383        .unwrap();
384        self
385    }
386
387    /// Draws polyline with straight segments, quadratic Bezier, or cubic Bezier (2D only)
388    ///
389    /// **Note:** The first and last commands are ignored.
390    pub fn draw_polycurve<'a, T, U>(&mut self, points: &'a T, codes: &[PolyCode], closed: bool) -> Result<(), StrError>
391    where
392        T: AsMatrix<'a, U>,
393        U: 'a + std::fmt::Display,
394    {
395        let (npoint, ndim) = points.size();
396        if npoint < 3 {
397            return Err("npoint must be ≥ 3");
398        }
399        if ndim != 2 {
400            return Err("ndim must be equal to 2");
401        }
402        if codes.len() != npoint {
403            return Err("codes.len() must be equal to npoint");
404        }
405        write!(
406            &mut self.buffer,
407            "dat=[[pth.Path.MOVETO,({},{})]",
408            points.at(0, 0),
409            points.at(0, 1)
410        )
411        .unwrap();
412        for i in 1..npoint {
413            let keyword = match codes[i] {
414                PolyCode::MoveTo => "MOVETO",
415                PolyCode::LineTo => "LINETO",
416                PolyCode::Curve3 => "CURVE3",
417                PolyCode::Curve4 => "CURVE4",
418            };
419            write!(
420                &mut self.buffer,
421                ",[pth.Path.{},({},{})]",
422                keyword,
423                points.at(i, 0),
424                points.at(i, 1)
425            )
426            .unwrap();
427        }
428        if closed {
429            write!(&mut self.buffer, ",[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
430        }
431        let opt = self.options_shared();
432        write!(
433            &mut self.buffer,
434            "]\n\
435            cmd,pts=zip(*dat)\n\
436            h=pth.Path(pts,cmd)\n\
437            p=pat.PathPatch(h{})\n\
438            plt.gca().add_patch(p)\n",
439            &opt
440        )
441        .unwrap();
442        Ok(())
443    }
444
445    /// Begins adding points to a 3D polyline
446    ///
447    /// # Warning
448    ///
449    /// This function must be followed by [Canvas::polyline_3d_add] and [Canvas::polyline_3d_end],
450    /// otherwise Python/Matplotlib will fail
451    pub fn polyline_3d_begin(&mut self) -> &mut Self {
452        write!(&mut self.buffer, "xyz=np.array([").unwrap();
453        self
454    }
455
456    /// Adds point to a 3D polyline
457    ///
458    /// # Warning
459    ///
460    /// This function must be called after [Canvas::polyline_3d_begin] and must be followed by [Canvas::polyline_3d_end],
461    /// otherwise Python/Matplotlib will fail.
462    pub fn polyline_3d_add<T>(&mut self, x: T, y: T, z: T) -> &mut Self
463    where
464        T: std::fmt::Display + Num,
465    {
466        write!(&mut self.buffer, "[{},{},{}],", x, y, z).unwrap();
467        self
468    }
469
470    /// Ends adding points to a 3D polyline
471    ///
472    /// # Warning
473    ///
474    /// This function must be called after [Canvas::polyline_3d_begin] and [Canvas::polyline_3d_add],
475    /// otherwise Python/Matplotlib will fail.
476    pub fn polyline_3d_end(&mut self) -> &mut Self {
477        let opt = self.options_line_3d();
478        write!(
479            &mut self.buffer,
480            "])\nax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2]{})\n",
481            &opt
482        )
483        .unwrap();
484        self
485    }
486
487    /// Draws polyline (2D or 3D)
488    pub fn draw_polyline<'a, T, U>(&mut self, points: &'a T, closed: bool)
489    where
490        T: AsMatrix<'a, U>,
491        U: 'a + std::fmt::Display + Num,
492    {
493        let (npoint, ndim) = points.size();
494        if npoint < 2 {
495            return;
496        }
497        if ndim == 2 {
498            write!(
499                &mut self.buffer,
500                "dat=[[pth.Path.MOVETO,({},{})]",
501                points.at(0, 0),
502                points.at(0, 1)
503            )
504            .unwrap();
505            for i in 1..npoint {
506                write!(
507                    &mut self.buffer,
508                    ",[pth.Path.LINETO,({},{})]",
509                    points.at(i, 0),
510                    points.at(i, 1)
511                )
512                .unwrap();
513            }
514            if closed {
515                write!(&mut self.buffer, ",[pth.Path.CLOSEPOLY,(None,None)]").unwrap();
516            }
517            let opt = self.options_shared();
518            write!(
519                &mut self.buffer,
520                "]\n\
521                cmd,pts=zip(*dat)\n\
522                h=pth.Path(pts,cmd)\n\
523                p=pat.PathPatch(h{})\n\
524                plt.gca().add_patch(p)\n",
525                &opt
526            )
527            .unwrap();
528        }
529        if ndim == 3 {
530            self.polyline_3d_begin();
531            for i in 0..npoint {
532                self.polyline_3d_add(points.at(i, 0), points.at(i, 1), points.at(i, 2));
533            }
534            if closed && npoint > 2 {
535                self.polyline_3d_add(points.at(0, 0), points.at(0, 1), points.at(0, 2));
536            }
537            self.polyline_3d_end();
538        }
539    }
540
541    /// Draws a rectangle
542    pub fn draw_rectangle<T>(&mut self, x: T, y: T, width: T, height: T) -> &mut Self
543    where
544        T: std::fmt::Display + Num,
545    {
546        let opt = self.options_shared();
547        write!(
548            &mut self.buffer,
549            "p=pat.Rectangle(({},{}),{},{}{})\n\
550             plt.gca().add_patch(p)\n",
551            x, y, width, height, &opt
552        )
553        .unwrap();
554        self
555    }
556
557    /// Draws a text in a 2D graph
558    pub fn draw_text<T>(&mut self, x: T, y: T, label: &str) -> &mut Self
559    where
560        T: std::fmt::Display + Num,
561    {
562        self.text(2, &[x, y, T::zero()], label, false);
563        self
564    }
565
566    /// Draws an alternative text in a 2D graph
567    pub fn draw_alt_text<T>(&mut self, x: T, y: T, label: &str) -> &mut Self
568    where
569        T: std::fmt::Display + Num,
570    {
571        self.text(2, &[x, y, T::zero()], label, true);
572        self
573    }
574
575    /// Draws a 3D glyph at position (x,y,z) to indicate the direction of the X-Y-Z axes
576    pub fn draw_glyph_3d<T>(&mut self, x: T, y: T, z: T) -> &mut Self
577    where
578        T: std::fmt::Display + Num,
579    {
580        let size = self.glyph_size;
581        let lx = &self.glyph_label_x;
582        let ly = &self.glyph_label_y;
583        let lz = &self.glyph_label_z;
584        let lw = self.glyph_line_width;
585        let r = &self.glyph_color_x;
586        let g = &self.glyph_color_y;
587        let b = &self.glyph_color_z;
588        let tr = if self.glyph_label_color == "" {
589            &self.glyph_color_x
590        } else {
591            &self.glyph_label_color
592        };
593        let tg = if self.glyph_label_color == "" {
594            &self.glyph_color_y
595        } else {
596            &self.glyph_label_color
597        };
598        let tb = if self.glyph_label_color == "" {
599            &self.glyph_color_z
600        } else {
601            &self.glyph_label_color
602        };
603        write!(
604            &mut self.buffer,
605            "plt.gca().plot([{x},{x}+{size}],[{y},{y}],[{z},{z}],color='{r}',linewidth={lw})\n\
606             plt.gca().plot([{x},{x}],[{y},{y}+{size}],[{z},{z}],color='{g}',linewidth={lw})\n\
607             plt.gca().plot([{x},{x}],[{y},{y}],[{z},{z}+{size}],color='{b}',linewidth={lw})\n\
608             tx=plt.gca().text({x}+{size},{y},{z},'{lx}',color='{tr}',ha='center',va='center')\n\
609             ty=plt.gca().text({x},{y}+{size},{z},'{ly}',color='{tg}',ha='center',va='center')\n\
610             tz=plt.gca().text({x},{y},{z}+{size},'{lz}',color='{tb}',ha='center',va='center')\n"
611        )
612        .unwrap();
613        if self.glyph_bbox_opt != "" {
614            write!(
615                &mut self.buffer,
616                "tx.set_bbox(dict({}))\n\
617                 ty.set_bbox(dict({}))\n\
618                 tz.set_bbox(dict({}))\n",
619                self.glyph_bbox_opt, self.glyph_bbox_opt, self.glyph_bbox_opt
620            )
621            .unwrap();
622        }
623        self
624    }
625
626    /// Draws a 2D or 3D grid
627    ///
628    /// # Input
629    ///
630    /// * `xmin, xmax` -- min and max coordinates (len = 2 or 3 == ndim)
631    /// * `ndiv` -- number of divisions along each dimension (len = 2 or 3 == ndim)
632    ///
633    /// **Note:** See the `set_text_...` and `set_alt_text_...` functions to configure
634    /// the cell and point labels, respectively.
635    pub fn draw_grid(
636        &mut self,
637        xmin: &[f64],
638        xmax: &[f64],
639        ndiv: &[usize],
640        with_point_ids: bool,
641        with_cell_ids: bool,
642    ) -> Result<(), StrError> {
643        // check input
644        let ndim = ndiv.len();
645        if ndim < 2 || ndim > 3 {
646            return Err("len(ndiv) == ndim must be 2 or 3");
647        }
648        if xmin.len() != ndim {
649            return Err("size of xmin must equal ndim == len(ndiv)");
650        }
651        if xmax.len() != ndim {
652            return Err("size of xmax must equal ndim == len(ndiv)");
653        }
654
655        // compute delta
656        let mut npoint = [1; 3];
657        let mut delta = [0.0; 3];
658        for i in 0..ndim {
659            npoint[i] = ndiv[i] + 1;
660            delta[i] = xmax[i] - xmin[i];
661            if delta[i] <= 0.0 {
662                return Err("xmax must be greater than xmin");
663            }
664            delta[i] /= ndiv[i] as f64;
665        }
666
667        // auxiliary points
668        let mut a = [0.0; 3];
669        let mut b = [0.0; 3];
670
671        // loop over lines
672        if ndim == 2 {
673            write!(&mut self.buffer, "dat=[\n").unwrap();
674        }
675        let opt = self.options_shared();
676        let mut id_point = 0;
677        for k in 0..npoint[2] {
678            if ndim == 3 {
679                a[2] = xmin[2] + delta[2] * (k as f64);
680                b[2] = a[2];
681            }
682
683            // vertical lines
684            a[1] = xmin[1];
685            b[1] = xmax[1];
686            for i in 0..npoint[0] {
687                a[0] = xmin[0] + delta[0] * (i as f64);
688                b[0] = a[0];
689                self.line(ndim, &a, &b);
690            }
691
692            // horizontal lines
693            a[0] = xmin[0];
694            b[0] = xmax[0];
695            for j in 0..npoint[1] {
696                a[1] = xmin[1] + delta[1] * (j as f64);
697                b[1] = a[1];
698                self.line(ndim, &a, &b);
699            }
700
701            // add patch
702            if ndim == 2 {
703                write!(
704                    &mut self.buffer,
705                    "]\n\
706                    cmd,pts=zip(*dat)\n\
707                    h=pth.Path(pts,cmd)\n\
708                    p=pat.PathPatch(h{})\n\
709                    plt.gca().add_patch(p)\n",
710                    &opt
711                )
712                .unwrap();
713            }
714
715            // labels
716            if with_point_ids {
717                for j in 0..npoint[1] {
718                    a[1] = xmin[1] + delta[1] * (j as f64);
719                    for i in 0..npoint[0] {
720                        a[0] = xmin[0] + delta[0] * (i as f64);
721                        let txt = format!("{}", id_point);
722                        self.text(ndim, &a, &txt, true);
723                        id_point += 1;
724                    }
725                }
726            }
727        }
728
729        // cell ids
730        if with_cell_ids {
731            let mut id_cell = 0;
732            let nz = if ndim == 2 { 1 } else { ndiv[2] };
733            for k in 0..nz {
734                if ndim == 3 {
735                    a[2] = xmin[2] + delta[2] * (k as f64);
736                    b[2] = a[2] + delta[2] / 2.0;
737                }
738                for j in 0..ndiv[1] {
739                    a[1] = xmin[1] + delta[1] * (j as f64);
740                    b[1] = a[1] + delta[1] / 2.0;
741                    for i in 0..ndiv[0] {
742                        a[0] = xmin[0] + delta[0] * (i as f64);
743                        b[0] = a[0] + delta[0] / 2.0;
744                        let txt = format!("{}", id_cell);
745                        self.text(ndim, &b, &txt, false);
746                        id_cell += 1;
747                    }
748                }
749            }
750        }
751
752        // z-lines
753        if ndim == 3 {
754            a[2] = xmin[2];
755            b[2] = xmax[2];
756            for j in 0..npoint[1] {
757                a[1] = xmin[1] + delta[1] * (j as f64);
758                b[1] = a[1];
759                for i in 0..npoint[0] {
760                    a[0] = xmin[0] + delta[0] * (i as f64);
761                    b[0] = a[0];
762                    self.line(ndim, &a, &b);
763                }
764            }
765        }
766
767        // adjust limits
768        self.limits(ndim, xmin, xmax);
769
770        // done
771        Ok(())
772    }
773
774    /// Sets the edge color (shared among features)
775    pub fn set_edge_color(&mut self, color: &str) -> &mut Self {
776        self.edge_color = String::from(color);
777        self
778    }
779
780    /// Sets the face color (shared among features)
781    pub fn set_face_color(&mut self, color: &str) -> &mut Self {
782        self.face_color = String::from(color);
783        self
784    }
785
786    /// Sets the line width of edge (shared among features)
787    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
788        self.line_width = width;
789        self
790    }
791
792    /// Sets the line width of edge (shared among features)
793    ///
794    /// Options:
795    ///
796    /// * "`-`", `:`", "`--`", "`-.`", or "`None`"
797    /// * As defined in <https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html>
798    pub fn set_line_style(&mut self, style: &str) -> &mut Self {
799        self.line_style = String::from(style);
800        self
801    }
802
803    /// Sets the arrow scale
804    pub fn set_arrow_scale(&mut self, scale: f64) -> &mut Self {
805        self.arrow_scale = scale;
806        self
807    }
808
809    /// Sets the arrow style
810    ///
811    /// Options:
812    ///
813    /// * "`-`"      -- Curve         : None
814    /// * "`->`"     -- CurveB        : head_length=0.4,head_width=0.2
815    /// * "`-[`"     -- BracketB      : widthB=1.0,lengthB=0.2,angleB=None
816    /// * "`-|>`"    -- CurveFilledB  : head_length=0.4,head_width=0.2
817    /// * "`<-`"     -- CurveA        : head_length=0.4,head_width=0.2
818    /// * "`<->`"    -- CurveAB       : head_length=0.4,head_width=0.2
819    /// * "`<|-`"    -- CurveFilledA  : head_length=0.4,head_width=0.2
820    /// * "`<|-|>`"  -- CurveFilledAB : head_length=0.4,head_width=0.2
821    /// * "`]-`"     -- BracketA      : widthA=1.0,lengthA=0.2,angleA=None
822    /// * "`]-[`"    -- BracketAB     : widthA=1.0,lengthA=0.2,angleA=None,widthB=1.0,lengthB=0.2,angleB=None
823    /// * "`fancy`"  -- Fancy         : head_length=0.4,head_width=0.4,tail_width=0.4
824    /// * "`simple`" -- Simple        : head_length=0.5,head_width=0.5,tail_width=0.2
825    /// * "`wedge`"  -- Wedge         : tail_width=0.3,shrink_factor=0.5
826    /// * "`|-|`"    -- BarAB         : widthA=1.0,angleA=None,widthB=1.0,angleB=None
827    /// * As defined in <https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.FancyArrowPatch.html>
828    pub fn set_arrow_style(&mut self, style: &str) -> &mut Self {
829        self.arrow_style = String::from(style);
830        self
831    }
832
833    /// Sets the text color
834    pub fn set_text_color(&mut self, color: &str) -> &mut Self {
835        self.text_color = String::from(color);
836        self
837    }
838
839    /// Sets the text horizontal alignment
840    ///
841    /// Options: "center", "left", "right"
842    pub fn set_text_align_horizontal(&mut self, option: &str) -> &mut Self {
843        self.text_align_horizontal = String::from(option);
844        self
845    }
846
847    /// Sets the text vertical alignment
848    ///
849    /// Options: "center", "top", "bottom", "baseline", "center_baseline"
850    pub fn set_text_align_vertical(&mut self, option: &str) -> &mut Self {
851        self.text_align_vertical = String::from(option);
852        self
853    }
854
855    /// Sets the text font size
856    pub fn set_text_fontsize(&mut self, fontsize: f64) -> &mut Self {
857        self.text_fontsize = fontsize;
858        self
859    }
860
861    /// Sets the text rotation
862    pub fn set_text_rotation(&mut self, rotation: f64) -> &mut Self {
863        self.text_rotation = rotation;
864        self
865    }
866
867    /// Sets the alternative text color
868    pub fn set_alt_text_color(&mut self, color: &str) -> &mut Self {
869        self.alt_text_color = String::from(color);
870        self
871    }
872
873    /// Sets the alternative text horizontal alignment
874    ///
875    /// Options: "center", "left", "right"
876    pub fn set_alt_text_align_horizontal(&mut self, option: &str) -> &mut Self {
877        self.alt_text_align_horizontal = String::from(option);
878        self
879    }
880
881    /// Sets the alternative text vertical alignment
882    ///
883    /// Options: "center", "top", "bottom", "baseline", "center_baseline"
884    pub fn set_alt_text_align_vertical(&mut self, option: &str) -> &mut Self {
885        self.alt_text_align_vertical = String::from(option);
886        self
887    }
888
889    /// Sets the alternative text font size
890    pub fn set_alt_text_fontsize(&mut self, fontsize: f64) -> &mut Self {
891        self.alt_text_fontsize = fontsize;
892        self
893    }
894
895    /// Sets the alternative text rotation
896    pub fn set_alt_text_rotation(&mut self, rotation: f64) -> &mut Self {
897        self.alt_text_rotation = rotation;
898        self
899    }
900
901    /// Sets the flag to stop clipping features within margins
902    pub fn set_stop_clip(&mut self, flag: bool) -> &mut Self {
903        self.stop_clip = flag;
904        self
905    }
906
907    /// Sets shading for 3D surfaces (currently used only in draw_triangles_3d)
908    ///
909    /// Note: Shading is disabled if facecolor is non-empty.
910    ///
911    /// Default = true
912    pub fn set_shading(&mut self, flag: bool) -> &mut Self {
913        self.shading = flag;
914        self
915    }
916
917    /// Sets the line width used when drawing 3D glyphs
918    pub fn set_glyph_line_width(&mut self, width: f64) -> &mut Self {
919        self.glyph_line_width = width;
920        self
921    }
922
923    /// Sets the size (axis length) of 3D glyphs
924    pub fn set_glyph_size(&mut self, size: f64) -> &mut Self {
925        self.glyph_size = size;
926        self
927    }
928
929    /// Sets the color of the X axis in 3D glyphs
930    pub fn set_glyph_color_x(&mut self, color: &str) -> &mut Self {
931        self.glyph_color_x = String::from(color);
932        self
933    }
934
935    /// Sets the color of the Y axis in 3D glyphs
936    pub fn set_glyph_color_y(&mut self, color: &str) -> &mut Self {
937        self.glyph_color_y = String::from(color);
938        self
939    }
940
941    /// Sets the color of the Z axis in 3D glyphs
942    pub fn set_glyph_color_z(&mut self, color: &str) -> &mut Self {
943        self.glyph_color_z = String::from(color);
944        self
945    }
946
947    /// Sets the label used for the X axis in 3D glyphs
948    pub fn set_glyph_label_x(&mut self, label: &str) -> &mut Self {
949        self.glyph_label_x = String::from(label);
950        self
951    }
952
953    /// Sets the label used for the Y axis in 3D glyphs
954    pub fn set_glyph_label_y(&mut self, label: &str) -> &mut Self {
955        self.glyph_label_y = String::from(label);
956        self
957    }
958
959    /// Sets the label used for the Z axis in 3D glyphs
960    pub fn set_glyph_label_z(&mut self, label: &str) -> &mut Self {
961        self.glyph_label_z = String::from(label);
962        self
963    }
964
965    /// Sets a color to override the default label colors in 3D glyphs
966    ///
967    /// The default colors are the same as the axis colors.
968    pub fn set_glyph_label_color(&mut self, label_clr: &str) -> &mut Self {
969        self.glyph_label_color = String::from(label_clr);
970        self
971    }
972
973    /// Sets the Python dictionary string defining the bounding box of 3D glyphs
974    ///
975    /// Note: The setting of the bounding box here is different than the on implement in `Text`.
976    /// Here, all options for the Python whole dictionary must be provided, for example
977    /// (default string):
978    ///
979    /// ```text
980    /// "boxstyle='circle,pad=0.1',facecolor='white',edgecolor='None'"
981    /// ```
982    pub fn set_glyph_bbox(&mut self, bbox_dict: &str) -> &mut Self {
983        self.glyph_bbox_opt = String::from(bbox_dict);
984        self
985    }
986
987    /// Returns options for triangles (2D only)
988    fn options_triangles(&self) -> String {
989        let mut opt = String::new();
990        if self.edge_color != "" {
991            write!(&mut opt, ",color='{}'", self.edge_color).unwrap();
992        }
993        if self.line_width > 0.0 {
994            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
995        }
996        if self.line_style != "" {
997            write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
998        }
999        if self.stop_clip {
1000            write!(&mut opt, ",clip_on=False").unwrap();
1001        }
1002        opt
1003    }
1004
1005    /// Returns shared options
1006    fn options_triangles_3d(&self) -> String {
1007        let mut opt = String::new();
1008        if self.edge_color != "" {
1009            write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
1010        }
1011        if self.line_width > 0.0 {
1012            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
1013        }
1014        if self.line_style != "" {
1015            write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
1016        }
1017        if self.stop_clip {
1018            write!(&mut opt, ",clip_on=False").unwrap();
1019        }
1020        opt
1021    }
1022
1023    /// Returns shared options
1024    fn options_shared(&self) -> String {
1025        let mut opt = String::new();
1026        if self.edge_color != "" {
1027            write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
1028        }
1029        if self.face_color != "" {
1030            write!(&mut opt, ",facecolor='{}'", self.face_color).unwrap();
1031        }
1032        if self.line_width > 0.0 {
1033            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
1034        }
1035        if self.line_style != "" {
1036            write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
1037        }
1038        if self.stop_clip {
1039            write!(&mut opt, ",clip_on=False").unwrap();
1040        }
1041        opt
1042    }
1043
1044    /// Returns options for arrows
1045    fn options_arrow(&self) -> String {
1046        let mut opt = String::new();
1047        if self.arrow_scale > 0.0 {
1048            write!(&mut opt, ",mutation_scale={}", self.arrow_scale).unwrap();
1049        }
1050        if self.arrow_style != "" {
1051            write!(&mut opt, ",arrowstyle='{}'", self.arrow_style).unwrap();
1052        }
1053        opt
1054    }
1055
1056    /// Returns options for text
1057    fn options_text(&self) -> String {
1058        let mut opt = String::new();
1059        if self.text_color != "" {
1060            write!(&mut opt, ",color='{}'", self.text_color).unwrap();
1061        }
1062        if self.text_align_horizontal != "" {
1063            write!(&mut opt, ",ha='{}'", self.text_align_horizontal).unwrap();
1064        }
1065        if self.text_align_vertical != "" {
1066            write!(&mut opt, ",va='{}'", self.text_align_vertical).unwrap();
1067        }
1068        if self.text_fontsize > 0.0 {
1069            write!(&mut opt, ",fontsize={}", self.text_fontsize).unwrap();
1070        }
1071        if self.text_rotation > 0.0 {
1072            write!(&mut opt, ",rotation={}", self.text_rotation).unwrap();
1073        }
1074        opt
1075    }
1076
1077    /// Returns options for alternative text
1078    fn options_alt_text(&self) -> String {
1079        let mut opt = String::new();
1080        if self.alt_text_color != "" {
1081            write!(&mut opt, ",color='{}'", self.alt_text_color).unwrap();
1082        }
1083        if self.alt_text_align_horizontal != "" {
1084            write!(&mut opt, ",ha='{}'", self.alt_text_align_horizontal).unwrap();
1085        }
1086        if self.alt_text_align_vertical != "" {
1087            write!(&mut opt, ",va='{}'", self.alt_text_align_vertical).unwrap();
1088        }
1089        if self.alt_text_fontsize > 0.0 {
1090            write!(&mut opt, ",fontsize={}", self.alt_text_fontsize).unwrap();
1091        }
1092        if self.alt_text_rotation > 0.0 {
1093            write!(&mut opt, ",rotation={}", self.alt_text_rotation).unwrap();
1094        }
1095        opt
1096    }
1097
1098    /// Returns options for 3D line
1099    fn options_line_3d(&self) -> String {
1100        let mut opt = String::new();
1101        if self.edge_color != "" {
1102            write!(&mut opt, ",color='{}'", self.edge_color).unwrap();
1103        }
1104        if self.line_width > 0.0 {
1105            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
1106        }
1107        if self.line_style != "" {
1108            write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
1109        }
1110        opt
1111    }
1112
1113    /// Draws 2D or 3D line
1114    fn line<T>(&mut self, ndim: usize, a: &[T; 3], b: &[T; 3])
1115    where
1116        T: std::fmt::Display,
1117    {
1118        if ndim == 2 {
1119            write!(
1120                &mut self.buffer,
1121                "    [pth.Path.MOVETO,({},{})],[pth.Path.LINETO,({},{})],\n",
1122                a[0], a[1], b[0], b[1]
1123            )
1124            .unwrap();
1125        } else {
1126            let opt = self.options_line_3d();
1127            write!(
1128                &mut self.buffer,
1129                "ax3d().plot([{},{}],[{},{}],[{},{}]{})\n",
1130                a[0], b[0], a[1], b[1], a[2], b[2], opt,
1131            )
1132            .unwrap();
1133        }
1134    }
1135
1136    /// Draws 2D or 3D text
1137    fn text<T>(&mut self, ndim: usize, a: &[T; 3], txt: &str, alternative: bool)
1138    where
1139        T: std::fmt::Display,
1140    {
1141        let opt = if alternative {
1142            self.options_alt_text()
1143        } else {
1144            self.options_text()
1145        };
1146        if ndim == 2 {
1147            write!(&mut self.buffer, "plt.text({},{},'{}'{})\n", a[0], a[1], txt, &opt).unwrap();
1148        } else {
1149            write!(
1150                &mut self.buffer,
1151                "ax3d().text({},{},{},'{}'{})\n",
1152                a[0], a[1], a[2], txt, &opt
1153            )
1154            .unwrap();
1155        }
1156    }
1157
1158    /// Adjust 2D or 3D limits
1159    fn limits(&mut self, ndim: usize, xmin: &[f64], xmax: &[f64]) {
1160        const FACTOR: f64 = 0.1;
1161        let mut gap = [0.0; 3];
1162        for i in 0..ndim {
1163            gap[i] = (xmax[i] - xmin[i]) * FACTOR;
1164        }
1165        if ndim == 2 {
1166            write!(
1167                &mut self.buffer,
1168                "plt.axis([{},{},{},{}])\n",
1169                xmin[0] - gap[0],
1170                xmax[0] + gap[0],
1171                xmin[1] - gap[1],
1172                xmax[1] + gap[1]
1173            )
1174            .unwrap();
1175        } else {
1176            write!(
1177                &mut self.buffer,
1178                "ax3d().set_xlim3d({},{})\n\
1179                 ax3d().set_ylim3d({},{})\n\
1180                 ax3d().set_zlim3d({},{})\n",
1181                xmin[0] - gap[0],
1182                xmax[0] + gap[0],
1183                xmin[1] - gap[1],
1184                xmax[1] + gap[1],
1185                xmin[2] - gap[2],
1186                xmax[2] + gap[2]
1187            )
1188            .unwrap();
1189        }
1190    }
1191}
1192
1193impl GraphMaker for Canvas {
1194    fn get_buffer<'a>(&'a self) -> &'a String {
1195        &self.buffer
1196    }
1197    fn clear_buffer(&mut self) {
1198        self.buffer.clear();
1199    }
1200}
1201
1202////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
1203
1204#[cfg(test)]
1205mod tests {
1206    use super::Canvas;
1207    use crate::{GraphMaker, PolyCode};
1208
1209    #[test]
1210    fn derive_works() {
1211        let code = PolyCode::Curve3;
1212        let clone = code.clone();
1213        let correct = "Curve3";
1214        assert_eq!(format!("{:?}", code), correct);
1215        assert_eq!(format!("{:?}", clone), correct);
1216    }
1217
1218    #[test]
1219    fn new_works() {
1220        let canvas = Canvas::new();
1221        assert_eq!(canvas.edge_color.len(), 7);
1222        assert_eq!(canvas.face_color.len(), 0);
1223        assert_eq!(canvas.line_width, 0.0);
1224        assert_eq!(canvas.line_style.len(), 0);
1225        assert_eq!(canvas.arrow_scale, 0.0);
1226        assert_eq!(canvas.arrow_style.len(), 0);
1227        assert_eq!(canvas.buffer.len(), 0);
1228    }
1229
1230    #[test]
1231    fn options_shared_works() {
1232        let mut canvas = Canvas::new();
1233        canvas
1234            .set_edge_color("red")
1235            .set_face_color("blue")
1236            .set_line_width(2.5)
1237            .set_line_style("--")
1238            .set_stop_clip(true);
1239        let opt = canvas.options_shared();
1240        assert_eq!(
1241            opt,
1242            ",edgecolor='red'\
1243             ,facecolor='blue'\
1244             ,linewidth=2.5\
1245             ,linestyle='--'\
1246             ,clip_on=False"
1247        );
1248    }
1249
1250    #[test]
1251    fn options_arrow_works() {
1252        let mut canvas = Canvas::new();
1253        canvas.set_arrow_scale(25.0).set_arrow_style("fancy");
1254        let opt = canvas.options_arrow();
1255        assert_eq!(
1256            opt,
1257            ",mutation_scale=25\
1258             ,arrowstyle='fancy'"
1259        );
1260    }
1261
1262    #[test]
1263    fn options_text_works() {
1264        let mut canvas = Canvas::new();
1265        canvas
1266            .set_text_color("red")
1267            .set_text_align_horizontal("center")
1268            .set_text_align_vertical("center")
1269            .set_text_fontsize(8.0)
1270            .set_text_rotation(45.0);
1271        let opt = canvas.options_text();
1272        assert_eq!(
1273            opt,
1274            ",color='red'\
1275             ,ha='center'\
1276             ,va='center'\
1277             ,fontsize=8\
1278             ,rotation=45"
1279        );
1280    }
1281
1282    #[test]
1283    fn options_alt_text_works() {
1284        let mut canvas = Canvas::new();
1285        canvas
1286            .set_alt_text_color("blue")
1287            .set_alt_text_align_horizontal("right")
1288            .set_alt_text_align_vertical("bottom")
1289            .set_alt_text_fontsize(10.0)
1290            .set_alt_text_rotation(30.0);
1291        let opt = canvas.options_alt_text();
1292        assert_eq!(
1293            opt,
1294            ",color='blue'\
1295             ,ha='right'\
1296             ,va='bottom'\
1297             ,fontsize=10\
1298             ,rotation=30"
1299        );
1300    }
1301
1302    #[test]
1303    fn options_line_3d_works() {
1304        let mut canvas = Canvas::new();
1305        canvas.set_edge_color("red");
1306        let opt = canvas.options_line_3d();
1307        assert_eq!(opt, ",color='red'");
1308
1309        let mut canvas = Canvas::new();
1310        canvas.set_edge_color("red").set_line_width(5.0).set_line_style(":");
1311        let opt = canvas.options_line_3d();
1312        assert_eq!(opt, ",color='red',linewidth=5,linestyle=':'");
1313    }
1314
1315    #[test]
1316    fn glyph_setters_work() {
1317        let mut canvas = Canvas::new();
1318        canvas
1319            .set_glyph_line_width(4.5)
1320            .set_glyph_size(2.0)
1321            .set_glyph_color_x("orange")
1322            .set_glyph_color_y("cyan")
1323            .set_glyph_color_z("magenta")
1324            .set_glyph_label_x("Ux")
1325            .set_glyph_label_y("Uy")
1326            .set_glyph_label_z("Uz");
1327        assert_eq!(canvas.glyph_line_width, 4.5);
1328        assert_eq!(canvas.glyph_size, 2.0);
1329        assert_eq!(canvas.glyph_color_x, "orange");
1330        assert_eq!(canvas.glyph_color_y, "cyan");
1331        assert_eq!(canvas.glyph_color_z, "magenta");
1332        assert_eq!(canvas.glyph_label_x, "Ux");
1333        assert_eq!(canvas.glyph_label_y, "Uy");
1334        assert_eq!(canvas.glyph_label_z, "Uz");
1335    }
1336
1337    #[test]
1338    fn line_works() {
1339        let mut canvas = Canvas::new();
1340        let a = [0.0; 3];
1341        let b = [0.0; 3];
1342        canvas.line(2, &a, &b);
1343        canvas.line(3, &a, &b);
1344        assert_eq!(
1345            canvas.buffer,
1346            "\x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,0)],\n\
1347             ax3d().plot([0,0],[0,0],[0,0],color='#427ce5')\n"
1348        );
1349        canvas.clear_buffer();
1350        assert_eq!(canvas.buffer, "");
1351    }
1352
1353    #[test]
1354    fn text_works() {
1355        let mut canvas = Canvas::new();
1356        let a = [0.0; 3];
1357        canvas.text(2, &a, "hello", true);
1358        canvas.text(3, &a, "hello", false);
1359        assert_eq!(
1360            canvas.buffer,
1361            "plt.text(0,0,'hello',color='#a81414',fontsize=8,rotation=45)\n\
1362             ax3d().text(0,0,0,'hello',color='#343434',ha='center',va='center',fontsize=10)\n"
1363        );
1364    }
1365
1366    #[test]
1367    fn limits_works() {
1368        let mut canvas = Canvas::new();
1369        let xmin = [0.0; 3];
1370        let xmax = [0.0; 3];
1371        canvas.limits(2, &xmin, &xmax);
1372        canvas.limits(3, &xmin, &xmax);
1373        assert_eq!(
1374            canvas.buffer,
1375            "plt.axis([0,0,0,0])\n\
1376            ax3d().set_xlim3d(0,0)\n\
1377            ax3d().set_ylim3d(0,0)\n\
1378            ax3d().set_zlim3d(0,0)\n"
1379        );
1380    }
1381
1382    #[test]
1383    fn arc_works() {
1384        let mut canvas = Canvas::new();
1385        canvas.draw_arc(0.0, 0.0, 1.0, 30.0, 60.0);
1386        let b: &str = "p=pat.Arc((0,0),2*1,2*1,theta1=30,theta2=60,angle=0,edgecolor='#427ce5')\n\
1387                       plt.gca().add_patch(p)\n";
1388        assert_eq!(canvas.buffer, b);
1389    }
1390
1391    #[test]
1392    fn arrow_woks() {
1393        let mut canvas = Canvas::new();
1394        canvas.draw_arrow(0.0, 0.0, 1.0, 1.0);
1395        let b: &str =
1396            "p=pat.FancyArrowPatch((0,0),(1,1),shrinkA=0,shrinkB=0,path_effects=[pff.Stroke(joinstyle='miter')],edgecolor='#427ce5')\n\
1397             plt.gca().add_patch(p)\n";
1398        assert_eq!(canvas.buffer, b);
1399    }
1400
1401    #[test]
1402    fn circle_works() {
1403        let mut canvas = Canvas::new();
1404        canvas.draw_circle(0.0, 0.0, 1.0);
1405        let b: &str = "p=pat.Circle((0,0),1,edgecolor='#427ce5')\n\
1406                       plt.gca().add_patch(p)\n";
1407        assert_eq!(canvas.buffer, b);
1408    }
1409
1410    #[test]
1411    fn polycurve_methods_work() {
1412        // note the following sequence of codes won't work in Matplotlib because Curve3 and Curve4 are wrong
1413        let mut canvas = Canvas::new();
1414        canvas.polycurve_begin();
1415        assert_eq!(canvas.buffer, "dat=[");
1416        canvas.polycurve_add(0, 0, PolyCode::MoveTo);
1417        assert_eq!(canvas.buffer, "dat=[[pth.Path.MOVETO,(0,0)],");
1418        canvas.polycurve_add(1, 0, PolyCode::LineTo);
1419        assert_eq!(canvas.buffer, "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],");
1420        canvas.polycurve_add(2, 0, PolyCode::Curve3);
1421        assert_eq!(
1422            canvas.buffer,
1423            "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],"
1424        );
1425        canvas.polycurve_add(3, 0, PolyCode::Curve4);
1426        assert_eq!(
1427            canvas.buffer,
1428            "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],[pth.Path.CURVE4,(3,0)],"
1429        );
1430        canvas.polycurve_end(true);
1431        assert_eq!(
1432            canvas.buffer,
1433            "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],[pth.Path.CURVE3,(2,0)],[pth.Path.CURVE4,(3,0)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
1434            cmd,pts=zip(*dat)\n\
1435            h=pth.Path(pts,cmd)\n\
1436            p=pat.PathPatch(h,edgecolor='#427ce5')\n\
1437            plt.gca().add_patch(p)\n"
1438        );
1439    }
1440
1441    #[test]
1442    fn polycurve_capture_errors() {
1443        let mut canvas = Canvas::new();
1444        assert_eq!(
1445            canvas.draw_polycurve(&[[0, 0]], &[PolyCode::MoveTo], true).err(),
1446            Some("npoint must be ≥ 3")
1447        );
1448        assert_eq!(
1449            canvas
1450                .draw_polycurve(
1451                    &[[0], [0], [0]],
1452                    &[PolyCode::MoveTo, PolyCode::LineTo, PolyCode::LineTo],
1453                    true
1454                )
1455                .err(),
1456            Some("ndim must be equal to 2")
1457        );
1458        assert_eq!(
1459            canvas
1460                .draw_polycurve(&[[0, 0], [0, 0], [0, 0]], &[PolyCode::MoveTo], true)
1461                .err(),
1462            Some("codes.len() must be equal to npoint")
1463        );
1464    }
1465
1466    #[test]
1467    fn polycurve_works() {
1468        let mut canvas = Canvas::new();
1469        let points = &[[0, 0], [1, 0], [1, 1]];
1470        let codes = &[PolyCode::MoveTo, PolyCode::Curve3, PolyCode::Curve3];
1471        canvas.draw_polycurve(points, codes, true).unwrap();
1472        let b: &str = "dat=[[pth.Path.MOVETO,(0,0)],[pth.Path.CURVE3,(1,0)],[pth.Path.CURVE3,(1,1)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
1473                       cmd,pts=zip(*dat)\n\
1474                       h=pth.Path(pts,cmd)\n\
1475                       p=pat.PathPatch(h,edgecolor='#427ce5')\n\
1476                       plt.gca().add_patch(p)\n";
1477        assert_eq!(canvas.buffer, b);
1478    }
1479
1480    #[test]
1481    fn polyline_works_2d() {
1482        let mut canvas = Canvas::new();
1483        let points = &[[1.0, 1.0], [2.0, 1.0], [1.5, 1.866]];
1484        canvas.draw_polyline(points, true);
1485        let b: &str = "dat=[[pth.Path.MOVETO,(1,1)],[pth.Path.LINETO,(2,1)],[pth.Path.LINETO,(1.5,1.866)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
1486                       cmd,pts=zip(*dat)\n\
1487                       h=pth.Path(pts,cmd)\n\
1488                       p=pat.PathPatch(h,edgecolor='#427ce5')\n\
1489                       plt.gca().add_patch(p)\n";
1490        assert_eq!(canvas.buffer, b);
1491    }
1492
1493    #[test]
1494    fn polyline_3d_methods_work() {
1495        let mut canvas = Canvas::new();
1496        canvas
1497            .polyline_3d_begin()
1498            .polyline_3d_add(1, 2, 3)
1499            .polyline_3d_add(4, 5, 6)
1500            .polyline_3d_end();
1501        let b: &str = "\
1502            xyz=np.array([[1,2,3],[4,5,6],])\n\
1503            ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
1504        assert_eq!(canvas.buffer, b);
1505    }
1506
1507    #[test]
1508    fn polyline_works_3d() {
1509        let mut nothing = Canvas::new();
1510        nothing.draw_polyline(&[[0.0, 0.0]], true);
1511        assert_eq!(nothing.buffer, "");
1512
1513        #[rustfmt::skip]
1514        let points = &[
1515            [2.0, 1.0, 0.0],
1516            [0.0, 1.0, 0.0],
1517            [0.0, 1.0, 3.0],
1518            [2.0, 1.0, 3.0],
1519        ];
1520
1521        let mut open = Canvas::new();
1522        open.draw_polyline(points, false);
1523        let b: &str = "\
1524            xyz=np.array([[2,1,0],[0,1,0],[0,1,3],[2,1,3],])\n\
1525            ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
1526        assert_eq!(open.buffer, b);
1527
1528        let mut closed = Canvas::new();
1529        closed.draw_polyline(points, true);
1530        let b: &str = "\
1531            xyz=np.array([[2,1,0],[0,1,0],[0,1,3],[2,1,3],[2,1,0],])\n\
1532            ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
1533        assert_eq!(closed.buffer, b);
1534
1535        #[rustfmt::skip]
1536        let points = &[
1537            [2.0, 1.0, 0.0],
1538            [0.0, 1.0, 0.0],
1539        ];
1540
1541        let mut closed_few_points = Canvas::new();
1542        closed_few_points.draw_polyline(points, true);
1543        let b: &str = "\
1544            xyz=np.array([[2,1,0],[0,1,0],])\n\
1545            ax3d().plot(xyz[:,0],xyz[:,1],xyz[:,2],color='#427ce5')\n";
1546        assert_eq!(closed_few_points.buffer, b);
1547    }
1548
1549    #[test]
1550    fn grid_fails_on_wrong_input() {
1551        let mut canvas = Canvas::new();
1552        let res = canvas.draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1], true, false);
1553        assert_eq!(res, Err("len(ndiv) == ndim must be 2 or 3"));
1554        let res = canvas.draw_grid(&[0.0], &[1.0, 1.0], &[1, 1], true, false);
1555        assert_eq!(res, Err("size of xmin must equal ndim == len(ndiv)"));
1556        let res = canvas.draw_grid(&[0.0, 0.0], &[1.0], &[1, 1], true, false);
1557        assert_eq!(res, Err("size of xmax must equal ndim == len(ndiv)"));
1558        let res = canvas.draw_grid(&[0.0, 0.0], &[0.0, 1.0], &[1, 1], true, false);
1559        assert_eq!(res, Err("xmax must be greater than xmin"));
1560    }
1561
1562    #[test]
1563    fn grid_no_ids_works() {
1564        let mut canvas = Canvas::new();
1565        canvas
1566            .draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1, 1], false, false)
1567            .unwrap();
1568        let b: &str = "dat=[\n\
1569                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,1)],\n\
1570                      \x20\x20\x20\x20[pth.Path.MOVETO,(1,0)],[pth.Path.LINETO,(1,1)],\n\
1571                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],\n\
1572                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,1)],[pth.Path.LINETO,(1,1)],\n\
1573                      ]\n\
1574                      cmd,pts=zip(*dat)\n\
1575                      h=pth.Path(pts,cmd)\n\
1576                      p=pat.PathPatch(h,edgecolor='#427ce5')\n\
1577                      plt.gca().add_patch(p)\n\
1578                      plt.axis([-0.1,1.1,-0.1,1.1])\n";
1579        assert_eq!(canvas.buffer, b);
1580    }
1581
1582    #[test]
1583    fn grid_2d_works() {
1584        let mut canvas = Canvas::new();
1585        canvas.draw_grid(&[0.0, 0.0], &[1.0, 1.0], &[1, 1], true, true).unwrap();
1586        let b: &str = "dat=[\n\
1587                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(0,1)],\n\
1588                      \x20\x20\x20\x20[pth.Path.MOVETO,(1,0)],[pth.Path.LINETO,(1,1)],\n\
1589                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,0)],[pth.Path.LINETO,(1,0)],\n\
1590                      \x20\x20\x20\x20[pth.Path.MOVETO,(0,1)],[pth.Path.LINETO,(1,1)],\n\
1591                      ]\n\
1592                      cmd,pts=zip(*dat)\n\
1593                      h=pth.Path(pts,cmd)\n\
1594                      p=pat.PathPatch(h,edgecolor='#427ce5')\n\
1595                      plt.gca().add_patch(p)\n\
1596                      plt.text(0,0,'0',color='#a81414',fontsize=8,rotation=45)\n\
1597                      plt.text(1,0,'1',color='#a81414',fontsize=8,rotation=45)\n\
1598                      plt.text(0,1,'2',color='#a81414',fontsize=8,rotation=45)\n\
1599                      plt.text(1,1,'3',color='#a81414',fontsize=8,rotation=45)\n\
1600                      plt.text(0.5,0.5,'0',color='#343434',ha='center',va='center',fontsize=10)\n\
1601                      plt.axis([-0.1,1.1,-0.1,1.1])\n";
1602        assert_eq!(canvas.buffer, b);
1603    }
1604
1605    #[test]
1606    fn grid_3d_works() {
1607        let mut canvas = Canvas::new();
1608        canvas
1609            .draw_grid(&[0.0, 0.0, 0.0], &[1.0, 1.0, 1.0], &[1, 1, 1], true, true)
1610            .unwrap();
1611        let b: &str = "\
1612                       ax3d().plot([0,0],[0,1],[0,0],color='#427ce5')\n\
1613                       ax3d().plot([1,1],[0,1],[0,0],color='#427ce5')\n\
1614                       ax3d().plot([0,1],[0,0],[0,0],color='#427ce5')\n\
1615                       ax3d().plot([0,1],[1,1],[0,0],color='#427ce5')\n\
1616                       ax3d().text(0,0,0,'0',color='#a81414',fontsize=8,rotation=45)\n\
1617                       ax3d().text(1,0,0,'1',color='#a81414',fontsize=8,rotation=45)\n\
1618                       ax3d().text(0,1,0,'2',color='#a81414',fontsize=8,rotation=45)\n\
1619                       ax3d().text(1,1,0,'3',color='#a81414',fontsize=8,rotation=45)\n\
1620                       ax3d().plot([0,0],[0,1],[1,1],color='#427ce5')\n\
1621                       ax3d().plot([1,1],[0,1],[1,1],color='#427ce5')\n\
1622                       ax3d().plot([0,1],[0,0],[1,1],color='#427ce5')\n\
1623                       ax3d().plot([0,1],[1,1],[1,1],color='#427ce5')\n\
1624                       ax3d().text(0,0,1,'4',color='#a81414',fontsize=8,rotation=45)\n\
1625                       ax3d().text(1,0,1,'5',color='#a81414',fontsize=8,rotation=45)\n\
1626                       ax3d().text(0,1,1,'6',color='#a81414',fontsize=8,rotation=45)\n\
1627                       ax3d().text(1,1,1,'7',color='#a81414',fontsize=8,rotation=45)\n\
1628                       ax3d().text(0.5,0.5,0.5,'0',color='#343434',ha='center',va='center',fontsize=10)\n\
1629                       ax3d().plot([0,0],[0,0],[0,1],color='#427ce5')\n\
1630                       ax3d().plot([1,1],[0,0],[0,1],color='#427ce5')\n\
1631                       ax3d().plot([0,0],[1,1],[0,1],color='#427ce5')\n\
1632                       ax3d().plot([1,1],[1,1],[0,1],color='#427ce5')\n\
1633                       ax3d().set_xlim3d(-0.1,1.1)\n\
1634                       ax3d().set_ylim3d(-0.1,1.1)\n\
1635                       ax3d().set_zlim3d(-0.1,1.1)\n";
1636        assert_eq!(canvas.buffer, b);
1637    }
1638}