Skip to content

Commit f086247

Browse files
committed
Move the library code to a workspaced crate, plotlib
0 parents  commit f086247

File tree

4 files changed

+322
-0
lines changed

4 files changed

+322
-0
lines changed

Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "plotlib"
3+
version = "0.1.0"
4+
authors = ["Matt Williams <[email protected]>"]
5+
description = "Plotting data structures and tools"
6+
license = "MIT"
7+
repository = "https://github.com/milliams/plot"
8+
categories = ["visualization"]

src/axis.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
#[derive(Debug)]
2+
pub struct Axis {
3+
lower: f64,
4+
upper: f64,
5+
ticks: Vec<f64>,
6+
}
7+
8+
impl Axis {
9+
pub fn new(lower: f64, upper: f64) -> Axis {
10+
assert!(lower < upper);
11+
let default_max_ticks = 6;
12+
Axis {
13+
lower: lower,
14+
upper: upper,
15+
ticks: calculate_ticks(lower, upper, default_max_ticks),
16+
}
17+
}
18+
19+
pub fn max(&self) -> f64 {
20+
self.upper
21+
}
22+
23+
pub fn min(&self) -> f64 {
24+
self.lower
25+
}
26+
27+
pub fn ticks(&self) -> &Vec<f64> {
28+
&self.ticks
29+
}
30+
}
31+
32+
/// The base units for the step sizes
33+
/// They should be within one order of magnitude, e.g. [1,10)
34+
const BASE_STEPS: [u32; 4] = [1, 2, 4, 5];
35+
36+
#[derive(Debug,Clone)]
37+
struct TickSteps {
38+
next: f64,
39+
}
40+
41+
impl TickSteps {
42+
fn start_at(start: f64) -> TickSteps {
43+
let start_options = TickSteps::scaled_steps(start);
44+
let overflow = start_options[0] * 10.0;
45+
let curr = start_options.iter().skip_while(|&step| step < &start).next();
46+
47+
TickSteps { next: *curr.unwrap_or(&overflow) }
48+
}
49+
50+
fn scaled_steps(curr: f64) -> Vec<f64> {
51+
let power = curr.log10().floor();
52+
let base_step_scale = 10f64.powf(power);
53+
BASE_STEPS.iter().map(|&s| (s as f64 * base_step_scale)).collect()
54+
}
55+
}
56+
57+
impl Iterator for TickSteps {
58+
type Item = f64;
59+
60+
fn next(&mut self) -> Option<f64> {
61+
let curr = self.next; // cache the value we're currently on
62+
let curr_steps = TickSteps::scaled_steps(self.next);
63+
let overflow = curr_steps[0] * 10.0;
64+
self.next = *curr_steps.iter().skip_while(|&s| s <= &curr).next().unwrap_or(&overflow);
65+
Some(curr)
66+
}
67+
}
68+
69+
fn generate_ticks(min: f64, max: f64, step_size: f64) -> Vec<f64> {
70+
let mut ticks: Vec<f64> = vec![];
71+
if min <= 0.0 {
72+
if max >= 0.0 {
73+
// standard spanning axis
74+
ticks.extend((1..).map(|n| -1.0 * n as f64 * step_size).take_while(|&v| v >= min).collect::<Vec<f64>>().iter().rev());
75+
ticks.push(0.0);
76+
ticks.extend((1..).map(|n| n as f64 * step_size).take_while(|&v| v <= max));
77+
} else {
78+
// entirely negative axis
79+
ticks.extend((1..)
80+
.map(|n| -1.0 * n as f64 * step_size)
81+
.skip_while(|&v| v > max)
82+
.take_while(|&v| v >= min)
83+
.collect::<Vec<f64>>()
84+
.iter()
85+
.rev());
86+
}
87+
} else {
88+
// entirely positive axis
89+
ticks.extend((1..)
90+
.map(|n| n as f64 * step_size)
91+
.skip_while(|&v| v < min)
92+
.take_while(|&v| v <= max));
93+
}
94+
ticks
95+
}
96+
97+
/// Given a range and a step size, work out how many ticks will be displayed
98+
fn number_of_ticks(min: f64, max: f64, step_size: f64) -> usize {
99+
generate_ticks(min, max, step_size).len()
100+
}
101+
102+
/// Given a range of values, and a maximum number of ticks, calulate the step between the ticks
103+
fn calculate_tick_step_for_range(min: f64, max: f64, max_ticks: usize) -> f64 {
104+
let range = max - min;
105+
let min_tick_step = range / max_ticks as f64;
106+
// Get the first entry which is our smallest possible tick step size
107+
let smallest_valid_step = TickSteps::start_at(min_tick_step)
108+
.skip_while(|&s| number_of_ticks(min, max, s) > max_ticks)
109+
.next()
110+
.expect("ERROR: We've somehow run out of tick step options!");
111+
// Count how many ticks that relates to
112+
let actual_num_ticks = number_of_ticks(min, max, smallest_valid_step);
113+
114+
// Create a new TickStep iterator, starting at the correct lower bound
115+
let tick_steps = TickSteps::start_at(smallest_valid_step);
116+
// Get all the possible tick step sizes that give just as many ticks
117+
let step_options = tick_steps.take_while(|&s| number_of_ticks(min, max, s) == actual_num_ticks);
118+
// Get the largest tick step size from the list
119+
step_options.fold(-1. / 0., f64::max)
120+
}
121+
122+
/// Given an axis range, calculate the sensible places to place the ticks
123+
fn calculate_ticks(min: f64, max: f64, max_ticks: usize) -> Vec<f64> {
124+
let tick_step = calculate_tick_step_for_range(min, max, max_ticks);
125+
generate_ticks(min, max, tick_step)
126+
}
127+
128+
#[cfg(test)]
129+
mod tests {
130+
use super::*;
131+
132+
#[test]
133+
fn test_tick_step_generator() {
134+
let t = TickSteps::start_at(1.0);
135+
let ts: Vec<_> = t.take(7).collect();
136+
assert_eq!(ts, [1.0, 2.0, 4.0, 5.0, 10.0, 20.0, 40.0]);
137+
138+
let t = TickSteps::start_at(100.0);
139+
let ts: Vec<_> = t.take(5).collect();
140+
assert_eq!(ts, [100.0, 200.0, 400.0, 500.0, 1000.0]);
141+
142+
let t = TickSteps::start_at(3.0);
143+
let ts: Vec<_> = t.take(5).collect();
144+
assert_eq!(ts, [4.0, 5.0, 10.0, 20.0, 40.0]);
145+
146+
let t = TickSteps::start_at(8.0);
147+
let ts: Vec<_> = t.take(3).collect();
148+
assert_eq!(ts, [10.0, 20.0, 40.0]);
149+
}
150+
151+
#[test]
152+
fn test_number_of_ticks() {
153+
assert_eq!(number_of_ticks(-7.93, 15.58, 4.0), 5);
154+
assert_eq!(number_of_ticks(-7.93, 15.58, 5.0), 5);
155+
assert_eq!(number_of_ticks(0.0, 15.0, 4.0), 4);
156+
assert_eq!(number_of_ticks(0.0, 15.0, 5.0), 4);
157+
assert_eq!(number_of_ticks(5.0, 21.0, 4.0), 4);
158+
assert_eq!(number_of_ticks(5.0, 21.0, 5.0), 4);
159+
assert_eq!(number_of_ticks(-8.0, 15.58, 4.0), 6);
160+
assert_eq!(number_of_ticks(-8.0, 15.58, 5.0), 5);
161+
}
162+
163+
#[test]
164+
fn test_calculate_tick_step_for_range() {
165+
assert_eq!(calculate_tick_step_for_range(0.0, 3.0, 6), 1.0);
166+
assert_eq!(calculate_tick_step_for_range(0.0, 6.0, 6), 2.0);
167+
assert_eq!(calculate_tick_step_for_range(0.0, 11.0, 6), 2.0);
168+
assert_eq!(calculate_tick_step_for_range(0.0, 14.0, 6), 4.0);
169+
assert_eq!(calculate_tick_step_for_range(0.0, 15.0, 6), 5.0);
170+
assert_eq!(calculate_tick_step_for_range(-1.0, 5.0, 6), 2.0);
171+
assert_eq!(calculate_tick_step_for_range(-7.93, 15.58, 6), 5.0);
172+
assert_eq!(calculate_tick_step_for_range(0.0, 0.06, 6), 0.02);
173+
}
174+
175+
#[test]
176+
fn test_calculate_ticks() {
177+
178+
macro_rules! assert_approx_eq {
179+
($a:expr, $b:expr) => ({
180+
let (a, b) = (&$a, &$b);
181+
assert!((*a - *b).abs() < 1.0e-6,
182+
"{} is not approximately equal to {}", *a, *b);
183+
})
184+
}
185+
186+
for (prod, want) in calculate_ticks(0.0, 1.0, 6)
187+
.iter()
188+
.zip([0.0, 0.2, 0.4, 0.6, 0.8, 1.0].iter()) {
189+
assert_approx_eq!(prod, want);
190+
}
191+
for (prod, want) in calculate_ticks(0.0, 2.0, 6)
192+
.iter()
193+
.zip([0.0, 0.4, 0.8, 1.2, 1.6, 2.0].iter()) {
194+
assert_approx_eq!(prod, want);
195+
}
196+
assert_eq!(calculate_ticks(0.0, 3.0, 6), [0.0, 1.0, 2.0, 3.0]);
197+
assert_eq!(calculate_ticks(0.0, 4.0, 6), [0.0, 1.0, 2.0, 3.0, 4.0]);
198+
assert_eq!(calculate_ticks(0.0, 5.0, 6), [0.0, 1.0, 2.0, 3.0, 4.0, 5.0]);
199+
assert_eq!(calculate_ticks(0.0, 6.0, 6), [0.0, 2.0, 4.0, 6.0]);
200+
assert_eq!(calculate_ticks(0.0, 7.0, 6), [0.0, 2.0, 4.0, 6.0]);
201+
assert_eq!(calculate_ticks(0.0, 8.0, 6), [0.0, 2.0, 4.0, 6.0, 8.0]);
202+
assert_eq!(calculate_ticks(0.0, 9.0, 6), [0.0, 2.0, 4.0, 6.0, 8.0]);
203+
assert_eq!(calculate_ticks(0.0, 10.0, 6),
204+
[0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
205+
assert_eq!(calculate_ticks(0.0, 11.0, 6),
206+
[0.0, 2.0, 4.0, 6.0, 8.0, 10.0]);
207+
assert_eq!(calculate_ticks(0.0, 12.0, 6), [0.0, 4.0, 8.0, 12.0]);
208+
assert_eq!(calculate_ticks(0.0, 13.0, 6), [0.0, 4.0, 8.0, 12.0]);
209+
assert_eq!(calculate_ticks(0.0, 14.0, 6), [0.0, 4.0, 8.0, 12.0]);
210+
assert_eq!(calculate_ticks(0.0, 15.0, 6), [0.0, 5.0, 10.0, 15.0]);
211+
assert_eq!(calculate_ticks(0.0, 16.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
212+
assert_eq!(calculate_ticks(0.0, 17.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
213+
assert_eq!(calculate_ticks(0.0, 18.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
214+
assert_eq!(calculate_ticks(0.0, 19.0, 6), [0.0, 4.0, 8.0, 12.0, 16.0]);
215+
assert_eq!(calculate_ticks(0.0, 20.0, 6),
216+
[0.0, 4.0, 8.0, 12.0, 16.0, 20.0]);
217+
assert_eq!(calculate_ticks(0.0, 21.0, 6),
218+
[0.0, 4.0, 8.0, 12.0, 16.0, 20.0]);
219+
assert_eq!(calculate_ticks(0.0, 22.0, 6),
220+
[0.0, 4.0, 8.0, 12.0, 16.0, 20.0]);
221+
assert_eq!(calculate_ticks(0.0, 23.0, 6),
222+
[0.0, 4.0, 8.0, 12.0, 16.0, 20.0]);
223+
assert_eq!(calculate_ticks(0.0, 24.0, 6), [0.0, 5.0, 10.0, 15.0, 20.0]);
224+
assert_eq!(calculate_ticks(0.0, 25.0, 6),
225+
[0.0, 5.0, 10.0, 15.0, 20.0, 25.0]);
226+
assert_eq!(calculate_ticks(0.0, 26.0, 6),
227+
[0.0, 5.0, 10.0, 15.0, 20.0, 25.0]);
228+
assert_eq!(calculate_ticks(0.0, 27.0, 6),
229+
[0.0, 5.0, 10.0, 15.0, 20.0, 25.0]);
230+
assert_eq!(calculate_ticks(0.0, 28.0, 6),
231+
[0.0, 5.0, 10.0, 15.0, 20.0, 25.0]);
232+
assert_eq!(calculate_ticks(0.0, 29.0, 6),
233+
[0.0, 5.0, 10.0, 15.0, 20.0, 25.0]);
234+
assert_eq!(calculate_ticks(0.0, 30.0, 6), [0.0, 10.0, 20.0, 30.0]);
235+
assert_eq!(calculate_ticks(0.0, 31.0, 6), [0.0, 10.0, 20.0, 30.0]);
236+
//...
237+
assert_eq!(calculate_ticks(0.0, 40.0, 6), [0.0, 10.0, 20.0, 30.0, 40.0]);
238+
assert_eq!(calculate_ticks(0.0, 50.0, 6),
239+
[0.0, 10.0, 20.0, 30.0, 40.0, 50.0]);
240+
assert_eq!(calculate_ticks(0.0, 60.0, 6), [0.0, 20.0, 40.0, 60.0]);
241+
assert_eq!(calculate_ticks(0.0, 70.0, 6), [0.0, 20.0, 40.0, 60.0]);
242+
assert_eq!(calculate_ticks(0.0, 80.0, 6), [0.0, 20.0, 40.0, 60.0, 80.0]);
243+
assert_eq!(calculate_ticks(0.0, 90.0, 6), [0.0, 20.0, 40.0, 60.0, 80.0]);
244+
assert_eq!(calculate_ticks(0.0, 100.0, 6),
245+
[0.0, 20.0, 40.0, 60.0, 80.0, 100.0]);
246+
assert_eq!(calculate_ticks(0.0, 110.0, 6),
247+
[0.0, 20.0, 40.0, 60.0, 80.0, 100.0]);
248+
assert_eq!(calculate_ticks(0.0, 120.0, 6), [0.0, 40.0, 80.0, 120.0]);
249+
assert_eq!(calculate_ticks(0.0, 130.0, 6), [0.0, 40.0, 80.0, 120.0]);
250+
assert_eq!(calculate_ticks(0.0, 140.0, 6), [0.0, 40.0, 80.0, 120.0]);
251+
assert_eq!(calculate_ticks(0.0, 150.0, 6), [0.0, 50.0, 100.0, 150.0]);
252+
//...
253+
assert_eq!(calculate_ticks(0.0, 3475.0, 6),
254+
[0.0, 1000.0, 2000.0, 3000.0]);
255+
256+
assert_eq!(calculate_ticks(-10.0, -3.0, 6), [-10.0, -8.0, -6.0, -4.0]);
257+
}
258+
}

src/histogram.rs

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
//! A module for Histograms
2+
//!
3+
//! TODO:
4+
//! - frequency or density option
5+
//! - Variable bins implies frequency
6+
//! - What should be the default?
7+
8+
#[derive(Debug)]
9+
pub struct Histogram {
10+
pub bin_bounds: Vec<f64>, // will have N_bins + 1 entries
11+
pub bin_counts: Vec<u32>, // will have N_bins entries
12+
pub bin_densities: Vec<f64>, // will have N_bins entries
13+
}
14+
15+
impl Histogram {
16+
pub fn from_vec(v: &[f64]) -> Histogram {
17+
18+
let max = v.iter().fold(-1. / 0., |a, &b| f64::max(a, b));
19+
let min = v.iter().fold(1. / 0., |a, &b| f64::min(a, b));
20+
21+
let num_bins = 30; // Number of bins
22+
23+
let mut bins = vec![0; num_bins];
24+
25+
let range = max - min;
26+
27+
let bin_width = (max - min) / num_bins as f64; // width of bin in real units
28+
29+
let mut bounds: Vec<f64> =
30+
(0..num_bins).map(|n| (n as f64 / num_bins as f64) * range + min).collect();
31+
bounds.push(max);
32+
let bounds = bounds;
33+
34+
for &val in v.iter() {
35+
let mut bin = ((val - min) / bin_width) as usize;
36+
if bin == num_bins && val == max {
37+
//We are right on the top-most bound
38+
bin = num_bins - 1;
39+
}
40+
bins[bin] += 1;
41+
}
42+
let density_per_bin = bins.iter().map(|&x| x as f64 / bin_width).collect();
43+
44+
Histogram {
45+
bin_bounds: bounds,
46+
bin_counts: bins,
47+
bin_densities: density_per_bin,
48+
}
49+
}
50+
51+
pub fn num_bins(&self) -> usize {
52+
self.bin_counts.len()
53+
}
54+
}

src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
pub mod histogram;
2+
pub mod axis;

0 commit comments

Comments
 (0)