plotpy/legend.rs
1use super::{generate_list, GraphMaker};
2use std::fmt::Write;
3
4/// Generates a Legend
5///
6/// [See Matplotlib's documentation](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.legend.html)
7///
8/// # Example
9///
10/// ```
11/// use plotpy::{linspace, Curve, Legend, Plot, StrError};
12///
13/// fn main() -> Result<(), StrError> {
14/// // generate (x,y) points
15/// let x = linspace(0.0, 5.0, 6);
16/// let y1: Vec<_> = x.iter().map(|v| 0.5 * *v).collect();
17/// let y2: Vec<_> = x.iter().map(|v| 1.0 * *v).collect();
18/// let y3: Vec<_> = x.iter().map(|v| 1.5 * *v).collect();
19/// let y4: Vec<_> = x.iter().map(|v| 2.0 * *v).collect();
20///
21/// // configure and draw curves
22/// let mut curve1 = Curve::new();
23/// let mut curve2 = Curve::new();
24/// let mut curve3 = Curve::new();
25/// let mut curve4 = Curve::new();
26/// curve1.set_label("y = 0.5 x");
27/// curve2.set_label("y = 1.0 x");
28/// curve3.set_label("y = 1.5 x");
29/// curve4.set_label("y = 2.0 x");
30/// curve1.draw(&x, &y1);
31/// curve2.draw(&x, &y2);
32/// curve3.draw(&x, &y3);
33/// curve4.draw(&x, &y4);
34///
35/// // configure and draw legends
36/// let mut legend1 = Legend::new();
37/// legend1.set_fontsize(14.0)
38/// .set_handle_len(6.0)
39/// .set_num_col(2)
40/// .set_outside(true)
41/// .set_show_frame(false);
42/// legend1.draw();
43/// let mut legend2 = Legend::new();
44/// legend2.draw();
45///
46/// // add curves and legends to subplots
47/// let mut plot = Plot::new();
48/// plot.set_subplot(2, 1, 1)
49/// .add(&curve1)
50/// .add(&curve2)
51/// .add(&curve3)
52/// .add(&curve4)
53/// .add(&legend1);
54/// plot.set_subplot(2, 1, 2)
55/// .add(&curve1)
56/// .add(&curve2)
57/// .add(&curve3)
58/// .add(&curve4)
59/// .add(&legend2);
60///
61/// // save figure
62/// plot.save("/tmp/plotpy/doc_tests/doc_legend.svg")?;
63/// Ok(())
64/// }
65/// ```
66///
67/// 
68///
69/// See also integration tests in the [tests directory](https://github.com/cpmech/plotpy/tree/main/tests)
70pub struct Legend {
71 fontsize: f64, // Fontsize
72 handle_len: f64, // Length of legend's indicator line
73 num_col: usize, // Number of columns
74 location: String, // Location, e.g., "best", "right", "center left"
75 outside: bool, // Put legend outside plot area
76 show_frame: bool, // Show frame around legend
77 x_coords: Vec<f64>, // Normalized coordinates to put legend outside
78 extra: String, // Extra commands (comma separated)
79 buffer: String, // buffer
80}
81
82impl Legend {
83 /// Creates a new Legend object
84 pub fn new() -> Self {
85 Legend {
86 fontsize: 0.0,
87 handle_len: 3.0,
88 num_col: 1,
89 location: "best".to_string(),
90 outside: false,
91 show_frame: true,
92 x_coords: vec![0.0, 1.02, 1.0, 0.102],
93 extra: String::new(),
94 buffer: String::new(),
95 }
96 }
97
98 /// Draws legend
99 pub fn draw(&mut self) {
100 let opt = self.options();
101 if self.outside {
102 generate_list(&mut self.buffer, "coo", self.x_coords.as_slice());
103 }
104 write!(&mut self.buffer, "h,l=plt.gca().get_legend_handles_labels()\n").unwrap();
105 write!(&mut self.buffer, "if len(h)>0 and len(l)>0:\n").unwrap();
106 write!(&mut self.buffer, " leg=plt.legend({})\n", &opt).unwrap();
107 write!(&mut self.buffer, " add_to_ea(leg)\n").unwrap();
108 if !self.show_frame {
109 write!(&mut self.buffer, " leg.get_frame().set_linewidth(0.0)\n").unwrap();
110 }
111 }
112
113 /// Sets the fontsize
114 pub fn set_fontsize(&mut self, fontsize: f64) -> &mut Self {
115 self.fontsize = fontsize;
116 self
117 }
118
119 /// Sets the length of legend's indicator line
120 pub fn set_handle_len(&mut self, length: f64) -> &mut Self {
121 self.handle_len = length;
122 self
123 }
124
125 /// Sets the number of columns
126 pub fn set_num_col(&mut self, num_columns: usize) -> &mut Self {
127 self.num_col = num_columns;
128 self
129 }
130
131 /// Sets the location
132 ///
133 /// Options:
134 ///
135 /// * "best", "right", "center left"
136 /// * Note: Only used if outside == false
137 pub fn set_location(&mut self, location: &str) -> &mut Self {
138 self.location = String::from(location);
139 self
140 }
141
142 /// Sets option to put legend outside of plot area
143 pub fn set_outside(&mut self, flag: bool) -> &mut Self {
144 self.outside = flag;
145 self
146 }
147
148 /// Sets option to show frame around legend
149 pub fn set_show_frame(&mut self, flag: bool) -> &mut Self {
150 self.show_frame = flag;
151 self
152 }
153
154 /// Sets the normalized coordinates when drawing an outside legend
155 ///
156 /// Example: `[0.0, 1.02, 1.0, 0.102]`
157 pub fn set_x_coords(&mut self, coords: &[f64]) -> &mut Self {
158 self.x_coords = coords.to_vec();
159 self
160 }
161
162 /// Sets extra matplotlib commands (comma separated)
163 ///
164 /// **Important:** The extra commands must be comma separated. For example:
165 ///
166 /// ```text
167 /// param1=123,param2='hello'
168 /// ```
169 ///
170 /// [See Matplotlib's documentation for extra parameters](https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.legend.html)
171 pub fn set_extra(&mut self, extra: &str) -> &mut Self {
172 self.extra = extra.to_string();
173 self
174 }
175
176 /// Returns options for legend
177 fn options(&self) -> String {
178 let mut opt = String::new();
179 let mut comma = "";
180 if self.handle_len > 0.0 {
181 write!(&mut opt, "handlelength={}", self.handle_len).unwrap();
182 comma = ",";
183 }
184 if self.fontsize > 0.0 {
185 write!(&mut opt, "{}prop={{'size':{}}}", comma, self.fontsize).unwrap();
186 comma = ",";
187 }
188 if self.num_col > 0 {
189 write!(&mut opt, "{}ncol={}", comma, self.num_col).unwrap();
190 comma = ",";
191 }
192 if self.outside {
193 write!(
194 &mut opt,
195 "{}loc=3,bbox_to_anchor=coo,mode='expand',borderaxespad=0.0,columnspacing=1,handletextpad=0.05",
196 comma
197 )
198 .unwrap();
199 } else {
200 if self.location != "" {
201 write!(&mut opt, "{}loc='{}'", comma, self.location).unwrap();
202 }
203 }
204 if self.extra != "" {
205 write!(&mut opt, ",{}", self.extra).unwrap();
206 }
207 opt
208 }
209}
210
211impl GraphMaker for Legend {
212 fn get_buffer<'a>(&'a self) -> &'a String {
213 &self.buffer
214 }
215 fn clear_buffer(&mut self) {
216 self.buffer.clear();
217 }
218}
219
220////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
221
222#[cfg(test)]
223mod tests {
224 use super::Legend;
225 use crate::GraphMaker;
226
227 #[test]
228 fn new_works() {
229 let legend = Legend::new();
230 assert_eq!(legend.fontsize, 0.0);
231 assert_eq!(legend.handle_len, 3.0);
232 assert_eq!(legend.num_col, 1);
233 assert_eq!(legend.location, "best".to_string());
234 assert_eq!(legend.outside, false);
235 assert_eq!(legend.show_frame, true);
236 assert_eq!(legend.x_coords, vec![0.0, 1.02, 1.0, 0.102]);
237 assert_eq!(legend.buffer.len(), 0);
238 }
239
240 #[test]
241 fn options_works() {
242 let mut legend = Legend::new();
243 legend.set_handle_len(6.0);
244 let opt = legend.options();
245 assert_eq!(opt, "handlelength=6,ncol=1,loc='best'");
246 }
247
248 #[test]
249 fn draw_works() {
250 let mut legend = Legend::new();
251 legend.draw();
252 let b: &str = "h,l=plt.gca().get_legend_handles_labels()\n\
253 if len(h)>0 and len(l)>0:\n\
254 \x20\x20\x20\x20leg=plt.legend(handlelength=3,ncol=1,loc='best')\n\
255 \x20\x20\x20\x20add_to_ea(leg)\n";
256 assert_eq!(legend.buffer, b);
257 legend.clear_buffer();
258 assert_eq!(legend.buffer, "");
259 }
260}