plotpy/
text.rs

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