simple_chart/
axis.rs

1use std::f64;
2use DisplayPoint;
3use tick;
4
5const W_POINT: u8 = 1;      //value point separator width
6const W_BORDER: usize = 1;     //space around graph width
7const W_DECIMAL_SEPARATOR: usize = 1;     //space between numbers in pixels
8const W_ARROW: usize = 4;      //width of arrow
9const W_NUMBER: usize = 4;     //number width in pixel
10const H_NUMBER: usize = 5;     //number height in pixels
11const MAX_INTERVALS: u8 = 10;   // maximum intervals count
12const START_SHIFT: usize = W_BORDER + H_NUMBER + W_NUMBER;
13const DEFAULT_SIZE: usize = 100;
14
15#[derive(Debug, Clone)]
16pub struct Axis {
17    pub min_value: f64,
18    pub max_value: f64,
19    pub interval_count: u8,
20    pub scale_interval_pix: f64,
21    scale_interval_value: f64,
22    pub decimal_places: u8,
23    size: usize,
24    rotated: bool,
25}
26
27
28impl Axis {
29    pub fn rotate(self) -> Self {
30        Axis { rotated: true, ..self }
31    }
32
33    pub fn create_points(&self) -> Vec<DisplayPoint> {
34        let mut v: Vec<DisplayPoint> = vec![];
35        let ticks = self.create_ticks_points();
36        let line = self.calculate_axis_line();
37        let arrow = self.calculate_axis_arrow();
38        v.extend(ticks);
39        v.extend(line);
40        v.extend(arrow);
41        if self.rotated {
42            v.into_iter()
43                .map(|p| DisplayPoint { x: p.y, y: p.x })
44                .collect::<Vec<DisplayPoint>>()
45        } else {
46            v
47        }
48    }
49
50    pub fn set_axis_auto(max: f64, min: f64, total_size: usize) -> Axis {
51        let available_size = total_size - 2 * W_BORDER - H_NUMBER - W_NUMBER - W_ARROW;
52        let (s_max, decimal_places) = determine_max_numbers_count(max, min);
53        let interval_count = calculate_intervals_count(available_size, s_max);
54        let scale_interval_pix = (available_size as f64) / (interval_count as f64);
55        let min_value = calc(f64::floor, min, decimal_places as i32);
56        let max_value = calc(f64::ceil, max, decimal_places as i32);
57        let scale_interval_value = (max_value - min_value) / (interval_count as f64);
58        let scale_interval_value = calc(f64::ceil, scale_interval_value, decimal_places as i32);
59
60        Axis {
61            min_value: min_value,
62            max_value: max_value,
63            scale_interval_value: scale_interval_value,
64            scale_interval_pix: scale_interval_pix,
65            interval_count: interval_count,
66            decimal_places: decimal_places,
67            size: total_size,
68            rotated: false,
69        }
70    }
71
72
73    pub fn set_axis_manual(min_value: f64,
74                           max_value: f64,
75                           interval_count: u8,
76                           decimal_places: u8,
77                           size: usize)
78                           -> Axis {
79        let available_size = size - 2 * W_BORDER - H_NUMBER - W_NUMBER - W_ARROW;
80        let scale_interval_pix = (available_size as f64) / (interval_count as f64);
81        let min = calc(f64::floor, min_value, decimal_places as i32);
82        let max = calc(f64::ceil, max_value, decimal_places as i32);
83        let mut scale_interval_value = (max - min) / (interval_count as f64);
84        scale_interval_value = calc(f64::ceil, scale_interval_value, decimal_places as i32);
85
86        Axis {
87            min_value: min,
88            max_value: max,
89            scale_interval_value: scale_interval_value,
90            scale_interval_pix: scale_interval_pix,
91            interval_count: interval_count,
92            decimal_places: decimal_places,
93            size: size,
94            rotated: false,
95        }
96    }
97
98    pub fn new(min_value: f64, max_value: f64, interval_count: u8, decimal_places: u8) -> Axis {
99        Axis {
100            min_value: min_value,
101            max_value: max_value,
102            scale_interval_value: 0f64,
103            scale_interval_pix: 0f64,
104            interval_count: interval_count,
105            decimal_places: decimal_places,
106            size: DEFAULT_SIZE,
107            rotated: false,
108        }
109    }
110
111    fn create_ticks_points(&self) -> Vec<DisplayPoint> {
112        let mut v: Vec<DisplayPoint> = vec![];
113        for i in 0..self.interval_count {
114            let value = round((self.min_value + self.scale_interval_value * (i as f64)),
115                              self.decimal_places as i32);
116            let value_s = &*value.to_string();
117            let shift = (self.scale_interval_pix * (i as f64)).round() as usize;
118            v.extend(tick::create_tick_with_label(START_SHIFT + shift, value_s, self.rotated));
119        }
120        v
121    }
122
123    fn calculate_axis_line(&self) -> Vec<DisplayPoint> {
124        let mut v = vec![];
125        for x in START_SHIFT..self.size {
126            v.push(DisplayPoint {
127                x: x,
128                y: START_SHIFT,
129            });
130        }
131        v
132    }
133
134
135    fn calculate_axis_arrow(&self) -> Vec<DisplayPoint> {
136        vec![(4, 13), (3, 12), (2, 11), (4, 7), (3, 8), (2, 9)]
137            .into_iter()
138            .map(move |(x, y)| {
139                DisplayPoint {
140                    x: self.size - x,
141                    y: y,
142                }
143            })
144            .collect()
145    }
146}
147
148
149fn round(value: f64, decimal_places: i32) -> f64 {
150    let k = 10f64.powi(decimal_places);
151    (value * k).round() / k
152}
153
154fn calc<F>(f: F, value: f64, decimal_places: i32) -> f64
155    where F: Fn(f64) -> f64
156{
157    let k = 10f64.powi(decimal_places);
158    let new_value = f(value * k);
159    new_value / k
160}
161
162fn determine_max_numbers_count(max: f64, min: f64) -> (u8, u8) {
163    let mut d = max - min;
164    let c_max = get_numbers_count(max as i64);
165    let c_min = get_numbers_count(min as i64);
166
167    let maxc = if c_max >= c_min { c_max } else { c_min };
168
169    if d > 10.0 {
170        (maxc, 0)
171    } else {
172        let mut decimal_places = 0;
173        while d < 10.0 {
174            d *= 10.0;
175            decimal_places += 1;
176        }
177        (maxc + W_POINT + decimal_places, decimal_places)
178    }
179}
180
181fn get_numbers_count(value: i64) -> u8 {
182    value.to_string().len() as u8
183}
184
185fn calculate_intervals_count(available_size: usize, s_max: u8) -> u8 {
186    let k = (available_size / ((W_NUMBER + W_DECIMAL_SEPARATOR) * (s_max as usize))) - 1;
187    if k > MAX_INTERVALS as usize {
188        MAX_INTERVALS
189    } else {
190        k as u8
191    }
192
193}
194
195
196#[cfg(test)]
197mod tests {
198    use axis;
199
200    #[test]
201    fn get_numbers_count_test() {
202        let val = 34234;
203        let c = axis::get_numbers_count(val);
204        assert_eq!(c, 5);
205    }
206
207    #[test]
208    fn determine_max_numbers_count_test_diff_more_10() {
209        let max = 13.54543;
210        let min = 1.34;
211        let (s_max, decimal_places) = axis::determine_max_numbers_count(max, min);
212        assert_eq!(s_max, 2);
213        assert_eq!(decimal_places, 0);
214    }
215
216    #[test]
217    fn determine_max_numbers_count_test_diff_less_10() {
218        let max = 1.54543;
219        let min = 1.34;
220        let (s_max, decimal_places) = axis::determine_max_numbers_count(max, min);
221        assert_eq!(s_max, 4);
222        assert_eq!(decimal_places, 2);
223    }
224
225    #[test]
226    fn calculate_intervals_count_test_less_10() {
227        let available_width = 100;
228        let s_max = 5;
229        let interval_count = axis::calculate_intervals_count(available_width, s_max);
230        assert_eq!(interval_count, 3);
231    }
232
233    #[test]
234    fn calculate_intervals_count_test_more_10() {
235        let width = 1000;
236        let s_max = 5;
237        let interval_count = axis::calculate_intervals_count(width, s_max);
238        assert_eq!(interval_count, 10);
239    }
240}
241
242#[cfg(all(feature = "dev", test))]
243mod bench {
244    extern crate test;
245    use super::*;
246
247    #[bench]
248    fn create_axis_bench(b: &mut test::Bencher) {
249        b.iter(|| {
250            let axis = Axis::set_axis_auto(100.0, 0.0, 1000);
251            let _ = axis.create_points();
252        })
253    }
254}