1use log::debug;
2
3use crate::types::*;
4use crate::utils::magnitude;
5
6const LABEL_OFFSET: f32 = 6.0;
7const TICK_SIZE: f32 = 10.0;
8
9#[derive(Copy, Clone)]
10pub enum Direction {
11 Horizontal,
12 Vertical,
13}
14
15#[derive(Copy, Clone)]
16pub(crate) struct Axis {
17 view: Rect,
18 step_len: f32,
19 steps: i32,
20 world_start: f32,
21 world: f32,
22 grid_ticks: bool,
23 label_interpolation: Option<fn(f32) -> String>,
24 label_size: i32,
25 direction: Direction,
26}
27
28impl Default for Axis {
29 fn default() -> Self {
30 Self {
31 view: Rect::default(),
32 step_len: 0.0,
33 steps: 0,
34 world_start: 0.0,
35 world: 0.0,
36 grid_ticks: false,
37 label_interpolation: None,
38 label_size: 60,
39 direction: Direction::Horizontal,
40 }
41 }
42}
43
44impl Axis {
45 pub fn builder<'a>() -> AxisBuilder<'a> {
46 AxisBuilder::default()
47 }
48
49 pub fn world_to_view(&self, v: f32, start_offset: f32) -> f32 {
50 if self.world > 0.0 {
51 match self.direction {
52 Direction::Vertical => v / self.world * self.view.width() + self.view.min.x,
53 Direction::Horizontal => {
54 let c = (v - start_offset) / self.world * self.view.height() + self.view.min.y;
55 self.view.min.y - c + self.view.max.y
56 }
57 }
58 } else {
59 0.0
60 }
61 }
62
63 pub fn step_to_world(&self, v: f32) -> f32 {
64 self.world / (self.steps as f32 - 1.0) * v
65 }
66
67 pub fn lines(&self) -> Vec<Rect> {
68 let mut lines = Vec::<Rect>::new();
69
70 for i in 0..self.steps {
71 let w = self.step_to_world(i as f32);
72 let v = self.world_to_view(w, 0.0);
73
74 match self.direction {
75 Direction::Vertical => {
76 let end = if self.grid_ticks && i != 0 {
77 self.view.max.y - TICK_SIZE
78 } else {
79 self.view.min.y
80 };
81
82 lines.push(Rect::new(v, self.view.max.y, v, end));
83 }
84 Direction::Horizontal => {
85 let end = if self.grid_ticks && i != 0 {
86 self.view.min.x + TICK_SIZE
87 } else {
88 self.view.max.x
89 };
90
91 lines.push(Rect::new(self.view.min.x, v, end, v));
92 }
93 }
94 }
95
96 lines
97 }
98
99 pub fn tick_centers(&self) -> Vec<Point> {
100 let mut points = Vec::<Point>::new();
101
102 for i in 1..self.steps {
103 let p = i - 1;
104 let w1 = self.step_to_world(p as f32);
105 let w2 = self.step_to_world(i as f32);
106 let v1 = self.world_to_view(w1, 0.0);
107 let v2 = self.world_to_view(w2, 0.0);
108 let center = (v1 + v2) / 2.0;
109
110 match self.direction {
111 Direction::Vertical => {
112 let center = (v1 + v2) / 2.0;
113 points.push(Point::new(center, self.view.max.y));
114 }
115 Direction::Horizontal => {
116 points.push(Point::new(self.view.min.x, center));
117 }
118 }
119 }
120
121 match self.direction {
122 Direction::Vertical => points,
123 Direction::Horizontal => points.into_iter().rev().collect(),
124 }
125 }
126
127 pub fn centered_text_rects(&self, n_labels: i32) -> Vec<Rect> {
128 let mut texts = Vec::<Rect>::new();
129 let n_labels = self.steps.min(n_labels + 1);
130
131 for i in 1..n_labels {
132 let p = i - 1;
133 let w1 = self.step_to_world(p as f32);
134 let w2 = self.step_to_world(i as f32);
135 let v1 = self.world_to_view(w1, 0.0);
136 let v2 = self.world_to_view(w2, 0.0);
137
138 match self.direction {
139 Direction::Vertical => {
140 let width = v2 - v1;
141 let height = self.label_size as f32;
142 texts.push(Rect::new(v1, self.view.max.y + LABEL_OFFSET, width, height));
143 }
144 Direction::Horizontal => {
145 let height = v1 - v2;
146 let width = self.label_size as f32;
147 texts.push(Rect::new(
148 self.view.min.x - LABEL_OFFSET - width,
149 v2,
150 width,
151 height,
152 ));
153 }
154 }
155 }
156
157 texts
158 }
159
160 pub fn text_data(&self, n_labels: usize) -> Vec<TextData> {
161 let mut texts = Vec::<TextData>::new();
162 let n_labels = self.steps.min(n_labels as i32);
163
164 for i in 0..n_labels {
165 let w = self.step_to_world(i as f32);
166 let v = self.world_to_view(w, 0.0);
167
168 match self.direction {
169 Direction::Vertical => {
170 texts.push(TextData {
171 x: v,
172 y: self.view.max.y + LABEL_OFFSET,
173 anchor: "start",
174 baseline: "hanging",
175 });
176 }
177 Direction::Horizontal => {
178 texts.push(TextData {
179 x: self.view.min.x - LABEL_OFFSET,
180 y: v,
181 anchor: "end",
182 baseline: "text-bottom",
183 });
184 }
185 }
186 }
187
188 texts
189 }
190
191 pub fn generated_labels(&self) -> Labels {
192 let mut labels = Labels::new();
193
194 for i in 0..=self.steps {
195 if let Some(func) = self.label_interpolation {
196 labels.push(func(self.world_start + i as f32 * self.step_len));
197 } else {
198 labels.push(format!("{}", self.world_start + i as f32 * self.step_len));
199 }
200 }
201
202 labels
203 }
204}
205
206pub(crate) struct AxisBuilder<'a> {
207 view: Rect,
208 lowest: Option<f32>,
209 highest: Option<f32>,
210 direction: Direction,
211 label_interpolation: Option<fn(f32) -> String>,
212 labels_centered: bool,
213 label_size: i32,
214 grid_ticks: bool,
215 max_ticks: i32,
216 stacked_series: bool,
217 series: Option<&'a Series>,
218 labels: Option<&'a Labels>,
219}
220
221impl<'a> Default for AxisBuilder<'a> {
222 fn default() -> Self {
223 Self {
224 view: Rect::default(),
225 highest: None,
226 lowest: None,
227 direction: Direction::Horizontal,
228 label_interpolation: None,
229 labels_centered: false,
230 label_size: 60,
231 grid_ticks: false,
232 max_ticks: 8,
233 stacked_series: false,
234 series: None,
235 labels: None,
236 }
237 }
238}
239
240impl<'a> AxisBuilder<'a> {
241 pub fn with_view(mut self, view: Rect) -> Self {
242 self.view = view;
243 self
244 }
245
246 pub fn with_lowest(mut self, lowest: Option<f32>) -> Self {
247 self.lowest = lowest;
248 self
249 }
250
251 pub fn with_highest(mut self, highest: Option<f32>) -> Self {
252 self.highest = highest;
253 self
254 }
255
256 pub fn with_series(mut self, series: &'a Series) -> Self {
257 self.series = Some(series);
258 self
259 }
260
261 pub fn with_stacked_series(mut self, stacked: bool) -> Self {
262 self.stacked_series = stacked;
263 self
264 }
265
266 pub fn with_labels(mut self, labels: Option<&'a Labels>) -> Self {
267 self.labels = labels;
268 self
269 }
270
271 pub fn with_label_size(mut self, size: i32) -> Self {
272 self.label_size = size;
273 self
274 }
275
276 pub fn with_max_ticks(mut self, n_ticks: i32) -> Self {
277 self.max_ticks = n_ticks;
278 self
279 }
280
281 pub fn with_grid_ticks(mut self, show_ticks: bool) -> Self {
282 self.grid_ticks = show_ticks;
283 self
284 }
285
286 pub fn with_direction(mut self, direction: Direction) -> Self {
287 self.direction = direction;
288 self
289 }
290
291 pub fn with_centered_labels(mut self, labels: Option<&'a Labels>) -> Self {
292 self.labels = labels;
293 self.labels_centered = true;
294 self
295 }
296
297 pub fn with_label_interpolation(mut self, func: Option<fn(f32) -> String>) -> Self {
298 self.label_interpolation = func;
299 self
300 }
301
302 pub fn build(self) -> Axis {
303 if let Some(series) = self.series {
304 let highest = if let Some(high) = self.highest {
305 high
306 } else if self.stacked_series {
307 MultiZip(series.iter().map(|a| a.iter().copied()).collect())
308 .map(|t| t.iter().sum())
309 .reduce(f32::max)
310 .unwrap()
311 } else {
312 series
313 .iter()
314 .map(|a| a.iter().copied().reduce(f32::max).unwrap())
315 .reduce(f32::max)
316 .unwrap()
317 };
318
319 let lowest = if let Some(low) = self.lowest {
328 low
329 } else {
330 series
331 .iter()
332 .map(|a| a.iter().copied().reduce(f32::min).unwrap())
333 .reduce(f32::min)
334 .unwrap()
335 };
336
337 debug!("highest: {}", highest);
338 debug!("lowest: {}", lowest);
339 let value_range = highest - lowest;
340 let minimum_tick = value_range / (self.max_ticks as f32 - 2.0);
341 let magnitude = magnitude(minimum_tick);
342 let residual = minimum_tick / magnitude;
343 debug!("magnitude: {}", magnitude);
344
345 let step = match residual {
346 n if n > 9.0 => 10.0,
347 n if n > 8.0 => 9.0,
348 n if n > 7.0 => 8.0,
349 n if n > 6.0 => 7.0,
350 n if n > 5.0 => 6.0,
351 n if n > 4.0 => 5.0,
352 n if n > 3.0 => 4.0,
353 n if n > 2.5 => 3.0,
354 n if n > 2.0 => 2.5,
355 n if n > 1.5 => 2.0,
356 n if n > 1.0 => 1.5,
357 _ => 1.0,
358 } * magnitude;
359
360 debug!("step_len: {}", step);
361
362 let max = if self.highest.is_some() {
363 highest
364 } else {
365 let max = (highest / step).ceil() * step;
366 if max < highest {
367 debug!("step added to max");
368 max + step
369 } else {
370 max
371 }
372 };
373
374 let min = if self.lowest.is_some() {
375 lowest
376 } else {
377 let min = (lowest / step).floor() * step;
378 if min > lowest {
379 debug!("step added to min");
380 min - step
381 } else {
382 min
383 }
384 };
385
386 let range = max - min;
387 debug!("range: {} min: {}, max: {}", range, min, max);
388 let steps = unsafe { (range / step).round().to_int_unchecked::<i32>() + 1 };
389 debug!("steps: {}", steps);
390
391 Axis {
392 view: self.view,
393 step_len: step,
394 steps,
395 world_start: min,
396 world: range,
397 label_interpolation: self.label_interpolation,
398 grid_ticks: self.grid_ticks,
399 label_size: self.label_size,
400 direction: self.direction,
401 }
402 } else if let Some(labels) = self.labels {
403 let len = labels.len();
404 let steps = if self.labels_centered {
405 len as i32 + 1
406 } else {
407 len as i32
408 };
409
410 Axis {
411 view: self.view,
412 step_len: steps as f32 / (steps as f32 - 1.0),
413 steps,
414 world: steps as f32,
415 grid_ticks: self.grid_ticks,
416 label_size: self.label_size,
417 direction: self.direction,
418 ..Axis::default()
419 }
420 } else {
421 Axis::default()
422 }
423 }
424}
425
426pub(crate) struct Grid {
427 pub x: Axis,
428 pub y: Axis,
429}
430
431impl Grid {
432 pub fn new<'a>(x: AxisBuilder<'a>, y: AxisBuilder<'a>) -> Grid {
433 Grid {
434 x: x.with_direction(Direction::Vertical).build(),
435 y: y.with_direction(Direction::Horizontal).build(),
436 }
437 }
438
439 pub fn world_to_view(&self, cx: f32, cy: f32, inverted: bool) -> Point {
440 if inverted {
441 Point {
442 x: self.x.world_to_view(cx, self.x.world_start),
443 y: self.y.world_to_view(self.y.step_to_world(cy), 0.0),
444 }
445 } else {
446 Point {
447 x: self.x.world_to_view(self.x.step_to_world(cx), 0.0),
448 y: self.y.world_to_view(cy, self.y.world_start),
449 }
450 }
451 }
452
453 pub fn lines(&self) -> Vec<Rect> {
454 [self.x.lines().as_slice(), self.y.lines().as_slice()].concat()
455 }
456
457 pub fn text_data(&self, x_n_labels: Option<usize>, y_n_labels: Option<usize>) -> Vec<TextData> {
458 [
459 self.x.text_data(x_n_labels.unwrap_or(0)).as_slice(),
460 self.y.text_data(y_n_labels.unwrap_or(0)).as_slice(),
461 ]
462 .concat()
463 }
464}