235 lines
6.8 KiB
Rust
235 lines
6.8 KiB
Rust
//! The chrome as data. Axes, tick labels, gridline positions, the readout,
|
|
//! markers' and spans' places and the direct-label anchors are handed to the
|
|
//! host, which draws them in the DOM: crisp at any zoom, themed by the page's
|
|
//! tokens, and read by assistive technology. Text wears the page's ink
|
|
//! tokens; a series' colour only marks its key (a short line beside its
|
|
//! name).
|
|
//!
|
|
//! Positions are CSS pixels from the plot area's top-left corner. Every
|
|
//! list keeps its capacity, so a frame writes the chrome without allocating;
|
|
//! `labels_changed` tells the host when text (not just positions) changed.
|
|
|
|
use crate::annot::Tint;
|
|
use crate::label::Label;
|
|
use crate::layout::Rect;
|
|
use crate::view::Mode;
|
|
use alloc::vec::Vec;
|
|
|
|
/// Most ticks an axis carries.
|
|
pub const MAX_TICKS: usize = 32;
|
|
|
|
/// One tick: where, and its label. Gridlines go at the same positions.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
|
pub struct Tick {
|
|
/// Position along the axis (x for the time axis, y for a lane), CSS px.
|
|
pub pos: f32,
|
|
/// The label (in the axis's unit, prefix included in `Axis::unit`).
|
|
pub label: Label,
|
|
}
|
|
|
|
/// An axis.
|
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
pub struct Axis {
|
|
/// Ticks, in order.
|
|
pub ticks: Vec<Tick>,
|
|
/// The unit with its prefix ("mV", "ms", "m:ss").
|
|
pub unit: Label,
|
|
/// Whether any label or the unit changed since the last frame.
|
|
pub labels_changed: bool,
|
|
}
|
|
|
|
impl Axis {
|
|
/// An axis with room for [`MAX_TICKS`].
|
|
pub fn new() -> Axis {
|
|
Axis {
|
|
ticks: Vec::with_capacity(MAX_TICKS),
|
|
unit: Label::new(),
|
|
labels_changed: true,
|
|
}
|
|
}
|
|
|
|
/// Starts writing a frame's ticks.
|
|
pub fn begin(&mut self) -> AxisWriter<'_> {
|
|
AxisWriter {
|
|
axis: self,
|
|
n: 0,
|
|
changed: false,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Writes an axis's ticks in place, noting whether any label changed.
|
|
pub struct AxisWriter<'a> {
|
|
axis: &'a mut Axis,
|
|
n: usize,
|
|
changed: bool,
|
|
}
|
|
|
|
impl AxisWriter<'_> {
|
|
/// Adds a tick; `write` fills its label.
|
|
pub fn tick(&mut self, pos: f32, write: impl FnOnce(&mut Label)) {
|
|
if self.n >= MAX_TICKS {
|
|
return;
|
|
}
|
|
let mut l = Label::new();
|
|
write(&mut l);
|
|
if self.n < self.axis.ticks.len() {
|
|
let t = &mut self.axis.ticks[self.n];
|
|
if t.label != l {
|
|
self.changed = true;
|
|
t.label = l;
|
|
}
|
|
t.pos = pos;
|
|
} else {
|
|
self.axis.ticks.push(Tick { pos, label: l });
|
|
self.changed = true;
|
|
}
|
|
self.n += 1;
|
|
}
|
|
|
|
/// Sets the unit and finishes.
|
|
pub fn finish(self, unit: &Label) {
|
|
let changed = self.changed || self.n != self.axis.ticks.len() || self.axis.unit != *unit;
|
|
self.axis.ticks.truncate(self.n);
|
|
self.axis.unit = *unit;
|
|
self.axis.labels_changed = changed;
|
|
}
|
|
}
|
|
|
|
/// A lane's chrome.
|
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
pub struct LaneChrome {
|
|
/// Where the lane is.
|
|
pub rect: Rect,
|
|
/// Its vertical axis (positions are y).
|
|
pub y: Axis,
|
|
/// Its range this frame.
|
|
pub lo: f64,
|
|
/// Its range this frame.
|
|
pub hi: f64,
|
|
/// The y of zero, if zero is in range (a baseline).
|
|
pub zero: Option<f32>,
|
|
}
|
|
|
|
/// One series at the cursor: the value first, the name after.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
|
pub struct ReadoutRow {
|
|
/// Lane.
|
|
pub lane: u16,
|
|
/// Series in the lane.
|
|
pub series: u16,
|
|
/// Its colour slot (for its key).
|
|
pub slot: u8,
|
|
/// The value at the cursor's sample ("—" when there is none).
|
|
pub value: Label,
|
|
/// The unit with prefix.
|
|
pub unit: Label,
|
|
/// Where the value is on screen (CSS px), NaN when none.
|
|
pub y: f32,
|
|
/// The value as a number.
|
|
pub v: f64,
|
|
/// When the cursor's pixel column holds more than one sample: their
|
|
/// least and greatest (empty labels otherwise).
|
|
pub col_min: Label,
|
|
/// See `col_min`.
|
|
pub col_max: Label,
|
|
}
|
|
|
|
/// The crosshair and its readout.
|
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
pub struct Readout {
|
|
/// Whether there is a cursor.
|
|
pub visible: bool,
|
|
/// Its x (CSS px), snapped to the focused lane's nearest sample.
|
|
pub x: f32,
|
|
/// Its page time.
|
|
pub t: f64,
|
|
/// Its time, written on the time axis's terms with finer decimals.
|
|
pub time: Label,
|
|
/// The time's unit.
|
|
pub time_unit: Label,
|
|
/// The lane the cursor snaps to.
|
|
pub focus_lane: u16,
|
|
/// Every series of every lane at that time.
|
|
pub rows: Vec<ReadoutRow>,
|
|
}
|
|
|
|
/// A marker or span on screen.
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
|
pub struct MarkChrome {
|
|
/// x of a marker, or a span's left edge (CSS px).
|
|
pub x0: f32,
|
|
/// A span's right edge (equal to `x0` for a marker).
|
|
pub x1: f32,
|
|
/// The host's id.
|
|
pub id: u32,
|
|
/// Its tint.
|
|
pub tint: Tint,
|
|
}
|
|
|
|
/// Where a series' newest visible value is, for a direct label.
|
|
#[derive(Clone, Copy, Debug, PartialEq, Default)]
|
|
pub struct EndLabel {
|
|
/// Lane.
|
|
pub lane: u16,
|
|
/// Series in the lane.
|
|
pub series: u16,
|
|
/// y (CSS px), NaN when nothing is visible.
|
|
pub y: f32,
|
|
}
|
|
|
|
/// Everything the host draws around and over the canvas.
|
|
#[derive(Clone, Debug, PartialEq, Default)]
|
|
pub struct Chrome {
|
|
/// Frames made.
|
|
pub frame: u64,
|
|
/// The mode.
|
|
pub mode: Mode,
|
|
/// The window shown (page times).
|
|
pub window: (f64, f64),
|
|
/// The time axis (positions are x).
|
|
pub x: Axis,
|
|
/// The lanes, top to bottom.
|
|
pub lanes: Vec<LaneChrome>,
|
|
/// The cursor's readout.
|
|
pub readout: Readout,
|
|
/// Markers on screen.
|
|
pub markers: Vec<MarkChrome>,
|
|
/// Spans on screen.
|
|
pub spans: Vec<MarkChrome>,
|
|
/// Direct-label anchors.
|
|
pub ends: Vec<EndLabel>,
|
|
/// The scope's trigger x (CSS px) when one is shown.
|
|
pub trigger_x: Option<f32>,
|
|
/// The trigger's level as y in its lane (CSS px).
|
|
pub trigger_y: Option<f32>,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn writing_notes_label_changes() {
|
|
let mut a = Axis::new();
|
|
let mut w = a.begin();
|
|
w.tick(1.0, |l| l.push_str("0"));
|
|
w.tick(2.0, |l| l.push_str("5"));
|
|
w.finish(&Label::of("s"));
|
|
assert!(a.labels_changed);
|
|
let cap = a.ticks.capacity();
|
|
// Same labels, new positions: only positions change.
|
|
let mut w = a.begin();
|
|
w.tick(1.5, |l| l.push_str("0"));
|
|
w.tick(2.5, |l| l.push_str("5"));
|
|
w.finish(&Label::of("s"));
|
|
assert!(!a.labels_changed);
|
|
assert_eq!(a.ticks[1].pos, 2.5);
|
|
let mut w = a.begin();
|
|
w.tick(1.5, |l| l.push_str("0"));
|
|
w.finish(&Label::of("s"));
|
|
assert!(a.labels_changed);
|
|
assert_eq!(a.ticks.len(), 1);
|
|
assert_eq!(a.ticks.capacity(), cap);
|
|
}
|
|
}
|