use std::collections::BTreeMap;
use itertools::Itertools as _;
use re_log_types::{TimeInt, TimeRange, TimeType};
#[derive(Clone, Debug)]
pub(crate) struct TimelineAxis {
pub ranges: vec1::Vec1<TimeRange>,
}
impl TimelineAxis {
pub fn new<T>(time_type: TimeType, times: &BTreeMap<TimeInt, T>) -> Self {
crate::profile_function!();
assert!(!times.is_empty());
let gap_threshold = gap_size_heuristic(time_type, times);
Self {
ranges: create_ranges(times, gap_threshold),
}
}
pub fn sum_time_lengths(&self) -> TimeInt {
self.ranges.iter().map(|t| t.length()).sum()
}
pub fn min(&self) -> TimeInt {
self.ranges.first().min
}
}
fn time_abs_diff(a: TimeInt, b: TimeInt) -> u64 {
a.as_i64().abs_diff(b.as_i64())
}
fn gap_size_heuristic<T>(time_type: TimeType, times: &BTreeMap<TimeInt, T>) -> u64 {
crate::profile_function!();
assert!(!times.is_empty());
if times.len() <= 2 {
return u64::MAX;
}
let total_time_span = time_abs_diff(
*times.first_key_value().unwrap().0,
*times.last_key_value().unwrap().0,
);
let min_gap_size: u64 = match time_type {
TimeType::Sequence => 9,
TimeType::Time => 100_000_000, };
let mut gap_sizes = {
crate::profile_scope!("collect_gaps");
times
.keys()
.tuple_windows()
.map(|(a, b)| time_abs_diff(*a, *b))
.filter(|&gap_size| gap_size > min_gap_size)
.collect_vec()
};
gap_sizes.sort_unstable();
let max_collapses: usize = ((times.len() - 1) / 3).min(20);
let min_collapse_fraction: f64 = (2.0 / (times.len() - 1) as f64).max(0.35);
let mut gap_threshold = u64::MAX;
let mut uncollapsed_time = total_time_span;
for &gap in gap_sizes.iter().rev().take(max_collapses) {
let gap_fraction = gap as f64 / uncollapsed_time as f64;
if gap_fraction > min_collapse_fraction {
gap_threshold = gap;
uncollapsed_time -= gap;
} else {
break; }
}
gap_threshold
}
fn create_ranges<T>(times: &BTreeMap<TimeInt, T>, gap_threshold: u64) -> vec1::Vec1<TimeRange> {
crate::profile_function!();
let mut values_it = times.keys();
let mut ranges = vec1::vec1![TimeRange::point(*values_it.next().unwrap())];
for &new_value in values_it {
let last_max = &mut ranges.last_mut().max;
if time_abs_diff(*last_max, new_value) < gap_threshold {
*last_max = new_value; } else {
ranges.push(TimeRange::point(new_value)); }
}
ranges
}
#[cfg(test)]
mod tests {
use super::*;
use re_arrow_store::TimeRange;
fn ranges(times: &[i64]) -> vec1::Vec1<TimeRange> {
#[allow(clippy::zero_sized_map_values)]
let times: BTreeMap<TimeInt, ()> = times
.iter()
.map(|&seq| (TimeInt::from_sequence(seq), ()))
.collect();
TimelineAxis::new(TimeType::Sequence, ×).ranges
}
#[test]
fn test_time_axis() {
assert_eq!(1, ranges(&[1]).len());
assert_eq!(1, ranges(&[1, 2, 3, 4]).len());
assert_eq!(1, ranges(&[10, 20, 30, 40]).len());
assert_eq!(1, ranges(&[1, 2, 3, 11, 12, 13]).len(), "Too small gap");
assert_eq!(2, ranges(&[10, 20, 30, 110, 120, 130]).len());
assert_eq!(1, ranges(&[10, 1000]).len(), "not enough numbers");
}
}