1use std::cmp::Ordering;
2
3use plotters_backend::DrawingBackend;
4
5use crate::chart::ChartContext;
6use crate::coord::{
7 cartesian::Cartesian3d,
8 ranged1d::{KeyPointHint, Ranged},
9 CoordTranslate,
10};
11use crate::drawing::DrawingAreaErrorKind;
12use crate::element::{EmptyElement, PathElement, Polygon, Text};
13use crate::style::{
14 text_anchor::{HPos, Pos, VPos},
15 ShapeStyle, TextStyle,
16};
17
18use super::Coord3D;
19
20pub(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
26impl<'a, DB, X: Ranged, Y: Ranged, Z: Ranged> ChartContext<'a, DB, Cartesian3d<X, Y, Z>>
27where
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