use super::labels::{PlotBounds, value_to_plot_x};
use super::overlay::overlay_cell_if_blank_or_weak_area_fill;
use crate::annotations::{AnnotationCluster, AnnotationEvent, cluster_events_by};
use ratatui::prelude::*;
pub(super) fn cluster_badge(count: usize) -> char {
match count {
0 => ' ',
1 => '•',
2..=9 => char::from_digit(count as u32, 10).unwrap(),
_ => '+',
}
}
pub(super) fn terminal_clusters<'a>(
events: Vec<&'a AnnotationEvent>,
x_bounds: [f64; 2],
plot: PlotBounds,
) -> Vec<AnnotationCluster<'a>> {
cluster_events_by(events, |timestamp| {
value_to_plot_x(timestamp, x_bounds, plot).map(u32::from)
})
}
pub(super) fn render_annotation_clusters(
frame: &mut Frame,
clusters: &[AnnotationCluster<'_>],
plot: PlotBounds,
strong_data: Option<&ratatui::buffer::Buffer>,
color: Color,
) {
for cluster in clusters {
let Ok(x) = u16::try_from(cluster.coordinate) else {
continue;
};
if x < plot.left || x >= plot.right {
continue;
}
render_marker_cell(
frame,
strong_data,
x,
plot.top,
cluster_badge(cluster.events.len()),
color,
);
for y in plot.top.saturating_add(1)..=plot.bottom {
render_marker_cell(frame, strong_data, x, y, '┊', color);
}
}
}
fn render_marker_cell(
frame: &mut Frame,
strong_data: Option<&ratatui::buffer::Buffer>,
x: u16,
y: u16,
marker: char,
color: Color,
) {
let Some(destination) = frame.buffer_mut().cell_mut((x, y)) else {
return;
};
let empty_strong = ratatui::buffer::Cell::default();
let strong = strong_data
.and_then(|buffer| buffer.cell((x, y)))
.unwrap_or(&empty_strong);
overlay_marker_cell(destination, strong, marker, color);
}
fn overlay_marker_cell(
destination: &mut ratatui::buffer::Cell,
strong_data: &ratatui::buffer::Cell,
marker: char,
color: Color,
) {
let mut source = ratatui::buffer::Cell::default();
source
.set_char(marker)
.set_style(Style::default().fg(color));
overlay_cell_if_blank_or_weak_area_fill(destination, &source, strong_data);
}
pub(super) fn active_cluster<'a>(
clusters: &'a [AnnotationCluster<'a>],
cursor_x: Option<f64>,
x_bounds: [f64; 2],
plot: PlotBounds,
) -> Option<&'a AnnotationCluster<'a>> {
let cursor_column = value_to_plot_x(cursor_x?, x_bounds, plot)?;
clusters
.iter()
.find(|cluster| cluster.coordinate == u32::from(cursor_column))
}
#[cfg(test)]
mod tests {
use super::*;
use ratatui::style::Color;
#[test]
fn badge_matches_cluster_size() {
assert_eq!(cluster_badge(1), '•');
assert_eq!(cluster_badge(2), '2');
assert_eq!(cluster_badge(9), '9');
assert_eq!(cluster_badge(10), '+');
}
#[test]
fn marker_does_not_replace_strong_data_cell() {
let mut destination = ratatui::buffer::Cell::default();
destination.set_char('x');
let strong = ratatui::buffer::Cell::default();
overlay_marker_cell(&mut destination, &strong, '┊', Color::Yellow);
assert_eq!(destination.symbol(), "x");
}
}