• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use super::context::ChartContext;
2 
3 use crate::coord::cartesian::{Cartesian2d, Cartesian3d};
4 use crate::coord::ranged1d::AsRangedCoord;
5 use crate::coord::Shift;
6 
7 use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
8 use crate::style::{IntoTextStyle, SizeDesc, TextStyle};
9 
10 use plotters_backend::DrawingBackend;
11 
12 /// The enum used to specify the position of label area.
13 /// This is used when we configure the label area size with the API
14 /// [ChartBuilder::set_label_area_size](struct ChartBuilder.html#method.set_label_area_size)
15 #[derive(Copy, Clone)]
16 pub enum LabelAreaPosition {
17     Top = 0,
18     Bottom = 1,
19     Left = 2,
20     Right = 3,
21 }
22 
23 /// The helper object to create a chart context, which is used for the high-level figure drawing.
24 /// With the help of this object, we can convert a basic drawing area into a chart context, which
25 /// allows the high-level charting API being used on the drawing area.
26 pub struct ChartBuilder<'a, 'b, DB: DrawingBackend> {
27     label_area_size: [u32; 4], // [upper, lower, left, right]
28     overlap_plotting_area: [bool; 4],
29     root_area: &'a DrawingArea<DB, Shift>,
30     title: Option<(String, TextStyle<'b>)>,
31     margin: [u32; 4],
32 }
33 
34 impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
35     /// Create a chart builder on the given drawing area
36     /// - `root`: The root drawing area
37     /// - Returns: The chart builder object
on(root: &'a DrawingArea<DB, Shift>) -> Self38     pub fn on(root: &'a DrawingArea<DB, Shift>) -> Self {
39         Self {
40             label_area_size: [0; 4],
41             root_area: root,
42             title: None,
43             margin: [0; 4],
44             overlap_plotting_area: [false; 4],
45         }
46     }
47 
48     /// Set the margin size of the chart (applied for top, bottom, left and right at the same time)
49     /// - `size`: The size of the chart margin.
margin<S: SizeDesc>(&mut self, size: S) -> &mut Self50     pub fn margin<S: SizeDesc>(&mut self, size: S) -> &mut Self {
51         let size = size.in_pixels(self.root_area).max(0) as u32;
52         self.margin = [size, size, size, size];
53         self
54     }
55 
56     /// Set the top margin of current chart
57     /// - `size`: The size of the top margin.
margin_top<S: SizeDesc>(&mut self, size: S) -> &mut Self58     pub fn margin_top<S: SizeDesc>(&mut self, size: S) -> &mut Self {
59         let size = size.in_pixels(self.root_area).max(0) as u32;
60         self.margin[0] = size;
61         self
62     }
63 
64     /// Set the bottom margin of current chart
65     /// - `size`: The size of the bottom margin.
margin_bottom<S: SizeDesc>(&mut self, size: S) -> &mut Self66     pub fn margin_bottom<S: SizeDesc>(&mut self, size: S) -> &mut Self {
67         let size = size.in_pixels(self.root_area).max(0) as u32;
68         self.margin[1] = size;
69         self
70     }
71 
72     /// Set the left margin of current chart
73     /// - `size`: The size of the left margin.
margin_left<S: SizeDesc>(&mut self, size: S) -> &mut Self74     pub fn margin_left<S: SizeDesc>(&mut self, size: S) -> &mut Self {
75         let size = size.in_pixels(self.root_area).max(0) as u32;
76         self.margin[2] = size;
77         self
78     }
79 
80     /// Set the right margin of current chart
81     /// - `size`: The size of the right margin.
margin_right<S: SizeDesc>(&mut self, size: S) -> &mut Self82     pub fn margin_right<S: SizeDesc>(&mut self, size: S) -> &mut Self {
83         let size = size.in_pixels(self.root_area).max(0) as u32;
84         self.margin[3] = size;
85         self
86     }
87 
88     /// Set all the label area size with the same value
set_all_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self89     pub fn set_all_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
90         let size = size.in_pixels(self.root_area);
91         self.set_label_area_size(LabelAreaPosition::Top, size)
92             .set_label_area_size(LabelAreaPosition::Bottom, size)
93             .set_label_area_size(LabelAreaPosition::Left, size)
94             .set_label_area_size(LabelAreaPosition::Right, size)
95     }
96 
97     /// Set the most commonly used label area size to the same value
set_left_and_bottom_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self98     pub fn set_left_and_bottom_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
99         let size = size.in_pixels(self.root_area);
100         self.set_label_area_size(LabelAreaPosition::Left, size)
101             .set_label_area_size(LabelAreaPosition::Bottom, size)
102     }
103 
104     /// Set the size of X label area
105     /// - `size`: The height of the x label area, if x is 0, the chart doesn't have the X label area
x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self106     pub fn x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
107         self.set_label_area_size(LabelAreaPosition::Bottom, size)
108     }
109 
110     /// Set the size of the Y label area
111     /// - `size`: The width of the Y label area. If size is 0, the chart doesn't have Y label area
y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self112     pub fn y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
113         self.set_label_area_size(LabelAreaPosition::Left, size)
114     }
115 
116     /// Set the size of X label area on the top of the chart
117     /// - `size`: The height of the x label area, if x is 0, the chart doesn't have the X label area
top_x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self118     pub fn top_x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
119         self.set_label_area_size(LabelAreaPosition::Top, size)
120     }
121 
122     /// Set the size of the Y label area on the right side
123     /// - `size`: The width of the Y label area. If size is 0, the chart doesn't have Y label area
right_y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self124     pub fn right_y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
125         self.set_label_area_size(LabelAreaPosition::Right, size)
126     }
127 
128     /// Set a label area size
129     /// - `pos`: THe position where the label area located
130     /// - `size`: The size of the label area size
set_label_area_size<S: SizeDesc>( &mut self, pos: LabelAreaPosition, size: S, ) -> &mut Self131     pub fn set_label_area_size<S: SizeDesc>(
132         &mut self,
133         pos: LabelAreaPosition,
134         size: S,
135     ) -> &mut Self {
136         let size = size.in_pixels(self.root_area);
137         self.label_area_size[pos as usize] = size.abs() as u32;
138         self.overlap_plotting_area[pos as usize] = size < 0;
139         self
140     }
141 
142     /// Set the caption of the chart
143     /// - `caption`: The caption of the chart
144     /// - `style`: The text style
145     /// - Note: If the caption is set, the margin option will be ignored
caption<S: AsRef<str>, Style: IntoTextStyle<'b>>( &mut self, caption: S, style: Style, ) -> &mut Self146     pub fn caption<S: AsRef<str>, Style: IntoTextStyle<'b>>(
147         &mut self,
148         caption: S,
149         style: Style,
150     ) -> &mut Self {
151         self.title = Some((
152             caption.as_ref().to_string(),
153             style.into_text_style(self.root_area),
154         ));
155         self
156     }
157 
158     #[allow(clippy::type_complexity)]
159     #[deprecated(
160         note = "`build_ranged` has been renamed to `build_cartesian_2d` and is to be removed in the future."
161     )]
build_ranged<X: AsRangedCoord, Y: AsRangedCoord>( &mut self, x_spec: X, y_spec: Y, ) -> Result< ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>, DrawingAreaErrorKind<DB::ErrorType>, >162     pub fn build_ranged<X: AsRangedCoord, Y: AsRangedCoord>(
163         &mut self,
164         x_spec: X,
165         y_spec: Y,
166     ) -> Result<
167         ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
168         DrawingAreaErrorKind<DB::ErrorType>,
169     > {
170         self.build_cartesian_2d(x_spec, y_spec)
171     }
172 
173     /// Build the chart with a 2D Cartesian coordinate system. The function will returns a chart
174     /// context, where data series can be rendered on.
175     /// - `x_spec`: The specification of X axis
176     /// - `y_spec`: The specification of Y axis
177     /// - Returns: A chart context
178     #[allow(clippy::type_complexity)]
build_cartesian_2d<X: AsRangedCoord, Y: AsRangedCoord>( &mut self, x_spec: X, y_spec: Y, ) -> Result< ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>, DrawingAreaErrorKind<DB::ErrorType>, >179     pub fn build_cartesian_2d<X: AsRangedCoord, Y: AsRangedCoord>(
180         &mut self,
181         x_spec: X,
182         y_spec: Y,
183     ) -> Result<
184         ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
185         DrawingAreaErrorKind<DB::ErrorType>,
186     > {
187         let mut label_areas = [None, None, None, None];
188 
189         let mut drawing_area = DrawingArea::clone(self.root_area);
190 
191         if *self.margin.iter().max().unwrap_or(&0) > 0 {
192             drawing_area = drawing_area.margin(
193                 self.margin[0] as i32,
194                 self.margin[1] as i32,
195                 self.margin[2] as i32,
196                 self.margin[3] as i32,
197             );
198         }
199 
200         let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
201             let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
202             drawing_area = drawing_area.titled(title, style.clone())?;
203             let (current_dx, current_dy) = drawing_area.get_base_pixel();
204             (current_dx - origin_dx, current_dy - origin_dy)
205         } else {
206             (0, 0)
207         };
208 
209         let (w, h) = drawing_area.dim_in_pixel();
210 
211         let mut actual_drawing_area_pos = [0, h as i32, 0, w as i32];
212 
213         const DIR: [(i16, i16); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];
214 
215         for (idx, (dx, dy)) in (0..4).map(|idx| (idx, DIR[idx])) {
216             if self.overlap_plotting_area[idx] {
217                 continue;
218             }
219 
220             let size = self.label_area_size[idx] as i32;
221 
222             let split_point = if dx + dy < 0 { size } else { -size };
223 
224             actual_drawing_area_pos[idx] += split_point;
225         }
226 
227         // Now the root drawing area is to be split into
228         //
229         // +----------+------------------------------+------+
230         // |    0     |    1 (Top Label Area)        |   2  |
231         // +----------+------------------------------+------+
232         // |    3     |                              |   5  |
233         // |  Left    |       4 (Plotting Area)      | Right|
234         // |  Labels  |                              | Label|
235         // +----------+------------------------------+------+
236         // |    6     |        7 (Bottom Labels)     |   8  |
237         // +----------+------------------------------+------+
238 
239         let mut split: Vec<_> = drawing_area
240             .split_by_breakpoints(
241                 &actual_drawing_area_pos[2..4],
242                 &actual_drawing_area_pos[0..2],
243             )
244             .into_iter()
245             .map(Some)
246             .collect();
247 
248         // Take out the plotting area
249         std::mem::swap(&mut drawing_area, split[4].as_mut().unwrap());
250 
251         // Initialize the label areas - since the label area might be overlapping
252         // with the plotting area, in this case, we need handle them differently
253         for (src_idx, dst_idx) in [1, 7, 3, 5].iter().zip(0..4) {
254             if !self.overlap_plotting_area[dst_idx] {
255                 let (h, w) = split[*src_idx].as_ref().unwrap().dim_in_pixel();
256                 if h > 0 && w > 0 {
257                     std::mem::swap(&mut label_areas[dst_idx], &mut split[*src_idx]);
258                 }
259             } else if self.label_area_size[dst_idx] != 0 {
260                 let size = self.label_area_size[dst_idx] as i32;
261                 let (dw, dh) = drawing_area.dim_in_pixel();
262                 let x0 = if DIR[dst_idx].0 > 0 {
263                     dw as i32 - size
264                 } else {
265                     0
266                 };
267                 let y0 = if DIR[dst_idx].1 > 0 {
268                     dh as i32 - size
269                 } else {
270                     0
271                 };
272                 let x1 = if DIR[dst_idx].0 >= 0 { dw as i32 } else { size };
273                 let y1 = if DIR[dst_idx].1 >= 0 { dh as i32 } else { size };
274 
275                 label_areas[dst_idx] = Some(
276                     drawing_area
277                         .clone()
278                         .shrink((x0, y0), ((x1 - x0), (y1 - y0))),
279                 );
280             }
281         }
282 
283         let mut pixel_range = drawing_area.get_pixel_range();
284         pixel_range.1 = (pixel_range.1.end - 1)..(pixel_range.1.start - 1);
285 
286         let mut x_label_area = [None, None];
287         let mut y_label_area = [None, None];
288 
289         std::mem::swap(&mut x_label_area[0], &mut label_areas[0]);
290         std::mem::swap(&mut x_label_area[1], &mut label_areas[1]);
291         std::mem::swap(&mut y_label_area[0], &mut label_areas[2]);
292         std::mem::swap(&mut y_label_area[1], &mut label_areas[3]);
293 
294         Ok(ChartContext {
295             x_label_area,
296             y_label_area,
297             drawing_area: drawing_area.apply_coord_spec(Cartesian2d::new(
298                 x_spec,
299                 y_spec,
300                 pixel_range,
301             )),
302             series_anno: vec![],
303             drawing_area_pos: (
304                 actual_drawing_area_pos[2] + title_dx + self.margin[2] as i32,
305                 actual_drawing_area_pos[0] + title_dy + self.margin[0] as i32,
306             ),
307         })
308     }
309 
310     /// Build a 3 dimensional cartesian chart. The function will returns a chart
311     /// context, where data series can be rendered on.
312     /// - `x_spec`: The specification of X axis
313     /// - `y_spec`: The specification of Y axis
314     /// - `z_sepc`: The specification of Z axis
315     /// - Returns: A chart context
build_cartesian_3d<X: AsRangedCoord, Y: AsRangedCoord, Z: AsRangedCoord>( &mut self, x_spec: X, y_spec: Y, z_spec: Z, ) -> Result< ChartContext<'a, DB, Cartesian3d<X::CoordDescType, Y::CoordDescType, Z::CoordDescType>>, DrawingAreaErrorKind<DB::ErrorType>, >316     pub fn build_cartesian_3d<X: AsRangedCoord, Y: AsRangedCoord, Z: AsRangedCoord>(
317         &mut self,
318         x_spec: X,
319         y_spec: Y,
320         z_spec: Z,
321     ) -> Result<
322         ChartContext<'a, DB, Cartesian3d<X::CoordDescType, Y::CoordDescType, Z::CoordDescType>>,
323         DrawingAreaErrorKind<DB::ErrorType>,
324     > {
325         let mut drawing_area = DrawingArea::clone(self.root_area);
326 
327         if *self.margin.iter().max().unwrap_or(&0) > 0 {
328             drawing_area = drawing_area.margin(
329                 self.margin[0] as i32,
330                 self.margin[1] as i32,
331                 self.margin[2] as i32,
332                 self.margin[3] as i32,
333             );
334         }
335 
336         let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
337             let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
338             drawing_area = drawing_area.titled(title, style.clone())?;
339             let (current_dx, current_dy) = drawing_area.get_base_pixel();
340             (current_dx - origin_dx, current_dy - origin_dy)
341         } else {
342             (0, 0)
343         };
344 
345         let pixel_range = drawing_area.get_pixel_range();
346 
347         Ok(ChartContext {
348             x_label_area: [None, None],
349             y_label_area: [None, None],
350             drawing_area: drawing_area.apply_coord_spec(Cartesian3d::new(
351                 x_spec,
352                 y_spec,
353                 z_spec,
354                 pixel_range,
355             )),
356             series_anno: vec![],
357             drawing_area_pos: (
358                 title_dx + self.margin[2] as i32,
359                 title_dy + self.margin[0] as i32,
360             ),
361         })
362     }
363 }
364 
365 #[cfg(test)]
366 mod test {
367     use super::*;
368     use crate::prelude::*;
369     #[test]
test_label_area_size()370     fn test_label_area_size() {
371         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
372         let mut chart = ChartBuilder::on(&drawing_area);
373 
374         chart
375             .x_label_area_size(10)
376             .y_label_area_size(20)
377             .top_x_label_area_size(30)
378             .right_y_label_area_size(40);
379         assert_eq!(chart.label_area_size[1], 10);
380         assert_eq!(chart.label_area_size[2], 20);
381         assert_eq!(chart.label_area_size[0], 30);
382         assert_eq!(chart.label_area_size[3], 40);
383 
384         chart.set_label_area_size(LabelAreaPosition::Left, 100);
385         chart.set_label_area_size(LabelAreaPosition::Right, 200);
386         chart.set_label_area_size(LabelAreaPosition::Top, 300);
387         chart.set_label_area_size(LabelAreaPosition::Bottom, 400);
388 
389         assert_eq!(chart.label_area_size[0], 300);
390         assert_eq!(chart.label_area_size[1], 400);
391         assert_eq!(chart.label_area_size[2], 100);
392         assert_eq!(chart.label_area_size[3], 200);
393     }
394 
395     #[test]
test_margin_configure()396     fn test_margin_configure() {
397         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
398         let mut chart = ChartBuilder::on(&drawing_area);
399 
400         chart.margin(5);
401         assert_eq!(chart.margin[0], 5);
402         assert_eq!(chart.margin[1], 5);
403         assert_eq!(chart.margin[2], 5);
404         assert_eq!(chart.margin[3], 5);
405 
406         chart.margin_top(10);
407         chart.margin_bottom(11);
408         chart.margin_left(12);
409         chart.margin_right(13);
410         assert_eq!(chart.margin[0], 10);
411         assert_eq!(chart.margin[1], 11);
412         assert_eq!(chart.margin[2], 12);
413         assert_eq!(chart.margin[3], 13);
414     }
415 
416     #[test]
test_caption()417     fn test_caption() {
418         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
419         let mut chart = ChartBuilder::on(&drawing_area);
420 
421         chart.caption("This is a test case", ("serif", 10));
422 
423         assert_eq!(chart.title.as_ref().unwrap().0, "This is a test case");
424         assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
425         assert_eq!(chart.title.as_ref().unwrap().1.font.get_size(), 10.0);
426         check_color(chart.title.as_ref().unwrap().1.color, BLACK.to_rgba());
427 
428         chart.caption("This is a test case", ("serif", 10));
429         assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
430     }
431 }
432