1use std::ops::Range;
2
3use densa_core::{Color, Rect};
4use densa_draw::Canvas;
5
6use crate::series::{points_within, with_neighbours};
7use crate::{Plot, Points};
8
9#[derive(Debug, Clone, Copy, PartialEq)]
12#[non_exhaustive]
13pub struct LineStyle {
14 pub color: Color,
16 pub width: f32,
18}
19
20impl LineStyle {
21 pub const fn new(color: Color) -> Self {
23 Self { color, width: 1.0 }
24 }
25
26 #[must_use]
28 pub fn with_width(mut self, width: f32) -> Self {
29 self.width = usable_width(width);
30 self
31 }
32}
33
34fn usable_width(width: f32) -> f32 {
36 if width.is_finite() {
37 width.max(0.1)
38 } else {
39 1.0
40 }
41}
42
43pub fn paint_line<P: Points + ?Sized>(
54 canvas: &mut Canvas<'_>,
55 plot: &Plot,
56 points: &P,
57 style: LineStyle,
58) {
59 let area = plot.area();
60 if area.is_empty() {
61 return;
62 }
63 let state = canvas.state();
64 canvas.clip(area);
65 let range = with_neighbours(points_within(points, &plot.x()), points.len());
66 let at = range.map(|index| (plot.x_at(points.x(index)), plot.y_at(points.y(index))));
67 let shown = canvas.clip_rect();
69 let columns = shown.x0.floor() as i64..shown.x1.ceil() as i64;
71 let half = f64::from(usable_width(style.width)) / 2.0;
72 for_each_span(at, columns, |column, low, high| {
73 let top = plot.clamp_y(low - half);
74 let bottom = plot.clamp_y(high + half);
75 fill_span(canvas, column as f32, top, bottom, style.color);
77 });
78 canvas.restore(state);
79}
80
81fn fill_span(canvas: &mut Canvas<'_>, x: f32, top: f64, bottom: f64, color: Color) {
84 let pixel = |canvas: &mut Canvas<'_>, y: f64, coverage: f64| {
85 let alpha = color.a * coverage as f32;
87 canvas.fill_rect(Rect::new(x, y as f32, 1.0, 1.0), color.with_alpha(alpha));
88 };
89 let (inner_top, inner_bottom) = (top.ceil(), bottom.floor());
90 if inner_top > inner_bottom {
91 pixel(canvas, top.floor(), bottom - top);
93 return;
94 }
95 if inner_top > top {
96 pixel(canvas, top.floor(), inner_top - top);
97 }
98 if inner_bottom > inner_top {
99 let rect = Rect::from_edges(x, inner_top as f32, x + 1.0, inner_bottom as f32);
101 canvas.fill_rect(rect, color);
102 }
103 if bottom > inner_bottom {
104 pixel(canvas, inner_bottom, bottom - inner_bottom);
105 }
106}
107
108#[derive(Debug, Clone, Copy)]
110struct Span {
111 column: i64,
112 low: f64,
113 high: f64,
114}
115
116impl Span {
117 fn new(column: i64, a: f64, b: f64) -> Self {
118 Self {
119 column,
120 low: a.min(b),
121 high: a.max(b),
122 }
123 }
124
125 fn reach(&mut self, y: f64) {
126 self.low = self.low.min(y);
127 self.high = self.high.max(y);
128 }
129}
130
131pub(crate) fn for_each_span(
136 points: impl Iterator<Item = (f64, f64)>,
137 columns: Range<i64>,
138 mut emit: impl FnMut(i64, f64, f64),
139) {
140 let mut flush = |span: Option<Span>| {
141 if let Some(span) = span.filter(|span| columns.contains(&span.column)) {
142 emit(span.column, span.low, span.high);
143 }
144 };
145 let mut open: Option<Span> = None;
146 let mut last: Option<(f64, f64)> = None;
147 for (x, y) in points {
148 if !(x.is_finite() && y.is_finite()) {
149 last = None;
152 continue;
153 }
154 let column = x.floor() as i64;
156 match (last, open.as_mut()) {
157 (_, Some(span)) if column == span.column => span.reach(y),
158 (Some((x0, y0)), Some(span)) if column > span.column => {
159 let slope = (y - y0) / (x - x0);
160 let y_at = |at: f64| y0 + (at - x0) * slope;
161 let edge = |column: i64| y_at(column as f64);
163 span.reach(edge(span.column + 1));
164 let first = span.column + 1;
165 flush(open.take());
166 for between in first.max(columns.start)..column.min(columns.end) {
167 flush(Some(Span::new(between, edge(between), edge(between + 1))));
168 }
169 open = Some(Span::new(column, edge(column), y));
170 }
171 _ => {
172 flush(open.take());
175 open = Some(Span::new(column, y, y));
176 }
177 }
178 last = Some((x, y));
179 }
180 flush(open);
181}
182
183#[cfg(test)]
184mod tests {
185 use super::for_each_span;
186
187 fn spans(points: &[(f64, f64)], columns: std::ops::Range<i64>) -> Vec<(i64, f64, f64)> {
188 let mut out = Vec::new();
189 for_each_span(points.iter().copied(), columns, |column, low, high| {
190 out.push((column, low, high));
191 });
192 out
193 }
194
195 #[test]
196 fn a_flat_line_is_one_span_per_column_it_crosses() {
197 let got = spans(&[(0.5, 5.0), (9.5, 5.0)], 0..100);
198 let want: Vec<_> = (0..10).map(|column| (column, 5.0, 5.0)).collect();
199 assert_eq!(got, want);
200 }
201
202 #[test]
203 fn a_slope_covers_its_rise_in_each_column_and_neighbours_meet() {
204 let got = spans(&[(0.0, 0.0), (4.0, 8.0)], 0..100);
205 assert_eq!(
206 got,
207 [
208 (0, 0.0, 2.0),
209 (1, 2.0, 4.0),
210 (2, 4.0, 6.0),
211 (3, 6.0, 8.0),
212 (4, 8.0, 8.0)
213 ]
214 );
215 }
216
217 #[test]
218 fn points_within_one_column_become_its_lowest_and_highest() {
219 let got = spans(&[(3.1, 4.0), (3.4, 1.0), (3.6, 7.0), (3.9, 2.0)], 0..100);
220 assert_eq!(got, [(3, 1.0, 7.0)]);
221 }
222
223 #[test]
224 fn a_gap_breaks_the_line() {
225 let got = spans(
226 &[(0.0, 0.0), (1.5, f64::NAN), (3.0, 6.0), (4.0, 6.0)],
227 0..100,
228 );
229 assert_eq!(got, [(0, 0.0, 0.0), (3, 6.0, 6.0), (4, 6.0, 6.0)]);
230 let infinite = spans(&[(0.0, 0.0), (1.0, f64::INFINITY), (2.0, 0.0)], 0..100);
231 assert_eq!(infinite, [(0, 0.0, 0.0), (2, 0.0, 0.0)]);
232 }
233
234 #[test]
235 fn only_the_columns_asked_for_are_given() {
236 let got = spans(&[(-1_000.0, 0.0), (1_000.0, 2_000.0)], 0..3);
237 assert_eq!(
238 got,
239 [
240 (0, 1_000.0, 1_001.0),
241 (1, 1_001.0, 1_002.0),
242 (2, 1_002.0, 1_003.0)
243 ]
244 );
245 let far = spans(&[(-1e30, 1.0), (1e30, 1.0)], 0..2);
246 assert_eq!(far, [(0, 1.0, 1.0), (1, 1.0, 1.0)]);
247 }
248
249 #[test]
250 fn a_point_left_of_the_last_starts_the_line_afresh() {
251 let got = spans(&[(5.0, 0.0), (2.0, 1.0), (3.0, 3.0)], 0..100);
252 assert_eq!(got, [(5, 0.0, 0.0), (2, 1.0, 3.0), (3, 3.0, 3.0)]);
253 }
254
255 #[test]
256 fn gaps_within_a_column_leave_it_one_span() {
257 let points = [
258 (0.1, 1.0),
259 (0.2, f64::NAN),
260 (0.3, 5.0),
261 (0.4, f64::NAN),
262 (1.5, 2.0),
263 ];
264 assert_eq!(spans(&points, 0..9), [(0, 1.0, 5.0), (1, 2.0, 2.0)]);
265 }
266
267 #[test]
268 fn a_lone_point_is_a_span_of_no_height() {
269 assert_eq!(spans(&[(7.2, 3.0)], 0..100), [(7, 3.0, 3.0)]);
270 assert_eq!(spans(&[], 0..100), []);
271 }
272}