| 1 | use std::cmp::Ordering; |
| 2 | |
| 3 | use plotters_backend::DrawingBackend; |
| 4 | |
| 5 | use crate::chart::ChartContext; |
| 6 | use crate::coord::{ |
| 7 | cartesian::Cartesian3d, |
| 8 | ranged1d::{KeyPointHint, Ranged}, |
| 9 | CoordTranslate, |
| 10 | }; |
| 11 | use crate::drawing::DrawingAreaErrorKind; |
| 12 | use crate::element::{EmptyElement, PathElement, Polygon, Text}; |
| 13 | use crate::style::{ |
| 14 | text_anchor::{HPos, Pos, VPos}, |
| 15 | ShapeStyle, TextStyle, |
| 16 | }; |
| 17 | |
| 18 | use super::Coord3D; |
| 19 | |
| 20 | pub(crate) struct KeyPoints3d<X: Ranged, Y: Ranged, Z: Ranged> { |
| 21 | pub(crate) x_points: Vec<X::ValueType>, |
| 22 | pub(crate) y_points: Vec<Y::ValueType>, |
| 23 | pub(crate) z_points: Vec<Z::ValueType>, |
| 24 | } |
| 25 | |
| 26 | impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>> |
| 27 | where |
| 28 | DB: DrawingBackend, |
| 29 | X::ValueType: Clone, |
| 30 | Y::ValueType: Clone, |
| 31 | Z::ValueType: Clone, |
| 32 | { |
| 33 | pub(crate) fn get_key_points<XH: KeyPointHint, YH: KeyPointHint, ZH: KeyPointHint>( |
| 34 | &self, |
| 35 | x_hint: XH, |
| 36 | y_hint: YH, |
| 37 | z_hint: ZH, |
| 38 | ) -> KeyPoints3d<X, Y, Z> { |
| 39 | let coord = self.plotting_area().as_coord_spec(); |
| 40 | let x_points = coord.logic_x.key_points(x_hint); |
| 41 | let y_points = coord.logic_y.key_points(y_hint); |
| 42 | let z_points = coord.logic_z.key_points(z_hint); |
| 43 | KeyPoints3d { |
| 44 | x_points, |
| 45 | y_points, |
| 46 | z_points, |
| 47 | } |
| 48 | } |
| 49 | #[allow (clippy::type_complexity)] |
| 50 | pub(crate) fn draw_axis_ticks( |
| 51 | &mut self, |
| 52 | axis: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], |
| 53 | labels: &[( |
| 54 | [Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3], |
| 55 | String, |
| 56 | )], |
| 57 | tick_size: i32, |
| 58 | style: ShapeStyle, |
| 59 | font: TextStyle, |
| 60 | ) -> Result<(), DrawingAreaErrorKind<DB::ErrorType>> { |
| 61 | let coord = self.plotting_area().as_coord_spec(); |
| 62 | let begin = coord.translate(&Coord3D::build_coord([ |
| 63 | &axis[0][0], |
| 64 | &axis[0][1], |
| 65 | &axis[0][2], |
| 66 | ])); |
| 67 | let end = coord.translate(&Coord3D::build_coord([ |
| 68 | &axis[1][0], |
| 69 | &axis[1][1], |
| 70 | &axis[1][2], |
| 71 | ])); |
| 72 | let axis_dir = (end.0 - begin.0, end.1 - begin.1); |
| 73 | let (x_range, y_range) = self.plotting_area().get_pixel_range(); |
| 74 | let x_mid = (x_range.start + x_range.end) / 2; |
| 75 | let y_mid = (y_range.start + y_range.end) / 2; |
| 76 | |
| 77 | let x_dir = if begin.0 < x_mid { |
| 78 | (-tick_size, 0) |
| 79 | } else { |
| 80 | (tick_size, 0) |
| 81 | }; |
| 82 | |
| 83 | let y_dir = if begin.1 < y_mid { |
| 84 | (0, -tick_size) |
| 85 | } else { |
| 86 | (0, tick_size) |
| 87 | }; |
| 88 | |
| 89 | let x_score = (x_dir.0 * axis_dir.0 + x_dir.1 * axis_dir.1).abs(); |
| 90 | let y_score = (y_dir.0 * axis_dir.0 + y_dir.1 * axis_dir.1).abs(); |
| 91 | |
| 92 | let dir = if x_score < y_score { x_dir } else { y_dir }; |
| 93 | |
| 94 | for (pos, text) in labels { |
| 95 | let logic_pos = Coord3D::build_coord([&pos[0], &pos[1], &pos[2]]); |
| 96 | let mut font = font.clone(); |
| 97 | |
| 98 | match dir.0.cmp(&0) { |
| 99 | Ordering::Less => font.pos = Pos::new(HPos::Right, VPos::Center), |
| 100 | Ordering::Greater => font.pos = Pos::new(HPos::Left, VPos::Center), |
| 101 | _ => (), |
| 102 | } |
| 103 | |
| 104 | match dir.1.cmp(&0) { |
| 105 | Ordering::Less => font.pos = Pos::new(HPos::Center, VPos::Bottom), |
| 106 | Ordering::Greater => font.pos = Pos::new(HPos::Center, VPos::Top), |
| 107 | _ => (), |
| 108 | } |
| 109 | |
| 110 | let element = EmptyElement::at(logic_pos) |
| 111 | + PathElement::new(vec![(0, 0), dir], style) |
| 112 | + Text::new(text.to_string(), (dir.0 * 2, dir.1 * 2), font); |
| 113 | self.plotting_area().draw(&element)?; |
| 114 | } |
| 115 | Ok(()) |
| 116 | } |
| 117 | #[allow (clippy::type_complexity)] |
| 118 | pub(crate) fn draw_axis( |
| 119 | &mut self, |
| 120 | idx: usize, |
| 121 | panels: &[[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3], |
| 122 | style: ShapeStyle, |
| 123 | ) -> Result< |
| 124 | [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], |
| 125 | DrawingAreaErrorKind<DB::ErrorType>, |
| 126 | > { |
| 127 | let coord = self.plotting_area().as_coord_spec(); |
| 128 | let x_range = coord.logic_x.range(); |
| 129 | let y_range = coord.logic_y.range(); |
| 130 | let z_range = coord.logic_z.range(); |
| 131 | |
| 132 | let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [ |
| 133 | [Coord3D::X(x_range.start), Coord3D::X(x_range.end)], |
| 134 | [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)], |
| 135 | [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)], |
| 136 | ]; |
| 137 | |
| 138 | let (start, end) = { |
| 139 | let mut start = [&ranges[0][0], &ranges[1][0], &ranges[2][0]]; |
| 140 | let mut end = [&ranges[0][1], &ranges[1][1], &ranges[2][1]]; |
| 141 | |
| 142 | let mut plan = vec![]; |
| 143 | |
| 144 | for i in 0..3 { |
| 145 | if i == idx { |
| 146 | continue; |
| 147 | } |
| 148 | start[i] = &panels[i][0][i]; |
| 149 | end[i] = &panels[i][0][i]; |
| 150 | for j in 0..3 { |
| 151 | if i != idx && i != j && j != idx { |
| 152 | for k in 0..2 { |
| 153 | start[j] = &panels[i][k][j]; |
| 154 | end[j] = &panels[i][k][j]; |
| 155 | plan.push((start, end)); |
| 156 | } |
| 157 | } |
| 158 | } |
| 159 | } |
| 160 | plan.into_iter() |
| 161 | .min_by_key(|&(s, e)| { |
| 162 | let d = coord.projected_depth(s[0].get_x(), s[1].get_y(), s[2].get_z()); |
| 163 | let d = d + coord.projected_depth(e[0].get_x(), e[1].get_y(), e[2].get_z()); |
| 164 | let (_, y1) = coord.translate(&Coord3D::build_coord(s)); |
| 165 | let (_, y2) = coord.translate(&Coord3D::build_coord(e)); |
| 166 | let y = y1 + y2; |
| 167 | (d, y) |
| 168 | }) |
| 169 | .unwrap() |
| 170 | }; |
| 171 | |
| 172 | self.plotting_area().draw(&PathElement::new( |
| 173 | vec![Coord3D::build_coord(start), Coord3D::build_coord(end)], |
| 174 | style, |
| 175 | ))?; |
| 176 | |
| 177 | Ok([ |
| 178 | [start[0].clone(), start[1].clone(), start[2].clone()], |
| 179 | [end[0].clone(), end[1].clone(), end[2].clone()], |
| 180 | ]) |
| 181 | } |
| 182 | |
| 183 | #[allow (clippy::type_complexity)] |
| 184 | pub(crate) fn draw_axis_panels( |
| 185 | &mut self, |
| 186 | bold_points: &KeyPoints3d<X, Y, Z>, |
| 187 | light_points: &KeyPoints3d<X, Y, Z>, |
| 188 | panel_style: ShapeStyle, |
| 189 | bold_grid_style: ShapeStyle, |
| 190 | light_grid_style: ShapeStyle, |
| 191 | ) -> Result< |
| 192 | [[[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2]; 3], |
| 193 | DrawingAreaErrorKind<DB::ErrorType>, |
| 194 | > { |
| 195 | let mut r_iter = (0..3).map(|idx| { |
| 196 | self.draw_axis_panel( |
| 197 | idx, |
| 198 | bold_points, |
| 199 | light_points, |
| 200 | panel_style, |
| 201 | bold_grid_style, |
| 202 | light_grid_style, |
| 203 | ) |
| 204 | }); |
| 205 | Ok([ |
| 206 | r_iter.next().unwrap()?, |
| 207 | r_iter.next().unwrap()?, |
| 208 | r_iter.next().unwrap()?, |
| 209 | ]) |
| 210 | } |
| 211 | #[allow (clippy::type_complexity)] |
| 212 | fn draw_axis_panel( |
| 213 | &mut self, |
| 214 | idx: usize, |
| 215 | bold_points: &KeyPoints3d<X, Y, Z>, |
| 216 | light_points: &KeyPoints3d<X, Y, Z>, |
| 217 | panel_style: ShapeStyle, |
| 218 | bold_grid_style: ShapeStyle, |
| 219 | light_grid_style: ShapeStyle, |
| 220 | ) -> Result< |
| 221 | [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 3]; 2], |
| 222 | DrawingAreaErrorKind<DB::ErrorType>, |
| 223 | > { |
| 224 | let coord = self.plotting_area().as_coord_spec(); |
| 225 | let x_range = coord.logic_x.range(); |
| 226 | let y_range = coord.logic_y.range(); |
| 227 | let z_range = coord.logic_z.range(); |
| 228 | |
| 229 | let ranges: [[Coord3D<X::ValueType, Y::ValueType, Z::ValueType>; 2]; 3] = [ |
| 230 | [Coord3D::X(x_range.start), Coord3D::X(x_range.end)], |
| 231 | [Coord3D::Y(y_range.start), Coord3D::Y(y_range.end)], |
| 232 | [Coord3D::Z(z_range.start), Coord3D::Z(z_range.end)], |
| 233 | ]; |
| 234 | |
| 235 | let (mut panel, start, end) = { |
| 236 | let vert_a = [&ranges[0][0], &ranges[1][0], &ranges[2][0]]; |
| 237 | let mut vert_b = [&ranges[0][1], &ranges[1][1], &ranges[2][1]]; |
| 238 | let mut vert_c = vert_a; |
| 239 | let vert_d = vert_b; |
| 240 | |
| 241 | vert_b[idx] = &ranges[idx][0]; |
| 242 | vert_c[idx] = &ranges[idx][1]; |
| 243 | |
| 244 | let (vert_a, vert_b) = |
| 245 | if coord.projected_depth(vert_a[0].get_x(), vert_a[1].get_y(), vert_a[2].get_z()) |
| 246 | >= coord.projected_depth( |
| 247 | vert_c[0].get_x(), |
| 248 | vert_c[1].get_y(), |
| 249 | vert_c[2].get_z(), |
| 250 | ) |
| 251 | { |
| 252 | (vert_a, vert_b) |
| 253 | } else { |
| 254 | (vert_c, vert_d) |
| 255 | }; |
| 256 | |
| 257 | let mut m = vert_a; |
| 258 | m[(idx + 1) % 3] = vert_b[(idx + 1) % 3]; |
| 259 | let mut n = vert_a; |
| 260 | n[(idx + 2) % 3] = vert_b[(idx + 2) % 3]; |
| 261 | |
| 262 | ( |
| 263 | vec![ |
| 264 | Coord3D::build_coord(vert_a), |
| 265 | Coord3D::build_coord(m), |
| 266 | Coord3D::build_coord(vert_b), |
| 267 | Coord3D::build_coord(n), |
| 268 | ], |
| 269 | vert_a, |
| 270 | vert_b, |
| 271 | ) |
| 272 | }; |
| 273 | self.plotting_area() |
| 274 | .draw(&Polygon::new(panel.clone(), panel_style))?; |
| 275 | panel.push(panel[0].clone()); |
| 276 | self.plotting_area() |
| 277 | .draw(&PathElement::new(panel, bold_grid_style))?; |
| 278 | |
| 279 | for (kps, style) in vec![ |
| 280 | (light_points, light_grid_style), |
| 281 | (bold_points, bold_grid_style), |
| 282 | ] |
| 283 | .into_iter() |
| 284 | { |
| 285 | for idx in (0..3).filter(|&i| i != idx) { |
| 286 | let kps: Vec<_> = match idx { |
| 287 | 0 => kps.x_points.iter().map(|x| Coord3D::X(x.clone())).collect(), |
| 288 | 1 => kps.y_points.iter().map(|y| Coord3D::Y(y.clone())).collect(), |
| 289 | _ => kps.z_points.iter().map(|z| Coord3D::Z(z.clone())).collect(), |
| 290 | }; |
| 291 | for kp in kps.iter() { |
| 292 | let mut kp_start = start; |
| 293 | let mut kp_end = end; |
| 294 | kp_start[idx] = kp; |
| 295 | kp_end[idx] = kp; |
| 296 | self.plotting_area().draw(&PathElement::new( |
| 297 | vec![Coord3D::build_coord(kp_start), Coord3D::build_coord(kp_end)], |
| 298 | style, |
| 299 | ))?; |
| 300 | } |
| 301 | } |
| 302 | } |
| 303 | |
| 304 | Ok([ |
| 305 | [start[0].clone(), start[1].clone(), start[2].clone()], |
| 306 | [end[0].clone(), end[1].clone(), end[2].clone()], |
| 307 | ]) |
| 308 | } |
| 309 | } |
| 310 | |