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