#![warn(missing_docs)]
use glam::Vec3;
use crate::color::{Color, ColorSpace};
use crate::plot::scale::Scale;
use crate::plot::series::Sample;
use crate::scene::geometry::{LineData, LineSegment, PointData, ScenePoint};
#[derive(Clone, Debug, Default, PartialEq)]
pub struct LoweredLine {
pub segments: LineData,
pub joins: PointData,
}
fn srgba(color: Color) -> [f32; 4] {
let c = color.convert_to(ColorSpace::SRGB);
[c.r, c.g, c.b, c.a]
}
fn position(s: Sample, x: Scale, y: Scale, origin: (f64, f64)) -> Vec3 {
Vec3::new(x.map(s.x, origin.0), y.map(s.y, origin.1), 0.0)
}
pub fn lower_line(
samples: &[Sample],
x: Scale,
y: Scale,
origin: (f64, f64),
color: Color,
) -> LoweredLine {
let rgba = srgba(color);
let positions: Vec<Vec3> = samples.iter().map(|&s| position(s, x, y, origin)).collect();
let segments = positions
.windows(2)
.map(|w| LineSegment {
start: w[0],
end: w[1],
color: rgba,
})
.collect();
let joins = positions
.iter()
.map(|&p| ScenePoint {
position: p,
color: rgba,
})
.collect();
LoweredLine {
segments: LineData { segments },
joins: PointData { points: joins },
}
}
pub fn lower_scatter(
samples: &[Sample],
x: Scale,
y: Scale,
origin: (f64, f64),
color: Color,
) -> PointData {
let rgba = srgba(color);
PointData {
points: samples
.iter()
.map(|&s| ScenePoint {
position: position(s, x, y, origin),
color: rgba,
})
.collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn s(x: f64, y: f64) -> Sample {
Sample::new(x, y)
}
#[test]
fn line_segment_and_join_counts() {
let pts = [s(0.0, 0.0), s(1.0, 1.0), s(2.0, 0.0)];
let l = lower_line(
&pts,
Scale::linear(),
Scale::linear(),
(0.0, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert_eq!(l.segments.segments.len(), 2); assert_eq!(l.joins.points.len(), 3); }
#[test]
fn line_positions_are_origin_relative_scale_space() {
let pts = [s(100.0, 10.0), s(101.0, 12.0)];
let l = lower_line(
&pts,
Scale::linear(),
Scale::linear(),
(100.0, 10.0),
Color::srgb_u8(255, 255, 255),
);
assert_eq!(l.segments.segments[0].start, Vec3::new(0.0, 0.0, 0.0));
assert_eq!(l.segments.segments[0].end, Vec3::new(1.0, 2.0, 0.0));
}
#[test]
fn time_origin_keeps_f32_precision() {
let base = 1_780_000_000.0_f64;
let pts = [s(base, 1.0), s(base + 0.5, 2.0)];
let l = lower_line(
&pts,
Scale::time(),
Scale::linear(),
(base, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert_eq!(l.segments.segments[0].start.x, 0.0);
assert_eq!(l.segments.segments[0].end.x, 0.5);
}
#[test]
fn single_sample_is_a_dot() {
let l = lower_line(
&[s(5.0, 5.0)],
Scale::linear(),
Scale::linear(),
(0.0, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert!(l.segments.segments.is_empty());
assert_eq!(l.joins.points.len(), 1);
}
#[test]
fn empty_is_empty() {
let l = lower_line(
&[],
Scale::linear(),
Scale::linear(),
(0.0, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert!(l.segments.segments.is_empty());
assert!(l.joins.points.is_empty());
}
#[test]
fn log_x_maps_through_warp() {
let l = lower_line(
&[s(1.0, 0.0), s(1000.0, 0.0)],
Scale::log(),
Scale::linear(),
(1.0, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert!((l.segments.segments[0].end.x - 3.0).abs() < 1e-5);
}
#[test]
fn scatter_maps_each_sample() {
let pts = [s(0.0, 0.0), s(2.0, 4.0)];
let p = lower_scatter(
&pts,
Scale::linear(),
Scale::linear(),
(0.0, 0.0),
Color::srgb_u8(255, 255, 255),
);
assert_eq!(p.points.len(), 2);
assert_eq!(p.points[1].position, Vec3::new(2.0, 4.0, 0.0));
}
}