pub fn lttb(x: &[f64], y: &[f64], target: usize) -> (Vec<f64>, Vec<f64>) {
assert_eq!(x.len(), y.len(), "lttb requires series of equal length");
let target = target.max(3);
let points: Vec<(f64, f64)> = x
.iter()
.zip(y.iter())
.filter(|(x, y)| x.is_finite() && y.is_finite())
.map(|(&x, &y)| (x, y))
.collect();
if points.len() <= target {
return points.into_iter().unzip();
}
let mut kept: Vec<(f64, f64)> = Vec::with_capacity(target);
kept.push(points[0]);
let interior = target - 2;
let span = (points.len() - 2) as f64 / interior as f64;
let mut previous = points[0];
for bucket in 0..interior {
let start = 1 + (bucket as f64 * span) as usize;
let end = 1 + (((bucket + 1) as f64 * span) as usize).min(points.len() - 2);
let ahead_start = end;
let ahead_end = if bucket + 1 == interior {
points.len()
} else {
1 + (((bucket + 2) as f64 * span) as usize).min(points.len() - 1)
};
let ahead_count = (ahead_end - ahead_start).max(1) as f64;
let ahead = points[ahead_start..ahead_end.max(ahead_start + 1)]
.iter()
.fold((0.0, 0.0), |acc, point| (acc.0 + point.0, acc.1 + point.1));
let ahead = (ahead.0 / ahead_count, ahead.1 / ahead_count);
let mut best = points[start];
let mut best_area = -1.0;
for &point in &points[start..end.max(start + 1)] {
let area = ((previous.0 - ahead.0) * (point.1 - previous.1)
- (previous.0 - point.0) * (ahead.1 - previous.1))
.abs();
if area > best_area {
best_area = area;
best = point;
}
}
kept.push(best);
previous = best;
}
kept.push(points[points.len() - 1]);
kept.into_iter().unzip()
}
#[cfg(test)]
#[path = "tests/lttb_tests.rs"]
mod tests;