Data holes + fix colours
This commit is contained in:
+43
-6
@@ -3,8 +3,31 @@ use egui::ecolor::Hsva;
|
||||
use egui::{Color32, Vec2b};
|
||||
use egui_plot::{AxisHints, Legend, Line, Plot, PlotPoints, VPlacement};
|
||||
|
||||
use egui_plot::PlotPoint;
|
||||
|
||||
use crate::signal::{decimate, nearest_y, PlottedSignal};
|
||||
|
||||
/// Split points into runs separated by x-gaps larger than `threshold`,
|
||||
/// so the line is broken across holes in the data instead of bridged.
|
||||
fn split_at_gaps(points: Vec<PlotPoint>, threshold: f64) -> Vec<Vec<PlotPoint>> {
|
||||
let mut segments = Vec::new();
|
||||
let mut current = Vec::new();
|
||||
let mut prev_x: Option<f64> = None;
|
||||
for p in points {
|
||||
if let Some(px) = prev_x {
|
||||
if p.x - px > threshold && !current.is_empty() {
|
||||
segments.push(std::mem::take(&mut current));
|
||||
}
|
||||
}
|
||||
prev_x = Some(p.x);
|
||||
current.push(p);
|
||||
}
|
||||
if !current.is_empty() {
|
||||
segments.push(current);
|
||||
}
|
||||
segments
|
||||
}
|
||||
|
||||
const MIN_PLOT_HEIGHT: f32 = 80.0;
|
||||
const Y_AXIS_MIN_THICKNESS: f32 = 48.0;
|
||||
|
||||
@@ -41,9 +64,11 @@ fn full_x_range(signals: &[PlottedSignal]) -> (f64, f64) {
|
||||
(min, max)
|
||||
}
|
||||
|
||||
fn color_for(id: u64) -> Color32 {
|
||||
let mixed = (id.wrapping_mul(2654435761)) as u32;
|
||||
let hue = mixed as f32 / u32::MAX as f32;
|
||||
/// Per-plot signal color. Index-based with golden-ratio hue spacing so
|
||||
/// neighbours on the same plot stay visually distinct.
|
||||
fn color_for_index(index: usize) -> Color32 {
|
||||
const GOLDEN_RATIO_CONJUGATE: f32 = 0.618_034;
|
||||
let hue = (index as f32 * GOLDEN_RATIO_CONJUGATE).fract();
|
||||
Hsva::new(hue, 0.85, 0.9, 1.0).into()
|
||||
}
|
||||
|
||||
@@ -119,12 +144,24 @@ pub fn show_plot_panel(
|
||||
let x_min = bounds.min()[0];
|
||||
let x_max = bounds.max()[0];
|
||||
let pixel_width = plot_ui.transform().frame().width() as usize;
|
||||
let bucket_w = (x_max - x_min) / pixel_width.max(1) as f64;
|
||||
let mut buf = Vec::new();
|
||||
for sig in &group.signals {
|
||||
for (sig_index, sig) in group.signals.iter().enumerate() {
|
||||
decimate(&sig.points, x_min, x_max, pixel_width.max(1), &mut buf);
|
||||
let plot_points = PlotPoints::Owned(std::mem::take(&mut buf));
|
||||
// When zoomed out, adjacent decimated points are bucket_w
|
||||
// apart, so don't split on anything smaller than that.
|
||||
let threshold = sig.gap_threshold.max(bucket_w * 1.5);
|
||||
let segments = split_at_gaps(std::mem::take(&mut buf), threshold);
|
||||
let color = color_for_index(sig_index);
|
||||
let name = line_label(sig, prev_cursor_x);
|
||||
plot_ui.line(Line::new(name, plot_points).color(color_for(sig.id)));
|
||||
for (i, seg) in segments.into_iter().enumerate() {
|
||||
// Only the first segment carries the name so the
|
||||
// legend shows a single entry per signal.
|
||||
let seg_name = if i == 0 { name.clone() } else { String::new() };
|
||||
plot_ui.line(
|
||||
Line::new(seg_name, PlotPoints::Owned(seg)).color(color),
|
||||
);
|
||||
}
|
||||
}
|
||||
plot_ui.pointer_coordinate().map(|c| c.x)
|
||||
});
|
||||
|
||||
@@ -6,6 +6,20 @@ pub struct PlottedSignal {
|
||||
pub label: String,
|
||||
pub unit: String,
|
||||
pub points: Vec<PlotPoint>,
|
||||
pub gap_threshold: f64,
|
||||
}
|
||||
|
||||
/// A jump in x larger than this is treated as a hole in the data and the
|
||||
/// line is broken rather than bridged. Based on the median sample interval.
|
||||
fn compute_gap_threshold(points: &[PlotPoint]) -> f64 {
|
||||
if points.len() < 2 {
|
||||
return f64::INFINITY;
|
||||
}
|
||||
let mut deltas: Vec<f64> =
|
||||
points.windows(2).map(|w| w[1].x - w[0].x).collect();
|
||||
deltas.sort_by(|a, b| a.total_cmp(b));
|
||||
let median = deltas[deltas.len() / 2];
|
||||
(median * 10.0).max(f64::MIN_POSITIVE)
|
||||
}
|
||||
|
||||
pub fn compute_signal(
|
||||
@@ -25,11 +39,13 @@ pub fn compute_signal(
|
||||
y: libsvld::motec::extract_signal(bytes, &sig.signal),
|
||||
});
|
||||
}
|
||||
let gap_threshold = compute_gap_threshold(&points);
|
||||
Some(PlottedSignal {
|
||||
id,
|
||||
label: sig.name.clone(),
|
||||
unit: sig.unit.clone(),
|
||||
points,
|
||||
gap_threshold,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user