1 use std::ops::Range; 2 3 use plotters_backend::DrawingBackend; 4 5 use crate::chart::ChartContext; 6 use crate::coord::{ 7 cartesian::{Cartesian2d, MeshLine}, 8 ranged1d::{KeyPointHint, Ranged}, 9 Shift, 10 }; 11 use crate::drawing::{DrawingArea, DrawingAreaErrorKind}; 12 use crate::element::PathElement; 13 use crate::style::{ 14 text_anchor::{HPos, Pos, VPos}, 15 FontTransform, ShapeStyle, TextStyle, 16 }; 17 18 impl<'a, DB: DrawingBackend, X: Ranged, Y: Ranged> ChartContext<'a, DB, Cartesian2d<X, Y>> { 19 /// The actual function that draws the mesh lines. 20 /// It also returns the label that suppose to be there. 21 #[allow(clippy::type_complexity)] draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( &mut self, (r, c): (YH, XH), (x_mesh, y_mesh): (bool, bool), mesh_line_style: &ShapeStyle, mut fmt_label: FmtLabel, ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>> where FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,22 fn draw_mesh_lines<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( 23 &mut self, 24 (r, c): (YH, XH), 25 (x_mesh, y_mesh): (bool, bool), 26 mesh_line_style: &ShapeStyle, 27 mut fmt_label: FmtLabel, 28 ) -> Result<(Vec<(i32, String)>, Vec<(i32, String)>), DrawingAreaErrorKind<DB::ErrorType>> 29 where 30 FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>, 31 { 32 let mut x_labels = vec![]; 33 let mut y_labels = vec![]; 34 let xr = self.drawing_area.as_coord_spec().x_spec(); 35 let yr = self.drawing_area.as_coord_spec().y_spec(); 36 self.drawing_area.draw_mesh( 37 |b, l| { 38 let draw = match l { 39 MeshLine::XMesh((x, _), _, _) => { 40 if let Some(label_text) = fmt_label(xr, yr, &l) { 41 x_labels.push((x, label_text)); 42 } 43 x_mesh 44 } 45 MeshLine::YMesh((_, y), _, _) => { 46 if let Some(label_text) = fmt_label(xr, yr, &l) { 47 y_labels.push((y, label_text)); 48 } 49 y_mesh 50 } 51 }; 52 if draw { 53 l.draw(b, mesh_line_style) 54 } else { 55 Ok(()) 56 } 57 }, 58 r, 59 c, 60 )?; 61 Ok((x_labels, y_labels)) 62 } 63 draw_axis( &self, area: &DrawingArea<DB, Shift>, axis_style: Option<&ShapeStyle>, orientation: (i16, i16), inward_labels: bool, ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>>64 fn draw_axis( 65 &self, 66 area: &DrawingArea<DB, Shift>, 67 axis_style: Option<&ShapeStyle>, 68 orientation: (i16, i16), 69 inward_labels: bool, 70 ) -> Result<Range<i32>, DrawingAreaErrorKind<DB::ErrorType>> { 71 let (x0, y0) = self.drawing_area.get_base_pixel(); 72 let (tw, th) = area.dim_in_pixel(); 73 74 let mut axis_range = if orientation.0 == 0 { 75 self.drawing_area.get_x_axis_pixel_range() 76 } else { 77 self.drawing_area.get_y_axis_pixel_range() 78 }; 79 80 // At this point, the coordinate system tells us the pixel range after the translation. 81 // However, we need to use the logic coordinate system for drawing. 82 if orientation.0 == 0 { 83 axis_range.start -= x0; 84 axis_range.end -= x0; 85 } else { 86 axis_range.start -= y0; 87 axis_range.end -= y0; 88 } 89 90 if let Some(axis_style) = axis_style { 91 let mut x0 = if orientation.0 > 0 { 0 } else { tw as i32 - 1 }; 92 let mut y0 = if orientation.1 > 0 { 0 } else { th as i32 - 1 }; 93 let mut x1 = if orientation.0 >= 0 { 0 } else { tw as i32 - 1 }; 94 let mut y1 = if orientation.1 >= 0 { 0 } else { th as i32 - 1 }; 95 96 if inward_labels { 97 if orientation.0 == 0 { 98 if y0 == 0 { 99 y0 = th as i32 - 1; 100 y1 = th as i32 - 1; 101 } else { 102 y0 = 0; 103 y1 = 0; 104 } 105 } else if x0 == 0 { 106 x0 = tw as i32 - 1; 107 x1 = tw as i32 - 1; 108 } else { 109 x0 = 0; 110 x1 = 0; 111 } 112 } 113 114 if orientation.0 == 0 { 115 x0 = axis_range.start; 116 x1 = axis_range.end; 117 } else { 118 y0 = axis_range.start; 119 y1 = axis_range.end; 120 } 121 122 area.draw(&PathElement::new( 123 vec![(x0, y0), (x1, y1)], 124 *axis_style, 125 ))?; 126 } 127 128 Ok(axis_range) 129 } 130 131 // TODO: consider make this function less complicated 132 #[allow(clippy::too_many_arguments)] 133 #[allow(clippy::cognitive_complexity)] draw_axis_and_labels( &self, area: Option<&DrawingArea<DB, Shift>>, axis_style: Option<&ShapeStyle>, labels: &[(i32, String)], label_style: &TextStyle, label_offset: i32, orientation: (i16, i16), axis_desc: Option<(&str, &TextStyle)>, tick_size: i32, ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>>134 fn draw_axis_and_labels( 135 &self, 136 area: Option<&DrawingArea<DB, Shift>>, 137 axis_style: Option<&ShapeStyle>, 138 labels: &[(i32, String)], 139 label_style: &TextStyle, 140 label_offset: i32, 141 orientation: (i16, i16), 142 axis_desc: Option<(&str, &TextStyle)>, 143 tick_size: i32, 144 ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { 145 let area = if let Some(target) = area { 146 target 147 } else { 148 return Ok(()); 149 }; 150 151 let (x0, y0) = self.drawing_area.get_base_pixel(); 152 let (tw, th) = area.dim_in_pixel(); 153 154 /* This is the minimal distance from the axis to the box of the labels */ 155 let label_dist = tick_size.abs() * 2; 156 157 /* Draw the axis and get the axis range so that we can do further label 158 * and tick mark drawing */ 159 let axis_range = self.draw_axis(area, axis_style, orientation, tick_size < 0)?; 160 161 /* To make the right label area looks nice, it's a little bit tricky, since for a that is 162 * very long, we actually prefer left alignment instead of right alignment. 163 * Otherwise, the right alignment looks better. So we estimate the max and min label width 164 * So that we are able decide if we should apply right alignment for the text. */ 165 let label_width: Vec<_> = labels 166 .iter() 167 .map(|(_, text)| { 168 if orientation.0 > 0 && orientation.1 == 0 && tick_size >= 0 { 169 self.drawing_area 170 .estimate_text_size(text, label_style) 171 .map(|(w, _)| w) 172 .unwrap_or(0) as i32 173 } else { 174 // Don't ever do the layout estimationfor the drawing area that is either not 175 // the right one or the tick mark is inward. 176 0 177 } 178 }) 179 .collect(); 180 181 let min_width = *label_width.iter().min().unwrap_or(&1).max(&1); 182 let max_width = *label_width 183 .iter() 184 .filter(|&&x| x < min_width * 2) 185 .max() 186 .unwrap_or(&min_width); 187 let right_align_width = (min_width * 2).min(max_width); 188 189 /* Then we need to draw the tick mark and the label */ 190 for ((p, t), w) in labels.iter().zip(label_width.into_iter()) { 191 /* Make sure we are actually in the visible range */ 192 let rp = if orientation.0 == 0 { *p - x0 } else { *p - y0 }; 193 194 if rp < axis_range.start.min(axis_range.end) 195 || axis_range.end.max(axis_range.start) < rp 196 { 197 continue; 198 } 199 200 let (cx, cy, h_pos, v_pos) = if tick_size >= 0 { 201 match orientation { 202 // Right 203 (dx, dy) if dx > 0 && dy == 0 => { 204 if w >= right_align_width { 205 (label_dist, *p - y0, HPos::Left, VPos::Center) 206 } else { 207 ( 208 label_dist + right_align_width, 209 *p - y0, 210 HPos::Right, 211 VPos::Center, 212 ) 213 } 214 } 215 // Left 216 (dx, dy) if dx < 0 && dy == 0 => { 217 (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center) 218 } 219 // Bottom 220 (dx, dy) if dx == 0 && dy > 0 => (*p - x0, label_dist, HPos::Center, VPos::Top), 221 // Top 222 (dx, dy) if dx == 0 && dy < 0 => { 223 (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom) 224 } 225 _ => panic!("Bug: Invalid orientation specification"), 226 } 227 } else { 228 match orientation { 229 // Right 230 (dx, dy) if dx > 0 && dy == 0 => { 231 (tw as i32 - label_dist, *p - y0, HPos::Right, VPos::Center) 232 } 233 // Left 234 (dx, dy) if dx < 0 && dy == 0 => { 235 (label_dist, *p - y0, HPos::Left, VPos::Center) 236 } 237 // Bottom 238 (dx, dy) if dx == 0 && dy > 0 => { 239 (*p - x0, th as i32 - label_dist, HPos::Center, VPos::Bottom) 240 } 241 // Top 242 (dx, dy) if dx == 0 && dy < 0 => (*p - x0, label_dist, HPos::Center, VPos::Top), 243 _ => panic!("Bug: Invalid orientation specification"), 244 } 245 }; 246 247 let (text_x, text_y) = if orientation.0 == 0 { 248 (cx + label_offset, cy) 249 } else { 250 (cx, cy + label_offset) 251 }; 252 253 let label_style = &label_style.pos(Pos::new(h_pos, v_pos)); 254 area.draw_text(t, label_style, (text_x, text_y))?; 255 256 if tick_size != 0 { 257 if let Some(style) = axis_style { 258 let xmax = tw as i32 - 1; 259 let ymax = th as i32 - 1; 260 let (kx0, ky0, kx1, ky1) = if tick_size > 0 { 261 match orientation { 262 (dx, dy) if dx > 0 && dy == 0 => (0, *p - y0, tick_size, *p - y0), 263 (dx, dy) if dx < 0 && dy == 0 => { 264 (xmax - tick_size, *p - y0, xmax, *p - y0) 265 } 266 (dx, dy) if dx == 0 && dy > 0 => (*p - x0, 0, *p - x0, tick_size), 267 (dx, dy) if dx == 0 && dy < 0 => { 268 (*p - x0, ymax - tick_size, *p - x0, ymax) 269 } 270 _ => panic!("Bug: Invalid orientation specification"), 271 } 272 } else { 273 match orientation { 274 (dx, dy) if dx > 0 && dy == 0 => { 275 (xmax, *p - y0, xmax + tick_size, *p - y0) 276 } 277 (dx, dy) if dx < 0 && dy == 0 => (0, *p - y0, -tick_size, *p - y0), 278 (dx, dy) if dx == 0 && dy > 0 => { 279 (*p - x0, ymax, *p - x0, ymax + tick_size) 280 } 281 (dx, dy) if dx == 0 && dy < 0 => (*p - x0, 0, *p - x0, -tick_size), 282 _ => panic!("Bug: Invalid orientation specification"), 283 } 284 }; 285 let line = PathElement::new(vec![(kx0, ky0), (kx1, ky1)], *style); 286 area.draw(&line)?; 287 } 288 } 289 } 290 291 if let Some((text, style)) = axis_desc { 292 let actual_style = if orientation.0 == 0 { 293 style.clone() 294 } else if orientation.0 == -1 { 295 style.transform(FontTransform::Rotate270) 296 } else { 297 style.transform(FontTransform::Rotate90) 298 }; 299 300 let (x0, y0, h_pos, v_pos) = match orientation { 301 // Right 302 (dx, dy) if dx > 0 && dy == 0 => (tw, th / 2, HPos::Center, VPos::Top), 303 // Left 304 (dx, dy) if dx < 0 && dy == 0 => (0, th / 2, HPos::Center, VPos::Top), 305 // Bottom 306 (dx, dy) if dx == 0 && dy > 0 => (tw / 2, th, HPos::Center, VPos::Bottom), 307 // Top 308 (dx, dy) if dx == 0 && dy < 0 => (tw / 2, 0, HPos::Center, VPos::Top), 309 _ => panic!("Bug: Invalid orientation specification"), 310 }; 311 312 let actual_style = &actual_style.pos(Pos::new(h_pos, v_pos)); 313 area.draw_text(text, actual_style, (x0 as i32, y0 as i32))?; 314 } 315 316 Ok(()) 317 } 318 319 #[allow(clippy::too_many_arguments)] draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( &mut self, (r, c): (YH, XH), mesh_line_style: &ShapeStyle, x_label_style: &TextStyle, y_label_style: &TextStyle, fmt_label: FmtLabel, x_mesh: bool, y_mesh: bool, x_label_offset: i32, y_label_offset: i32, x_axis: bool, y_axis: bool, axis_style: &ShapeStyle, axis_desc_style: &TextStyle, x_desc: Option<String>, y_desc: Option<String>, x_tick_size: [i32; 2], y_tick_size: [i32; 2], ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> where FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>,320 pub(crate) fn draw_mesh<FmtLabel, YH: KeyPointHint, XH: KeyPointHint>( 321 &mut self, 322 (r, c): (YH, XH), 323 mesh_line_style: &ShapeStyle, 324 x_label_style: &TextStyle, 325 y_label_style: &TextStyle, 326 fmt_label: FmtLabel, 327 x_mesh: bool, 328 y_mesh: bool, 329 x_label_offset: i32, 330 y_label_offset: i32, 331 x_axis: bool, 332 y_axis: bool, 333 axis_style: &ShapeStyle, 334 axis_desc_style: &TextStyle, 335 x_desc: Option<String>, 336 y_desc: Option<String>, 337 x_tick_size: [i32; 2], 338 y_tick_size: [i32; 2], 339 ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> 340 where 341 FmtLabel: FnMut(&X, &Y, &MeshLine<X, Y>) -> Option<String>, 342 { 343 let (x_labels, y_labels) = 344 self.draw_mesh_lines((r, c), (x_mesh, y_mesh), mesh_line_style, fmt_label)?; 345 346 for idx in 0..2 { 347 self.draw_axis_and_labels( 348 self.x_label_area[idx].as_ref(), 349 if x_axis { Some(axis_style) } else { None }, 350 &x_labels[..], 351 x_label_style, 352 x_label_offset, 353 (0, -1 + idx as i16 * 2), 354 x_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)), 355 x_tick_size[idx], 356 )?; 357 358 self.draw_axis_and_labels( 359 self.y_label_area[idx].as_ref(), 360 if y_axis { Some(axis_style) } else { None }, 361 &y_labels[..], 362 y_label_style, 363 y_label_offset, 364 (-1 + idx as i16 * 2, 0), 365 y_desc.as_ref().map(|desc| (&desc[..], axis_desc_style)), 366 y_tick_size[idx], 367 )?; 368 } 369 370 Ok(()) 371 } 372 } 373