use crate::{AnnotationGraph, Measurement, Variable};
use charming::{component::Axis, datatype::DataPoint, element::AxisType};
const BUCKET_COUNT: usize = 10;
const DECIMAL_PLACES: usize = 50;
fn find_min_max_duration(pkg_durations: &[f64]) -> (f64, f64) {
assert!(!pkg_durations.is_empty());
let mut min = f64::MAX;
let mut max = f64::MIN;
for duration in pkg_durations {
min = duration.min(min);
max = duration.max(max);
}
assert_ne!(min, f64::MAX);
assert_ne!(max, f64::MIN);
assert!(min.is_sign_positive());
assert!(max.is_sign_positive());
(min, max)
}
pub fn axes(annotations: &AnnotationGraph) -> (Axis, Axis) {
let packages = annotations.packages();
let pkg_durations = packages
.map(|id| annotations.variable(id, Variable::UnitDuration, Measurement::Exact))
.collect::<Vec<_>>();
let (min_duration, max_duration) = find_min_max_duration(&pkg_durations);
let bucket_width = (max_duration - min_duration) / (BUCKET_COUNT as f64);
let mut x_labels = Vec::with_capacity(BUCKET_COUNT);
for bucket_index in 0..BUCKET_COUNT {
// The start time is offset, first bucket starts at min_duration
let start_time = min_duration + (bucket_width * (bucket_index as f64));
// The label is the start time rounded to 2 decimal places
x_labels.push(format!("{start_time:.2}"));
}
let x_axis = Axis::new().type_(AxisType::Category).data(x_labels);
let y_axis = Axis::new().type_(AxisType::Value);
(x_axis, y_axis)
}
pub fn data(annotations: &AnnotationGraph) -> Vec<DataPoint> {
let packages = annotations.packages();
let pkg_durations = packages
.map(|id| annotations.variable(id, Variable::UnitDuration, Measurement::Exact))
.collect::<Vec<_>>();
let (min_duration, max_duration) = find_min_max_duration(&pkg_durations);
// Make sure to start the buckets at min, not 0
let bucket_width = (max_duration - min_duration) / (BUCKET_COUNT as f64);
let mut buckets = [0_u64; BUCKET_COUNT];
for duration in pkg_durations {
let relative_duration = duration - min_duration;
let remainder = relative_duration % bucket_width;
// Calculate the nearest multiple of bucket_size
let next_multiple = (relative_duration - remainder) / bucket_width;
// Make sure we have an integer before casting
let float_error_margin = 2_f64.powf(1_f64 - (DECIMAL_PLACES as f64));
assert!((next_multiple - next_multiple.round()).abs() <= float_error_margin);
// assert_eq!(next_multiple, next_multiple.round());
let bucket_index = (next_multiple.round()) as usize;
// Increment the frequency of the relevant bucket
if bucket_index == BUCKET_COUNT {
// The max duration should be the only one in this final bucket
assert_eq!(duration, max_duration);
buckets[bucket_index - 1] += 1;
} else {
// Every other duration should hit this case instead
buckets[bucket_index] += 1;
}
}
// Convert the buckets into `charming::datatype::DataPoint`s
buckets
.into_iter()
.map(|bucket| (bucket as i64).into())
.collect()
}