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