plotpy/
slope_icon.rs

1use super::GraphMaker;
2use std::fmt::Write;
3
4/// Creates an icon to indicate the slope of lines
5///
6/// # Notes
7///
8/// When using log scales, `plot.set_log_x(true)` or `plot.set_log_y(true)`
9/// must be called before adding the icon.
10///
11/// # Example
12///
13/// ```
14/// use plotpy::{linspace, Curve, Plot, SlopeIcon, StrError};
15///
16/// fn main() -> Result<(), StrError> {
17///     // models
18///     let slope = 2.0;
19///     let (xi, xf, yi) = (0.0, 10.0, 0.0);
20///     let f = |x: f64| yi + slope * (x - xi);
21///     let g = |x: f64| f(xf) - slope * (x - xi);
22///
23///     // curves
24///     let mut curve1 = Curve::new();
25///     let mut curve2 = Curve::new();
26///     let x = linspace(xi, xf, 3);
27///     let y1: Vec<_> = x.iter().map(|x| f(*x)).collect();
28///     let y2: Vec<_> = x.iter().map(|x| g(*x)).collect();
29///     curve1.set_marker_style("o").draw(&x, &y1);
30///     curve2.set_marker_style("*").draw(&x, &y2);
31///
32///     // icons
33///     let mut icon1 = SlopeIcon::new();
34///     let mut icon2 = SlopeIcon::new();
35///     let mut icon3 = SlopeIcon::new();
36///     let mut icon4 = SlopeIcon::new();
37///     icon2.set_above(true);
38///     icon4.set_above(true);
39///     icon1.draw(slope, 2.5, f(2.5));
40///     icon2.draw(slope, 7.5, f(7.5));
41///     icon3.draw(-slope, 2.5, g(2.5));
42///     icon4.draw(-slope, 7.5, g(7.5));
43///
44///     // plot
45///     let mut plot = Plot::new();
46///     plot.set_horizontal_gap(0.2);
47///     plot.set_subplot(2, 2, 1)
48///         .add(&curve1)
49///         .add(&curve2)
50///         .add(&icon1)
51///         .add(&icon2)
52///         .add(&icon3)
53///         .add(&icon4)
54///         .grid_and_labels("x", "y");
55///
56///     // save figure
57///     plot.save("/tmp/plotpy/doc_tests/doc_slope_icon.svg")?;
58///     Ok(())
59/// }
60/// ```
61///
62/// ![doc_slope_icon.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_slope_icon.svg)
63///
64/// See also integration test in the **tests** directory.
65///
66/// Output from some integration tests:
67///
68/// ![integ_slope_icon_above.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_above.svg)
69///
70/// ![integ_slope_icon_below.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_below.svg)
71///
72/// ![integ_slope_icon_example.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_example.svg)
73///
74/// ![integ_slope_icon_linx_liny.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_linx_liny.svg)
75///
76/// ![integ_slope_icon_logx_logy.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/integ_slope_icon_logx_logy.svg)
77pub struct SlopeIcon {
78    above: bool,        // draw icon above line
79    edge_color: String, // Color of icon lines
80    face_color: String, // Color of icon faces
81    line_style: String, // Style of lines
82    line_width: f64,    // Width of lines
83    length: f64,        // horizontal length of icon in Axes coords [0,1]
84    offset_v: f64,      // vertical offset in points
85    no_text: bool,      // do not draw text
86    fontsize: f64,      // text font size
87    precision: usize,   // precision of slope number in label
88    text_h: String,     // use fixed text for horizontal value
89    text_v: String,     // use fixed text for vertical (slope) value
90    text_color: String, // Color of text
91    text_offset_h: f64, // horizontal offset for text in points
92    text_offset_v: f64, // vertical offset for text in points
93    buffer: String,     // buffer
94}
95
96impl SlopeIcon {
97    /// Creates a new SlopeIcon object
98    pub fn new() -> Self {
99        SlopeIcon {
100            above: false,
101            edge_color: "#000000".to_string(),
102            face_color: "#f7f7f7".to_string(),
103            line_style: String::new(),
104            line_width: 0.0,
105            length: 0.1,
106            offset_v: 5.0,
107            no_text: false,
108            fontsize: 0.0,
109            precision: 0,
110            text_h: "1".to_string(),
111            text_v: String::new(),
112            text_color: "#000000".to_string(),
113            text_offset_h: 3.0,
114            text_offset_v: 2.0,
115            buffer: String::new(),
116        }
117    }
118
119    /// Draws an icon of line slope
120    pub fn draw(&mut self, slope: f64, x_center: f64, y_center: f64) {
121        // set flip flag
122        let flip = if slope < 0.0 { !self.above } else { self.above };
123
124        // compute axis (normalized) coordinates and slope
125        write!(
126            &mut self.buffer,
127            "slope,cx,cy=float({}),float({}),float({})\n\
128             if plt.gca().get_xscale() == 'log': cx=np.log10(cx)\n\
129             if plt.gca().get_yscale() == 'log': cy=np.log10(cy)\n\
130             xc,yc=data_to_axis((cx,cy))\n\
131             xa,ya=data_to_axis((cx+1.0,cy+slope))\n\
132             m,l=(ya-yc)/(xa-xc),{}\n",
133            slope,
134            x_center,
135            y_center,
136            self.length / 2.0,
137        )
138        .unwrap();
139
140        // set polygon
141        if flip {
142            self.buffer.push_str(
143                "dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],\
144                      [pth.Path.LINETO,(xc-l,yc+m*l)],\
145                      [pth.Path.LINETO,(xc+l,yc+m*l)],\
146                      [pth.Path.CLOSEPOLY,(None,None)]]\n",
147            );
148        } else {
149            self.buffer.push_str(
150                "dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],\
151                      [pth.Path.LINETO,(xc+l,yc-m*l)],\
152                      [pth.Path.LINETO,(xc+l,yc+m*l)],\
153                      [pth.Path.CLOSEPOLY,(None,None)]]\n",
154            );
155        }
156
157        // draw
158        let tf = self.transform(slope);
159        let opt = self.options();
160        write!(
161            &mut self.buffer,
162            "{}cmd,pts=zip(*dat)\n\
163             h=pth.Path(pts,cmd)\n\
164             p=pat.PathPatch(h{})\n\
165             plt.gca().add_patch(p)\n",
166            tf, opt,
167        )
168        .unwrap();
169
170        // skip text
171        if self.no_text {
172            return;
173        }
174
175        // coordinates for labels
176        self.buffer.push_str(
177            "xm,ym=xc-l,yc-m*l\n\
178             xp,yp=xc+l,yc+m*l\n",
179        );
180
181        // slope text
182        let mut text = String::new();
183        if self.text_v == "" {
184            if self.precision == 0 {
185                write!(&mut text, "{}", f64::abs(slope)).unwrap();
186            } else {
187                write!(&mut text, "{:.1$}", f64::abs(slope), self.precision).unwrap();
188            }
189        } else {
190            write!(&mut text, "{}", self.text_v).unwrap();
191        }
192
193        // draw labels
194        let tf_txt = self.transform_text(slope);
195        self.buffer.push_str(&tf_txt);
196        let (opt_x, opt_y) = self.options_text();
197        if flip {
198            if slope < 0.0 {
199                write!(
200                    &mut self.buffer,
201                    "plt.text(xc,yp,r'{}',ha='center',va='top'{})\n",
202                    self.text_h, opt_x
203                )
204                .unwrap();
205            } else {
206                write!(
207                    &mut self.buffer,
208                    "plt.text(xc,yp,r'{}',ha='center',va='bottom'{})\n",
209                    self.text_h, opt_x
210                )
211                .unwrap();
212            }
213            write!(
214                &mut self.buffer,
215                "plt.text(xm,yc,r'{}',ha='right',va='center'{})\n",
216                text, opt_y
217            )
218            .unwrap();
219        } else {
220            if slope < 0.0 {
221                write!(
222                    &mut self.buffer,
223                    "plt.text(xc,ym,r'{}',ha='center',va='bottom'{})\n",
224                    self.text_h, opt_x
225                )
226                .unwrap();
227            } else {
228                write!(
229                    &mut self.buffer,
230                    "plt.text(xc,ym,r'{}',ha='center',va='top'{})\n",
231                    self.text_h, opt_x
232                )
233                .unwrap();
234            }
235            write!(
236                &mut self.buffer,
237                "plt.text(xp,yc,r'{}',ha='left',va='center'{})\n",
238                text, opt_y
239            )
240            .unwrap();
241        }
242    }
243
244    /// Sets option to draw icon above line
245    pub fn set_above(&mut self, flag: bool) -> &mut Self {
246        self.above = flag;
247        self
248    }
249
250    /// Sets the color of icon lines
251    pub fn set_edge_color(&mut self, color: &str) -> &mut Self {
252        self.edge_color = String::from(color);
253        self
254    }
255
256    /// Sets the color of icon face
257    pub fn set_face_color(&mut self, color: &str) -> &mut Self {
258        self.face_color = String::from(color);
259        self
260    }
261
262    /// Sets the style of lines
263    ///
264    /// Options:
265    ///
266    /// * "`-`", `:`", "`--`", "`-.`", or "`None`"
267    /// * As defined in <https://matplotlib.org/stable/gallery/lines_bars_and_markers/linestyles.html>
268    pub fn set_line_style(&mut self, style: &str) -> &mut Self {
269        self.line_style = String::from(style);
270        self
271    }
272
273    /// Sets the width of lines
274    pub fn set_line_width(&mut self, width: f64) -> &mut Self {
275        self.line_width = width;
276        self
277    }
278
279    /// Sets the (horizontal) length of the icon in Axes coordinates [0, 1]
280    pub fn set_length(&mut self, value: f64) -> &mut Self {
281        self.length = value;
282        self
283    }
284
285    /// Sets the whole icon's offset in normalized axes coordinates in points
286    pub fn set_offset_v(&mut self, value: f64) -> &mut Self {
287        self.offset_v = value;
288        self
289    }
290
291    /// Sets option to skip drawing text
292    pub fn set_no_text(&mut self, flag: bool) -> &mut Self {
293        self.no_text = flag;
294        self
295    }
296
297    /// Sets the font size
298    pub fn set_fontsize(&mut self, fontsize: f64) -> &mut Self {
299        self.fontsize = fontsize;
300        self
301    }
302
303    /// Sets the the precision of slope number in label
304    pub fn set_precision(&mut self, value: usize) -> &mut Self {
305        self.precision = value;
306        self
307    }
308
309    /// Sets text of horizontal value (== 1)
310    pub fn set_text_h(&mut self, one: &str) -> &mut Self {
311        self.text_h = String::from(one);
312        self
313    }
314
315    /// Sets text of vertical value (slope)
316    pub fn set_text_v(&mut self, slope: &str) -> &mut Self {
317        self.text_v = String::from(slope);
318        self
319    }
320
321    /// Sets the color of text
322    pub fn set_text_color(&mut self, color: &str) -> &mut Self {
323        self.text_color = String::from(color);
324        self
325    }
326
327    /// Sets the horizontal offset for the text in normalized axes coordinates in points
328    pub fn set_text_offset_h(&mut self, value: f64) -> &mut Self {
329        self.text_offset_h = value;
330        self
331    }
332
333    /// Sets the vertical offset for the text in normalized axes coordinates in points
334    pub fn set_text_offset_v(&mut self, value: f64) -> &mut Self {
335        self.text_offset_v = value;
336        self
337    }
338
339    /// Returns the icon's (whole) coordinate transform
340    fn transform(&self, slope: f64) -> String {
341        let flip = if slope < 0.0 { !self.above } else { self.above };
342        let mut opt = String::new();
343        if self.offset_v > 0.0 {
344            let dv = if flip {
345                self.offset_v * f64::signum(slope)
346            } else {
347                -self.offset_v * f64::signum(slope)
348            };
349            write!(
350                &mut opt,
351                "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y={},units='points')\n",
352                dv,
353            )
354            .unwrap();
355        } else {
356            opt.push_str("tf=plt.gca().transAxes\n");
357        }
358        opt
359    }
360
361    /// Returns the coordinate transform for text
362    fn transform_text(&self, slope: f64) -> String {
363        let flip = if slope < 0.0 { !self.above } else { self.above };
364        let mut opt = String::new();
365        if self.offset_v > 0.0 || self.text_offset_v > 0.0 {
366            let dv = if flip {
367                (self.offset_v + self.text_offset_v) * f64::signum(slope)
368            } else {
369                -(self.offset_v + self.text_offset_v) * f64::signum(slope)
370            };
371            write!(
372                &mut opt,
373                "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y={},units='points')\n",
374                dv,
375            )
376            .unwrap();
377        } else {
378            opt.push_str("tfx=plt.gca().transAxes\n");
379        }
380        if self.offset_v > 0.0 || self.text_offset_h > 0.0 {
381            let dv = if flip {
382                self.offset_v * f64::signum(slope)
383            } else {
384                -self.offset_v * f64::signum(slope)
385            };
386            let dh = if flip { -self.text_offset_h } else { self.text_offset_h };
387            write!(
388                &mut opt,
389                "tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x={},y={},units='points')\n",
390                dh, dv,
391            )
392            .unwrap();
393        } else {
394            opt.push_str("tfy=plt.gca().transAxes\n");
395        }
396        opt
397    }
398
399    /// Returns options for slope icon
400    fn options(&self) -> String {
401        let mut opt = String::from(",transform=tf");
402        if self.edge_color != "" {
403            write!(&mut opt, ",edgecolor='{}'", self.edge_color).unwrap();
404        }
405        if self.face_color != "" {
406            write!(&mut opt, ",facecolor='{}'", self.face_color).unwrap();
407        }
408        if self.line_style != "" {
409            write!(&mut opt, ",linestyle='{}'", self.line_style).unwrap();
410        }
411        if self.line_width > 0.0 {
412            write!(&mut opt, ",linewidth={}", self.line_width).unwrap();
413        }
414        opt
415    }
416
417    /// Returns options for text
418    fn options_text(&self) -> (String, String) {
419        let mut opt_x = String::from(",transform=tfx");
420        let mut opt_y = String::from(",transform=tfy");
421        if self.text_color != "" {
422            write!(&mut opt_x, ",color='{}'", self.text_color).unwrap();
423            write!(&mut opt_y, ",color='{}'", self.text_color).unwrap();
424        }
425        if self.fontsize > 0.0 {
426            write!(&mut opt_x, ",fontsize={}", self.fontsize).unwrap();
427            write!(&mut opt_y, ",fontsize={}", self.fontsize).unwrap();
428        }
429        (opt_x, opt_y)
430    }
431}
432
433impl GraphMaker for SlopeIcon {
434    fn get_buffer<'a>(&'a self) -> &'a String {
435        &self.buffer
436    }
437    fn clear_buffer(&mut self) {
438        self.buffer.clear();
439    }
440}
441
442////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
443
444#[cfg(test)]
445mod tests {
446    use super::SlopeIcon;
447    use crate::GraphMaker;
448
449    #[test]
450    fn new_works() {
451        let icon = SlopeIcon::new();
452        assert_eq!(icon.above, false);
453        assert_eq!(icon.edge_color.len(), 7);
454        assert_eq!(icon.line_style.len(), 0);
455        assert_eq!(icon.line_width, 0.0);
456        assert_eq!(icon.length, 0.1);
457        assert_eq!(icon.offset_v, 5.0);
458        assert_eq!(icon.no_text, false);
459        assert_eq!(icon.fontsize, 0.0);
460        assert_eq!(icon.precision, 0);
461        assert_eq!(icon.text_h.len(), 1);
462        assert_eq!(icon.text_v.len(), 0);
463        assert_eq!(icon.text_color.len(), 7);
464        assert_eq!(icon.text_offset_h, 3.0);
465        assert_eq!(icon.text_offset_v, 2.0);
466        assert_eq!(icon.buffer.len(), 0);
467    }
468
469    #[test]
470    fn transform_works() {
471        let mut icon = SlopeIcon::new();
472        icon.set_offset_v(7.0);
473        icon.set_above(false);
474        assert_eq!(
475            icon.transform(1.0),
476            "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-7,units='points')\n"
477        );
478        icon.set_above(true);
479        assert_eq!(
480            icon.transform(1.0),
481            "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=7,units='points')\n"
482        );
483        icon.set_above(false);
484        assert_eq!(
485            icon.transform(-1.0),
486            "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-7,units='points')\n"
487        );
488        icon.set_above(true);
489        assert_eq!(
490            icon.transform(-1.0),
491            "tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=7,units='points')\n"
492        );
493        icon.set_offset_v(0.0);
494        icon.set_above(false);
495        assert_eq!(icon.transform(-1.0), "tf=plt.gca().transAxes\n");
496    }
497
498    #[test]
499    fn transform_text_works() {
500        let mut icon = SlopeIcon::new();
501
502        icon.set_offset_v(7.0);
503        icon.set_text_offset_h(1.0);
504        icon.set_text_offset_v(3.0);
505        icon.set_above(false);
506        assert_eq!(
507            icon.transform_text(1.0),
508            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-10,units='points')\n\
509             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=-7,units='points')\n"
510        );
511        icon.set_above(true);
512        assert_eq!(
513            icon.transform_text(1.0),
514            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
515             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=7,units='points')\n"
516        );
517        icon.set_above(false);
518        assert_eq!(
519            icon.transform_text(-1.0),
520            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-10,units='points')\n\
521             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=-7,units='points')\n"
522        );
523        icon.set_above(true);
524        assert_eq!(
525            icon.transform_text(-1.0),
526            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
527             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=7,units='points')\n"
528        );
529
530        icon.set_offset_v(0.0);
531        icon.set_above(false);
532        assert_eq!(
533            icon.transform_text(1.0),
534            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-3,units='points')\n\
535             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=-0,units='points')\n"
536        );
537        icon.set_above(true);
538        assert_eq!(
539            icon.transform_text(1.0),
540            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
541             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=0,units='points')\n"
542        );
543        icon.set_above(false);
544        assert_eq!(
545            icon.transform_text(-1.0),
546            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=-3,units='points')\n\
547             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-1,y=-0,units='points')\n"
548        );
549        icon.set_above(true);
550        assert_eq!(
551            icon.transform_text(-1.0),
552            "tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
553             tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=1,y=0,units='points')\n"
554        );
555
556        icon.set_offset_v(0.0);
557        icon.set_text_offset_v(0.0);
558        icon.set_text_offset_h(0.0);
559        icon.set_above(false);
560        assert_eq!(
561            icon.transform_text(1.0),
562            "tfx=plt.gca().transAxes\n\
563             tfy=plt.gca().transAxes\n"
564        );
565        assert_eq!(
566            icon.transform_text(-1.0),
567            "tfx=plt.gca().transAxes\n\
568             tfy=plt.gca().transAxes\n"
569        );
570        icon.set_above(true);
571        assert_eq!(
572            icon.transform_text(1.0),
573            "tfx=plt.gca().transAxes\n\
574             tfy=plt.gca().transAxes\n"
575        );
576        assert_eq!(
577            icon.transform_text(-1.0),
578            "tfx=plt.gca().transAxes\n\
579             tfy=plt.gca().transAxes\n"
580        );
581    }
582
583    #[test]
584    fn options_works() {
585        let mut icon = SlopeIcon::new();
586        icon.set_edge_color("red")
587            .set_face_color("gold")
588            .set_line_style("--")
589            .set_line_width(2.0);
590        let options = icon.options();
591        assert_eq!(
592            options,
593            ",transform=tf\
594             ,edgecolor='red'\
595             ,facecolor='gold'\
596             ,linestyle='--'\
597             ,linewidth=2"
598        );
599    }
600
601    #[test]
602    fn options_text_works() {
603        let mut icon = SlopeIcon::new();
604        icon.set_text_color("red").set_fontsize(12.0);
605        let (opt_x, opt_y) = icon.options_text();
606        assert_eq!(
607            opt_x,
608            ",transform=tfx\
609             ,color='red'\
610             ,fontsize=12"
611        );
612        assert_eq!(
613            opt_y,
614            ",transform=tfy\
615             ,color='red'\
616             ,fontsize=12"
617        );
618    }
619
620    #[test]
621    fn draw_works() {
622        let mut icon = SlopeIcon::new();
623        icon.set_above(true)
624            .set_edge_color("red")
625            .set_face_color("blue")
626            .set_line_style(":")
627            .set_line_width(1.1)
628            .set_length(0.2)
629            .set_offset_v(3.0)
630            .set_no_text(false)
631            .set_fontsize(4.0)
632            .set_precision(5)
633            .set_text_h("one")
634            .set_text_v("lambda")
635            .set_text_color("gold")
636            .set_text_offset_h(6.0)
637            .set_text_offset_v(7.0)
638            .draw(10.0, 0.5, 0.1);
639        let b: &str = "slope,cx,cy=float(10),float(0.5),float(0.1)\n\
640                       if plt.gca().get_xscale() == 'log': cx=np.log10(cx)\n\
641                       if plt.gca().get_yscale() == 'log': cy=np.log10(cy)\n\
642                       xc,yc=data_to_axis((cx,cy))\n\
643                       xa,ya=data_to_axis((cx+1.0,cy+slope))\n\
644                       m,l=(ya-yc)/(xa-xc),0.1\n\
645                       dat=[[pth.Path.MOVETO,(xc-l,yc-m*l)],[pth.Path.LINETO,(xc-l,yc+m*l)],[pth.Path.LINETO,(xc+l,yc+m*l)],[pth.Path.CLOSEPOLY,(None,None)]]\n\
646                       tf=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=3,units='points')\n\
647                       cmd,pts=zip(*dat)\n\
648                       h=pth.Path(pts,cmd)\n\
649                       p=pat.PathPatch(h,transform=tf,edgecolor='red',facecolor='blue',linestyle=':',linewidth=1.1)\n\
650                       plt.gca().add_patch(p)\n\
651                       xm,ym=xc-l,yc-m*l\n\
652                       xp,yp=xc+l,yc+m*l\n\
653                       tfx=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=0,y=10,units='points')\n\
654                       tfy=tra.offset_copy(plt.gca().transAxes,fig=plt.gcf(),x=-6,y=3,units='points')\n\
655                       plt.text(xc,yp,r'one',ha='center',va='bottom',transform=tfx,color='gold',fontsize=4)\n\
656                       plt.text(xm,yc,r'lambda',ha='right',va='center',transform=tfy,color='gold',fontsize=4)\n";
657        assert_eq!(icon.buffer, b);
658        icon.clear_buffer();
659        assert_eq!(icon.buffer, "");
660    }
661}