• 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 /**
13 Specifies one of the four label positions around the figure.
14 
15 This is used to configure the label area size with function
16 [`ChartBuilder::set_label_area_size()`].
17 
18 # Example
19 
20 ```
21 use plotters::prelude::*;
22 let drawing_area = SVGBackend::new("label_area_position.svg", (300, 200)).into_drawing_area();
23 drawing_area.fill(&WHITE).unwrap();
24 let mut chart_builder = ChartBuilder::on(&drawing_area);
25 chart_builder.set_label_area_size(LabelAreaPosition::Bottom, 60).set_label_area_size(LabelAreaPosition::Left, 35);
26 let mut chart_context = chart_builder.build_cartesian_2d(0.0..4.0, 0.0..3.0).unwrap();
27 chart_context.configure_mesh().x_desc("Spacious X label area").y_desc("Narrow Y label area").draw().unwrap();
28 ```
29 
30 The result is a chart with a spacious X label area and a narrow Y label area:
31 
32 ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@9ca6541/apidoc/label_area_position.svg)
33 
34 # See also
35 
36 [`ChartBuilder::set_left_and_bottom_label_area_size()`]
37 */
38 #[derive(Copy, Clone)]
39 pub enum LabelAreaPosition {
40     /// Top of the figure
41     Top = 0,
42     /// Bottom of the figure
43     Bottom = 1,
44     /// Left side of the figure
45     Left = 2,
46     /// Right side of the figure
47     Right = 3,
48 }
49 
50 /**
51 The helper object to create a chart context, which is used for the high-level figure drawing.
52 
53 With the help of this object, we can convert a basic drawing area into a chart context, which
54 allows the high-level charting API being used on the drawing area.
55 
56 See [`ChartBuilder::on()`] for more information and examples.
57 */
58 pub struct ChartBuilder<'a, 'b, DB: DrawingBackend> {
59     label_area_size: [u32; 4], // [upper, lower, left, right]
60     overlap_plotting_area: [bool; 4],
61     root_area: &'a DrawingArea<DB, Shift>,
62     title: Option<(String, TextStyle<'b>)>,
63     margin: [u32; 4],
64 }
65 
66 impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
67     /**
68     Create a chart builder on the given drawing area
69 
70     - `root`: The root drawing area
71     - Returns: The chart builder object
72 
73     # Example
74 
75     ```
76     use plotters::prelude::*;
77     let drawing_area = SVGBackend::new("chart_builder_on.svg", (300, 200)).into_drawing_area();
78     drawing_area.fill(&WHITE).unwrap();
79     let mut chart_builder = ChartBuilder::on(&drawing_area);
80     chart_builder.margin(5).set_left_and_bottom_label_area_size(35)
81     .caption("Figure title or caption", ("Calibri", 20, FontStyle::Italic, &RED).into_text_style(&drawing_area));
82     let mut chart_context = chart_builder.build_cartesian_2d(0.0..3.8, 0.0..2.8).unwrap();
83     chart_context.configure_mesh().draw().unwrap();
84     ```
85     The result is a chart with customized margins, label area sizes, and title:
86 
87     ![](https://cdn.jsdelivr.net/gh/facorread/plotters-doc-data@42ecf52/apidoc/chart_builder_on.svg)
88 
89     */
on(root: &'a DrawingArea<DB, Shift>) -> Self90     pub fn on(root: &'a DrawingArea<DB, Shift>) -> Self {
91         Self {
92             label_area_size: [0; 4],
93             root_area: root,
94             title: None,
95             margin: [0; 4],
96             overlap_plotting_area: [false; 4],
97         }
98     }
99 
100     /**
101     Sets the size of the four margins of the chart.
102 
103     - `size`: The desired size of the four chart margins in backend units (pixels).
104 
105     See [`ChartBuilder::on()`] for more information and examples.
106     */
margin<S: SizeDesc>(&mut self, size: S) -> &mut Self107     pub fn margin<S: SizeDesc>(&mut self, size: S) -> &mut Self {
108         let size = size.in_pixels(self.root_area).max(0) as u32;
109         self.margin = [size, size, size, size];
110         self
111     }
112 
113     /**
114     Sets the size of the top margin of the chart.
115 
116     - `size`: The desired size of the margin in backend units (pixels).
117 
118     See [`ChartBuilder::on()`] for more information and examples.
119     */
margin_top<S: SizeDesc>(&mut self, size: S) -> &mut Self120     pub fn margin_top<S: SizeDesc>(&mut self, size: S) -> &mut Self {
121         let size = size.in_pixels(self.root_area).max(0) as u32;
122         self.margin[0] = size;
123         self
124     }
125 
126     /**
127     Sets the size of the bottom margin of the chart.
128 
129     - `size`: The desired size of the margin in backend units (pixels).
130 
131     See [`ChartBuilder::on()`] for more information and examples.
132     */
margin_bottom<S: SizeDesc>(&mut self, size: S) -> &mut Self133     pub fn margin_bottom<S: SizeDesc>(&mut self, size: S) -> &mut Self {
134         let size = size.in_pixels(self.root_area).max(0) as u32;
135         self.margin[1] = size;
136         self
137     }
138 
139     /**
140     Sets the size of the left margin of the chart.
141 
142     - `size`: The desired size of the margin in backend units (pixels).
143 
144     See [`ChartBuilder::on()`] for more information and examples.
145     */
margin_left<S: SizeDesc>(&mut self, size: S) -> &mut Self146     pub fn margin_left<S: SizeDesc>(&mut self, size: S) -> &mut Self {
147         let size = size.in_pixels(self.root_area).max(0) as u32;
148         self.margin[2] = size;
149         self
150     }
151 
152     /**
153     Sets the size of the right margin of the chart.
154 
155     - `size`: The desired size of the margin in backend units (pixels).
156 
157     See [`ChartBuilder::on()`] for more information and examples.
158     */
margin_right<S: SizeDesc>(&mut self, size: S) -> &mut Self159     pub fn margin_right<S: SizeDesc>(&mut self, size: S) -> &mut Self {
160         let size = size.in_pixels(self.root_area).max(0) as u32;
161         self.margin[3] = size;
162         self
163     }
164 
165     /**
166     Sets the size of the four label areas of the chart.
167 
168     - `size`: The desired size of the four label areas in backend units (pixels).
169 
170     See [`ChartBuilder::on()`] for more information and examples.
171     */
set_all_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self172     pub fn set_all_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
173         let size = size.in_pixels(self.root_area);
174         self.set_label_area_size(LabelAreaPosition::Top, size)
175             .set_label_area_size(LabelAreaPosition::Bottom, size)
176             .set_label_area_size(LabelAreaPosition::Left, size)
177             .set_label_area_size(LabelAreaPosition::Right, size)
178     }
179 
180     /**
181     Sets the size of the left and bottom label areas of the chart.
182 
183     - `size`: The desired size of the left and bottom label areas in backend units (pixels).
184 
185     See [`ChartBuilder::on()`] for more information and examples.
186     */
set_left_and_bottom_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self187     pub fn set_left_and_bottom_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
188         let size = size.in_pixels(self.root_area);
189         self.set_label_area_size(LabelAreaPosition::Left, size)
190             .set_label_area_size(LabelAreaPosition::Bottom, size)
191     }
192 
193     /**
194     Sets the size of the X label area at the bottom of the chart.
195 
196     - `size`: The desired size of the X label area in backend units (pixels).
197     If set to 0, the X label area is removed.
198 
199     See [`ChartBuilder::on()`] for more information and examples.
200     */
x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self201     pub fn x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
202         self.set_label_area_size(LabelAreaPosition::Bottom, size)
203     }
204 
205     /**
206     Sets the size of the Y label area to the left of the chart.
207 
208     - `size`: The desired size of the Y label area in backend units (pixels).
209     If set to 0, the Y label area is removed.
210 
211     See [`ChartBuilder::on()`] for more information and examples.
212     */
y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self213     pub fn y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
214         self.set_label_area_size(LabelAreaPosition::Left, size)
215     }
216 
217     /**
218     Sets the size of the X label area at the top of the chart.
219 
220     - `size`: The desired size of the top X label area in backend units (pixels).
221     If set to 0, the top X label area is removed.
222 
223     See [`ChartBuilder::on()`] for more information and examples.
224     */
top_x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self225     pub fn top_x_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
226         self.set_label_area_size(LabelAreaPosition::Top, size)
227     }
228 
229     /**
230     Sets the size of the Y label area to the right of the chart.
231 
232     - `size`: The desired size of the Y label area in backend units (pixels).
233     If set to 0, the Y label area to the right is removed.
234 
235     See [`ChartBuilder::on()`] for more information and examples.
236     */
right_y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self237     pub fn right_y_label_area_size<S: SizeDesc>(&mut self, size: S) -> &mut Self {
238         self.set_label_area_size(LabelAreaPosition::Right, size)
239     }
240 
241     /**
242     Sets the size of a chart label area.
243 
244     - `pos`: The position of the desired label area to adjust
245     - `size`: The desired size of the label area in backend units (pixels).
246     If set to 0, the label area is removed.
247 
248     See [`ChartBuilder::on()`] for more information and examples.
249     */
set_label_area_size<S: SizeDesc>( &mut self, pos: LabelAreaPosition, size: S, ) -> &mut Self250     pub fn set_label_area_size<S: SizeDesc>(
251         &mut self,
252         pos: LabelAreaPosition,
253         size: S,
254     ) -> &mut Self {
255         let size = size.in_pixels(self.root_area);
256         self.label_area_size[pos as usize] = size.unsigned_abs();
257         self.overlap_plotting_area[pos as usize] = size < 0;
258         self
259     }
260 
261     /**
262     Sets the title or caption of the chart.
263 
264     - `caption`: The caption of the chart
265     - `style`: The text style
266 
267     The title or caption will be centered at the top of the drawing area.
268 
269     See [`ChartBuilder::on()`] for more information and examples.
270     */
caption<S: AsRef<str>, Style: IntoTextStyle<'b>>( &mut self, caption: S, style: Style, ) -> &mut Self271     pub fn caption<S: AsRef<str>, Style: IntoTextStyle<'b>>(
272         &mut self,
273         caption: S,
274         style: Style,
275     ) -> &mut Self {
276         self.title = Some((
277             caption.as_ref().to_string(),
278             style.into_text_style(self.root_area),
279         ));
280         self
281     }
282 
283     /// This function has been renamed to [`ChartBuilder::build_cartesian_2d()`] and is to be removed in the future.
284     #[allow(clippy::type_complexity)]
285     #[deprecated(
286         note = "`build_ranged` has been renamed to `build_cartesian_2d` and is to be removed in the future."
287     )]
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>, >288     pub fn build_ranged<X: AsRangedCoord, Y: AsRangedCoord>(
289         &mut self,
290         x_spec: X,
291         y_spec: Y,
292     ) -> Result<
293         ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
294         DrawingAreaErrorKind<DB::ErrorType>,
295     > {
296         self.build_cartesian_2d(x_spec, y_spec)
297     }
298 
299     /**
300     Builds a chart with a 2D Cartesian coordinate system.
301 
302     - `x_spec`: Specifies the X axis range and data properties
303     - `y_spec`: Specifies the Y axis range and data properties
304     - Returns: A `ChartContext` object, ready to visualize data.
305 
306     See [`ChartBuilder::on()`] and [`ChartContext::configure_mesh()`] for more information and examples.
307     */
308     #[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>, >309     pub fn build_cartesian_2d<X: AsRangedCoord, Y: AsRangedCoord>(
310         &mut self,
311         x_spec: X,
312         y_spec: Y,
313     ) -> Result<
314         ChartContext<'a, DB, Cartesian2d<X::CoordDescType, Y::CoordDescType>>,
315         DrawingAreaErrorKind<DB::ErrorType>,
316     > {
317         let mut label_areas = [None, None, None, None];
318 
319         let mut drawing_area = DrawingArea::clone(self.root_area);
320 
321         if *self.margin.iter().max().unwrap_or(&0) > 0 {
322             drawing_area = drawing_area.margin(
323                 self.margin[0] as i32,
324                 self.margin[1] as i32,
325                 self.margin[2] as i32,
326                 self.margin[3] as i32,
327             );
328         }
329 
330         let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
331             let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
332             drawing_area = drawing_area.titled(title, style.clone())?;
333             let (current_dx, current_dy) = drawing_area.get_base_pixel();
334             (current_dx - origin_dx, current_dy - origin_dy)
335         } else {
336             (0, 0)
337         };
338 
339         let (w, h) = drawing_area.dim_in_pixel();
340 
341         let mut actual_drawing_area_pos = [0, h as i32, 0, w as i32];
342 
343         const DIR: [(i16, i16); 4] = [(0, -1), (0, 1), (-1, 0), (1, 0)];
344 
345         for (idx, (dx, dy)) in (0..4).map(|idx| (idx, DIR[idx])) {
346             if self.overlap_plotting_area[idx] {
347                 continue;
348             }
349 
350             let size = self.label_area_size[idx] as i32;
351 
352             let split_point = if dx + dy < 0 { size } else { -size };
353 
354             actual_drawing_area_pos[idx] += split_point;
355         }
356 
357         // Now the root drawing area is to be split into
358         //
359         // +----------+------------------------------+------+
360         // |    0     |    1 (Top Label Area)        |   2  |
361         // +----------+------------------------------+------+
362         // |    3     |                              |   5  |
363         // |  Left    |       4 (Plotting Area)      | Right|
364         // |  Labels  |                              | Label|
365         // +----------+------------------------------+------+
366         // |    6     |        7 (Bottom Labels)     |   8  |
367         // +----------+------------------------------+------+
368 
369         let mut split: Vec<_> = drawing_area
370             .split_by_breakpoints(
371                 &actual_drawing_area_pos[2..4],
372                 &actual_drawing_area_pos[0..2],
373             )
374             .into_iter()
375             .map(Some)
376             .collect();
377 
378         // Take out the plotting area
379         std::mem::swap(&mut drawing_area, split[4].as_mut().unwrap());
380 
381         // Initialize the label areas - since the label area might be overlapping
382         // with the plotting area, in this case, we need handle them differently
383         for (src_idx, dst_idx) in [1, 7, 3, 5].iter().zip(0..4) {
384             if !self.overlap_plotting_area[dst_idx] {
385                 let (h, w) = split[*src_idx].as_ref().unwrap().dim_in_pixel();
386                 if h > 0 && w > 0 {
387                     std::mem::swap(&mut label_areas[dst_idx], &mut split[*src_idx]);
388                 }
389             } else if self.label_area_size[dst_idx] != 0 {
390                 let size = self.label_area_size[dst_idx] as i32;
391                 let (dw, dh) = drawing_area.dim_in_pixel();
392                 let x0 = if DIR[dst_idx].0 > 0 {
393                     dw as i32 - size
394                 } else {
395                     0
396                 };
397                 let y0 = if DIR[dst_idx].1 > 0 {
398                     dh as i32 - size
399                 } else {
400                     0
401                 };
402                 let x1 = if DIR[dst_idx].0 >= 0 { dw as i32 } else { size };
403                 let y1 = if DIR[dst_idx].1 >= 0 { dh as i32 } else { size };
404 
405                 label_areas[dst_idx] = Some(
406                     drawing_area
407                         .clone()
408                         .shrink((x0, y0), ((x1 - x0), (y1 - y0))),
409                 );
410             }
411         }
412 
413         let mut pixel_range = drawing_area.get_pixel_range();
414         pixel_range.0.end -= 1;
415         pixel_range.1.end -= 1;
416         pixel_range.1 = pixel_range.1.end..pixel_range.1.start;
417 
418         let mut x_label_area = [None, None];
419         let mut y_label_area = [None, None];
420 
421         std::mem::swap(&mut x_label_area[0], &mut label_areas[0]);
422         std::mem::swap(&mut x_label_area[1], &mut label_areas[1]);
423         std::mem::swap(&mut y_label_area[0], &mut label_areas[2]);
424         std::mem::swap(&mut y_label_area[1], &mut label_areas[3]);
425 
426         Ok(ChartContext {
427             x_label_area,
428             y_label_area,
429             drawing_area: drawing_area.apply_coord_spec(Cartesian2d::new(
430                 x_spec,
431                 y_spec,
432                 pixel_range,
433             )),
434             series_anno: vec![],
435             drawing_area_pos: (
436                 actual_drawing_area_pos[2] + title_dx + self.margin[2] as i32,
437                 actual_drawing_area_pos[0] + title_dy + self.margin[0] as i32,
438             ),
439         })
440     }
441 
442     /**
443     Builds a chart with a 3D Cartesian coordinate system.
444 
445     - `x_spec`: Specifies the X axis range and data properties
446     - `y_spec`: Specifies the Y axis range and data properties
447     - `z_sepc`: Specifies the Z axis range and data properties
448     - Returns: A `ChartContext` object, ready to visualize data.
449 
450     See [`ChartBuilder::on()`] and [`ChartContext::configure_axes()`] for more information and examples.
451     */
452     #[allow(clippy::type_complexity)]
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>, >453     pub fn build_cartesian_3d<X: AsRangedCoord, Y: AsRangedCoord, Z: AsRangedCoord>(
454         &mut self,
455         x_spec: X,
456         y_spec: Y,
457         z_spec: Z,
458     ) -> Result<
459         ChartContext<'a, DB, Cartesian3d<X::CoordDescType, Y::CoordDescType, Z::CoordDescType>>,
460         DrawingAreaErrorKind<DB::ErrorType>,
461     > {
462         let mut drawing_area = DrawingArea::clone(self.root_area);
463 
464         if *self.margin.iter().max().unwrap_or(&0) > 0 {
465             drawing_area = drawing_area.margin(
466                 self.margin[0] as i32,
467                 self.margin[1] as i32,
468                 self.margin[2] as i32,
469                 self.margin[3] as i32,
470             );
471         }
472 
473         let (title_dx, title_dy) = if let Some((ref title, ref style)) = self.title {
474             let (origin_dx, origin_dy) = drawing_area.get_base_pixel();
475             drawing_area = drawing_area.titled(title, style.clone())?;
476             let (current_dx, current_dy) = drawing_area.get_base_pixel();
477             (current_dx - origin_dx, current_dy - origin_dy)
478         } else {
479             (0, 0)
480         };
481 
482         let pixel_range = drawing_area.get_pixel_range();
483 
484         Ok(ChartContext {
485             x_label_area: [None, None],
486             y_label_area: [None, None],
487             drawing_area: drawing_area.apply_coord_spec(Cartesian3d::new(
488                 x_spec,
489                 y_spec,
490                 z_spec,
491                 pixel_range,
492             )),
493             series_anno: vec![],
494             drawing_area_pos: (
495                 title_dx + self.margin[2] as i32,
496                 title_dy + self.margin[0] as i32,
497             ),
498         })
499     }
500 }
501 
502 #[cfg(test)]
503 mod test {
504     use super::*;
505     use crate::prelude::*;
506     #[test]
test_label_area_size()507     fn test_label_area_size() {
508         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
509         let mut chart = ChartBuilder::on(&drawing_area);
510 
511         chart
512             .x_label_area_size(10)
513             .y_label_area_size(20)
514             .top_x_label_area_size(30)
515             .right_y_label_area_size(40);
516         assert_eq!(chart.label_area_size[1], 10);
517         assert_eq!(chart.label_area_size[2], 20);
518         assert_eq!(chart.label_area_size[0], 30);
519         assert_eq!(chart.label_area_size[3], 40);
520 
521         chart.set_label_area_size(LabelAreaPosition::Left, 100);
522         chart.set_label_area_size(LabelAreaPosition::Right, 200);
523         chart.set_label_area_size(LabelAreaPosition::Top, 300);
524         chart.set_label_area_size(LabelAreaPosition::Bottom, 400);
525 
526         assert_eq!(chart.label_area_size[0], 300);
527         assert_eq!(chart.label_area_size[1], 400);
528         assert_eq!(chart.label_area_size[2], 100);
529         assert_eq!(chart.label_area_size[3], 200);
530     }
531 
532     #[test]
test_margin_configure()533     fn test_margin_configure() {
534         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
535         let mut chart = ChartBuilder::on(&drawing_area);
536 
537         chart.margin(5);
538         assert_eq!(chart.margin[0], 5);
539         assert_eq!(chart.margin[1], 5);
540         assert_eq!(chart.margin[2], 5);
541         assert_eq!(chart.margin[3], 5);
542 
543         chart.margin_top(10);
544         chart.margin_bottom(11);
545         chart.margin_left(12);
546         chart.margin_right(13);
547         assert_eq!(chart.margin[0], 10);
548         assert_eq!(chart.margin[1], 11);
549         assert_eq!(chart.margin[2], 12);
550         assert_eq!(chart.margin[3], 13);
551     }
552 
553     #[test]
test_caption()554     fn test_caption() {
555         let drawing_area = create_mocked_drawing_area(200, 200, |_| {});
556         let mut chart = ChartBuilder::on(&drawing_area);
557 
558         chart.caption("This is a test case", ("serif", 10));
559 
560         assert_eq!(chart.title.as_ref().unwrap().0, "This is a test case");
561         assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
562         assert_eq!(chart.title.as_ref().unwrap().1.font.get_size(), 10.0);
563         check_color(chart.title.as_ref().unwrap().1.color, BLACK.to_rgba());
564 
565         chart.caption("This is a test case", ("serif", 10));
566         assert_eq!(chart.title.as_ref().unwrap().1.font.get_name(), "serif");
567     }
568 }
569