1#![warn(missing_docs)]
27
28use glam::Vec3;
29
30use crate::color::{Color, ColorSpace};
31use crate::plot::scale::Scale;
32use crate::plot::series::Sample;
33use crate::scene::geometry::{LineData, LineSegment, PointData, ScenePoint};
34
35#[derive(Clone, Debug, Default, PartialEq)]
38pub struct LoweredLine {
39 pub segments: LineData,
41 pub joins: PointData,
43}
44
45fn srgba(color: Color) -> [f32; 4] {
48 let c = color.convert_to(ColorSpace::SRGB);
49 [c.r, c.g, c.b, c.a]
50}
51
52fn position(s: Sample, x: Scale, y: Scale, origin: (f64, f64)) -> Vec3 {
54 Vec3::new(x.map(s.x, origin.0), y.map(s.y, origin.1), 0.0)
55}
56
57pub fn lower_line(
64 samples: &[Sample],
65 x: Scale,
66 y: Scale,
67 origin: (f64, f64),
68 color: Color,
69) -> LoweredLine {
70 let rgba = srgba(color);
71 let positions: Vec<Vec3> = samples.iter().map(|&s| position(s, x, y, origin)).collect();
72
73 let segments = positions
74 .windows(2)
75 .map(|w| LineSegment {
76 start: w[0],
77 end: w[1],
78 color: rgba,
79 })
80 .collect();
81
82 let joins = positions
83 .iter()
84 .map(|&p| ScenePoint {
85 position: p,
86 color: rgba,
87 })
88 .collect();
89
90 LoweredLine {
91 segments: LineData { segments },
92 joins: PointData { points: joins },
93 }
94}
95
96pub fn lower_scatter(
99 samples: &[Sample],
100 x: Scale,
101 y: Scale,
102 origin: (f64, f64),
103 color: Color,
104) -> PointData {
105 let rgba = srgba(color);
106 PointData {
107 points: samples
108 .iter()
109 .map(|&s| ScenePoint {
110 position: position(s, x, y, origin),
111 color: rgba,
112 })
113 .collect(),
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use super::*;
120
121 fn s(x: f64, y: f64) -> Sample {
122 Sample::new(x, y)
123 }
124
125 #[test]
126 fn line_segment_and_join_counts() {
127 let pts = [s(0.0, 0.0), s(1.0, 1.0), s(2.0, 0.0)];
128 let l = lower_line(
129 &pts,
130 Scale::linear(),
131 Scale::linear(),
132 (0.0, 0.0),
133 Color::srgb_u8(255, 255, 255),
134 );
135 assert_eq!(l.segments.segments.len(), 2); assert_eq!(l.joins.points.len(), 3); }
138
139 #[test]
140 fn line_positions_are_origin_relative_scale_space() {
141 let pts = [s(100.0, 10.0), s(101.0, 12.0)];
142 let l = lower_line(
143 &pts,
144 Scale::linear(),
145 Scale::linear(),
146 (100.0, 10.0),
147 Color::srgb_u8(255, 255, 255),
148 );
149 assert_eq!(l.segments.segments[0].start, Vec3::new(0.0, 0.0, 0.0));
151 assert_eq!(l.segments.segments[0].end, Vec3::new(1.0, 2.0, 0.0));
153 }
154
155 #[test]
156 fn time_origin_keeps_f32_precision() {
157 let base = 1_780_000_000.0_f64;
160 let pts = [s(base, 1.0), s(base + 0.5, 2.0)];
161 let l = lower_line(
162 &pts,
163 Scale::time(),
164 Scale::linear(),
165 (base, 0.0),
166 Color::srgb_u8(255, 255, 255),
167 );
168 assert_eq!(l.segments.segments[0].start.x, 0.0);
169 assert_eq!(l.segments.segments[0].end.x, 0.5);
170 }
171
172 #[test]
173 fn single_sample_is_a_dot() {
174 let l = lower_line(
175 &[s(5.0, 5.0)],
176 Scale::linear(),
177 Scale::linear(),
178 (0.0, 0.0),
179 Color::srgb_u8(255, 255, 255),
180 );
181 assert!(l.segments.segments.is_empty());
182 assert_eq!(l.joins.points.len(), 1);
183 }
184
185 #[test]
186 fn empty_is_empty() {
187 let l = lower_line(
188 &[],
189 Scale::linear(),
190 Scale::linear(),
191 (0.0, 0.0),
192 Color::srgb_u8(255, 255, 255),
193 );
194 assert!(l.segments.segments.is_empty());
195 assert!(l.joins.points.is_empty());
196 }
197
198 #[test]
199 fn log_x_maps_through_warp() {
200 let l = lower_line(
202 &[s(1.0, 0.0), s(1000.0, 0.0)],
203 Scale::log(),
204 Scale::linear(),
205 (1.0, 0.0),
206 Color::srgb_u8(255, 255, 255),
207 );
208 assert!((l.segments.segments[0].end.x - 3.0).abs() < 1e-5);
209 }
210
211 #[test]
212 fn scatter_maps_each_sample() {
213 let pts = [s(0.0, 0.0), s(2.0, 4.0)];
214 let p = lower_scatter(
215 &pts,
216 Scale::linear(),
217 Scale::linear(),
218 (0.0, 0.0),
219 Color::srgb_u8(255, 255, 255),
220 );
221 assert_eq!(p.points.len(), 2);
222 assert_eq!(p.points[1].position, Vec3::new(2.0, 4.0, 0.0));
223 }
224}