plotpy/
text.rs

1use super::GraphMaker;
2use std::fmt::Write;
3
4/// Creates text to be added to a plot
5///
6/// [See Matplotlib's documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html)
7///
8/// # Example
9///
10/// ```
11/// use plotpy::{Plot, Text, StrError};
12/// use std::path::Path;
13///
14/// fn main() -> Result<(), StrError> {
15///     // configure text
16///     let mut text = Text::new();
17///     text.set_color("purple")
18///         .set_align_horizontal("center")
19///         .set_align_vertical("center")
20///         .set_fontsize(30.0)
21///         .set_bbox(true)
22///         .set_bbox_facecolor("pink")
23///         .set_bbox_edgecolor("black")
24///         .set_bbox_alpha(0.3)
25///         .set_bbox_style("roundtooth,pad=0.3,tooth_size=0.2");
26///
27///     // draw text
28///     text.draw_3d(0.5, 0.5, 0.5, "Hello World!");
29///
30///     // add text to plot
31///     let mut plot = Plot::new();
32///     plot.add(&text);
33///
34///     // save figure
35///     plot.set_save_pad_inches(0.25)
36///         .save("/tmp/plotpy/doc_tests/doc_text.svg")?;
37///     Ok(())
38/// }
39/// ```
40///
41/// ![doc_text.svg](https://raw.githubusercontent.com/cpmech/plotpy/main/figures/doc_text.svg)
42pub struct Text {
43    // text
44    color: String,            // Color
45    align_horizontal: String, // Horizontal alignment
46    align_vertical: String,   // Vertical alignment
47    fontsize: f64,            // Font size
48    rotation: f64,            // Text rotation
49
50    // bounding box
51    bbox: bool,             // Use bounding box
52    bbox_facecolor: String, // Facecolor of bounding box
53    bbox_edgecolor: String, // Edgecolor of bounding box
54    bbox_alpha: f64,        // Alpha of bounding box
55    bbox_style: String,     // Style of bounding box; example "round,pad=0.2"
56
57    // extra and buffer
58    extra: String,  // Extra commands (comma separated)
59    buffer: String, // buffer
60}
61
62impl Text {
63    /// Creates a new Text object
64    pub fn new() -> Self {
65        Text {
66            color: String::new(),
67            align_horizontal: String::new(),
68            align_vertical: String::new(),
69            fontsize: 0.0,
70            rotation: 0.0,
71            bbox: false,
72            bbox_facecolor: String::new(),
73            bbox_edgecolor: String::new(),
74            bbox_alpha: 1.0,
75            bbox_style: String::new(),
76            extra: String::new(),
77            buffer: String::new(),
78        }
79    }
80
81    /// Draws text
82    pub fn draw(&mut self, x: f64, y: f64, message: &str) {
83        let opt = self.options();
84        write!(&mut self.buffer, "t=plt.text({},{},r'{}'{})\n", x, y, message, &opt).unwrap();
85        if self.bbox {
86            let opt_bbox = self.options_bbox();
87            write!(&mut self.buffer, "t.set_bbox(dict({}))\n", opt_bbox).unwrap();
88        }
89    }
90
91    /// Draws text in 3D plot
92    pub fn draw_3d(&mut self, x: f64, y: f64, z: f64, message: &str) {
93        let opt = self.options();
94        write!(
95            &mut self.buffer,
96            "t=ax3d().text({},{},{},r'{}'{})\n",
97            x, y, z, message, &opt
98        )
99        .unwrap();
100        if self.bbox {
101            let opt_bbox = self.options_bbox();
102            write!(&mut self.buffer, "t.set_bbox(dict({}))\n", opt_bbox).unwrap();
103        }
104    }
105
106    /// Sets the text color
107    pub fn set_color(&mut self, color: &str) -> &mut Self {
108        self.color = String::from(color);
109        self
110    }
111
112    /// Sets the horizontal alignment
113    ///
114    /// Options: "center", "left", "right"
115    pub fn set_align_horizontal(&mut self, option: &str) -> &mut Self {
116        self.align_horizontal = String::from(option);
117        self
118    }
119
120    /// Sets the vertical alignment
121    ///
122    /// Options: "center", "top", "bottom", "baseline", "center_baseline"
123    pub fn set_align_vertical(&mut self, option: &str) -> &mut Self {
124        self.align_vertical = String::from(option);
125        self
126    }
127
128    /// Sets the font size
129    pub fn set_fontsize(&mut self, fontsize: f64) -> &mut Self {
130        self.fontsize = fontsize;
131        self
132    }
133
134    /// Sets the text rotation (2D only)
135    pub fn set_rotation(&mut self, rotation: f64) -> &mut Self {
136        self.rotation = rotation;
137        self
138    }
139
140    /// Sets use bounding box flag
141    pub fn set_bbox(&mut self, flag: bool) -> &mut Self {
142        self.bbox = flag;
143        self
144    }
145
146    /// Sets facecolor of bounding box
147    pub fn set_bbox_facecolor(&mut self, color: &str) -> &mut Self {
148        self.bbox_facecolor = String::from(color);
149        self
150    }
151
152    /// Sets edgecolor of bounding box
153    pub fn set_bbox_edgecolor(&mut self, color: &str) -> &mut Self {
154        self.bbox_edgecolor = String::from(color);
155        self
156    }
157
158    /// Sets alpha of bounding box
159    pub fn set_bbox_alpha(&mut self, value: f64) -> &mut Self {
160        self.bbox_alpha = value;
161        self
162    }
163
164    /// Sets style of bounding box; example
165    ///
166    /// Examples:
167    ///
168    /// * "square,pad=0.3"
169    /// * "circle,pad=0.3"
170    /// * "larrow,pad=0.3"
171    /// * "rarrow,pad=0.3"
172    /// * "darrow,pad=0.3"
173    /// * "round,pad=0.3,rounding_size=0.15"
174    /// * "round4,pad=0.3,rounding_size=0.2"
175    /// * "sawtooth,pad=0.3,tooth_size=0.1"
176    /// * "roundtooth,pad=0.3,tooth_size=0.2"
177    ///
178    /// See [Matplotlib](https://matplotlib.org/stable/api/_as_gen/matplotlib.patches.BoxStyle.html)
179    pub fn set_bbox_style(&mut self, style: &str) -> &mut Self {
180        self.bbox_style = String::from(style);
181        self
182    }
183
184    /// Sets extra matplotlib commands (comma separated)
185    ///
186    /// **Important:** The extra commands must be comma separated. For example:
187    ///
188    /// ```text
189    /// param1=123,param2='hello'
190    /// ```
191    ///
192    /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.text.html)
193    pub fn set_extra(&mut self, extra: &str) -> &mut Self {
194        self.extra = extra.to_string();
195        self
196    }
197
198    /// Returns options for text
199    fn options(&self) -> String {
200        let mut opt = String::new();
201        if self.color != "" {
202            write!(&mut opt, ",color='{}'", self.color).unwrap();
203        }
204        if self.align_horizontal != "" {
205            write!(&mut opt, ",ha='{}'", self.align_horizontal).unwrap();
206        }
207        if self.align_vertical != "" {
208            write!(&mut opt, ",va='{}'", self.align_vertical).unwrap();
209        }
210        if self.fontsize > 0.0 {
211            write!(&mut opt, ",fontsize={}", self.fontsize).unwrap();
212        }
213        if self.rotation > 0.0 {
214            write!(&mut opt, ",rotation={}", self.rotation).unwrap();
215        }
216        if self.extra != "" {
217            write!(&mut opt, ",{}", self.extra).unwrap();
218        }
219        opt
220    }
221
222    /// Returns options for bounding box
223    fn options_bbox(&self) -> String {
224        let mut opt = String::new();
225        if self.bbox_facecolor != "" {
226            write!(&mut opt, "facecolor='{}',", self.bbox_facecolor).unwrap();
227        }
228        if self.bbox_edgecolor != "" {
229            write!(&mut opt, "edgecolor='{}',", self.bbox_edgecolor).unwrap();
230        }
231        write!(&mut opt, "alpha={},", self.bbox_alpha).unwrap();
232        if self.bbox_style != "" {
233            write!(&mut opt, "boxstyle='{}',", self.bbox_style).unwrap();
234        }
235        opt
236    }
237}
238
239impl GraphMaker for Text {
240    fn get_buffer<'a>(&'a self) -> &'a String {
241        &self.buffer
242    }
243    fn clear_buffer(&mut self) {
244        self.buffer.clear();
245    }
246}
247
248////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
249
250#[cfg(test)]
251mod tests {
252    use super::Text;
253    use crate::GraphMaker;
254
255    #[test]
256    fn new_works() {
257        let text = Text::new();
258        assert_eq!(text.color.len(), 0);
259        assert_eq!(text.align_horizontal.len(), 0);
260        assert_eq!(text.align_vertical.len(), 0);
261        assert_eq!(text.fontsize, 0.0);
262        assert_eq!(text.rotation, 0.0);
263        assert_eq!(text.buffer.len(), 0);
264    }
265
266    #[test]
267    fn options_works() {
268        let mut text = Text::new();
269        text.set_color("red")
270            .set_align_horizontal("center")
271            .set_align_vertical("center")
272            .set_fontsize(8.0)
273            .set_rotation(45.0);
274        let opt = text.options();
275        assert_eq!(
276            opt,
277            ",color='red'\
278             ,ha='center'\
279             ,va='center'\
280             ,fontsize=8\
281             ,rotation=45"
282        );
283    }
284
285    #[test]
286    fn options_box_works() {
287        let mut text = Text::new();
288        text.set_bbox(true)
289            .set_bbox_facecolor("pink")
290            .set_bbox_edgecolor("black")
291            .set_bbox_alpha(0.3)
292            .set_bbox_style("round,pad=0.4");
293        assert_eq!(text.bbox, true);
294        let opt = text.options_bbox();
295        assert_eq!(
296            opt,
297            "facecolor='pink',\
298             edgecolor='black',\
299             alpha=0.3,\
300             boxstyle='round,pad=0.4',"
301        );
302    }
303
304    #[test]
305    fn draw_works() {
306        let mut text = Text::new();
307        text.draw(1.2, 3.4, &"message".to_string());
308        let b: &str = "t=plt.text(1.2,3.4,r'message')\n";
309        assert_eq!(text.buffer, b);
310        text.clear_buffer();
311        assert_eq!(text.buffer, "");
312    }
313
314    #[test]
315    fn draw_3d_works() {
316        let mut text = Text::new();
317        text.draw_3d(1.2, 3.4, 5.6, &"message".to_string());
318        let b: &str = "t=ax3d().text(1.2,3.4,5.6,r'message')\n";
319        assert_eq!(text.buffer, b);
320    }
321}