1use super::context::ChartContext;
2
3use crate::coord::cartesian::{Cartesian2d, Cartesian3d};
4use crate::coord::ranged1d::AsRangedCoord;
5use crate::coord::Shift;
6
7use crate::drawing::{DrawingArea, DrawingAreaErrorKind};
8use crate::style::{IntoTextStyle, SizeDesc, TextStyle};
9
10use plotters_backend::DrawingBackend;
11
12#[derive(Copy, Clone)]
16pub enum LabelAreaPosition {
17 Top = 0,
18 Bottom = 1,
19 Left = 2,
20 Right = 3,
21}
22
23pub struct ChartBuilder<'a, 'b, DB: DrawingBackend> {
27 label_area_size: [u32; 4], overlap_plotting_area: [bool; 4],
29 root_area: &'a DrawingArea<DB, Shift>,
30 title: Option<(String, TextStyle<'b>)>,
31 margin: [u32; 4],
32}
33
34impl<'a, 'b, DB: DrawingBackend> ChartBuilder<'a, 'b, DB> {
35 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 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 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 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 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 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 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 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 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 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 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 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 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 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 )]
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 #[allow(clippy::type_complexity)]
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 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 std::mem::swap(&mut drawing_area, split[4].as_mut().unwrap());
250
251 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 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)]
366mod test {
367 use super::*;
368 use crate::prelude::*;
369 #[test]
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]
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]
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}