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 | |