1use crate::aes::Aesthetic;
6use crate::data::{DataFrame, Value};
7use crate::position::DODGE_OFFSET_COL;
8use crate::scale::{Scale, ScaleSet};
9
10pub(crate) fn row_groups(data: &DataFrame) -> Vec<Vec<usize>> {
14 let cols: Vec<&[Value]> = ["group", "color", "fill", "linetype"]
15 .iter()
16 .filter_map(|c| data.column(c))
17 .collect();
18 let n = data.nrows();
19 if cols.is_empty() {
20 return if n == 0 {
21 vec![]
22 } else {
23 vec![(0..n).collect()]
24 };
25 }
26 let mut groups: indexmap::IndexMap<Vec<crate::data::GroupKey<'_>>, Vec<usize>> =
27 indexmap::IndexMap::new();
28 for i in 0..n {
29 let key = cols.iter().map(|c| c[i].group_key()).collect();
30 groups.entry(key).or_default().push(i);
31 }
32 groups.into_values().collect()
33}
34
35pub(crate) struct XMapper<'a> {
39 scale: Option<&'a dyn Scale>,
40 offsets: Option<&'a [Value]>,
41 band: f64,
42}
43
44impl<'a> XMapper<'a> {
45 pub(crate) fn new(data: &'a DataFrame, scales: &'a ScaleSet) -> Self {
46 let scale = scales.get(&Aesthetic::X);
47 let offsets = data.column(DODGE_OFFSET_COL);
48 let band = match (scale, offsets) {
49 (Some(s), Some(_)) if s.is_discrete() => 1.0 / s.breaks().len().max(1) as f64,
50 _ => 0.0,
51 };
52 XMapper {
53 scale,
54 offsets,
55 band,
56 }
57 }
58
59 pub(crate) fn map(&self, value: &Value, i: usize) -> f64 {
61 let base = self.scale.map(|s| s.map(value)).unwrap_or(0.0);
62 let off = self
63 .offsets
64 .and_then(|o| o.get(i))
65 .and_then(|v| v.as_f64())
66 .filter(|v| v.is_finite())
67 .unwrap_or(0.0);
68 base + off * self.band
69 }
70}
71
72pub(crate) fn mark_interval(
76 backend: &mut dyn crate::render::backend::DrawBackend,
77 scales: &crate::scale::ScaleSet,
78 data: &DataFrame,
79 i: usize,
80 x: &Value,
81 lo: &Value,
82 hi: &Value,
83) {
84 let series = super::series_key(data, i);
85 let y = data.column("y").and_then(|c| c.get(i));
86 let range = format!("[{}, {}]", super::tip_value(lo), super::tip_value(hi));
87 let body = match y {
88 Some(y) => format!("{}: {} {range}", super::tip_value(x), super::tip_value(y)),
89 None => format!("{}: {range}", super::tip_value(x)),
90 };
91 let tip = Some(match &series {
92 Some(s) => format!("{s}: {body}"),
93 None => body,
94 });
95 let value = match y {
96 Some(y) => super::raw_value(y),
97 None => super::raw_value(lo)
98 .zip(super::raw_value(hi))
99 .map(|(a, b)| format!("{a} {b}")),
100 };
101 super::set_mark(backend, tip, Some(super::tip_value(x)), series, value);
102 super::set_mark_formatted(backend, scales, Some(x), &crate::aes::Aesthetic::Y, y);
106}
107
108pub(crate) fn map_position(scale: Option<&dyn Scale>, value: &Value) -> Option<f64> {
112 let s = scale?;
113 if s.is_discrete() {
114 if let Some(f) = value.as_f64() {
115 let n = s.breaks().len().max(1) as f64;
116 return Some((f - 0.5) / n);
117 }
118 }
119 Some(s.map(value))
120}
121
122pub(crate) fn clip_segment_unit(a: (f64, f64), b: (f64, f64)) -> Option<((f64, f64), (f64, f64))> {
125 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
126 let mut t0: f64 = 0.0;
127 let mut t1: f64 = 1.0;
128 for (p, q) in [(-dx, a.0), (dx, 1.0 - a.0), (-dy, a.1), (dy, 1.0 - a.1)] {
129 if p.abs() < 1e-15 {
130 if q < 0.0 {
131 return None;
132 }
133 } else {
134 let r = q / p;
135 if p < 0.0 {
136 t0 = t0.max(r);
137 } else {
138 t1 = t1.min(r);
139 }
140 }
141 }
142 if t0 > t1 || !(t0.is_finite() && t1.is_finite()) {
143 return None;
144 }
145 Some((
146 (a.0 + t0 * dx, a.1 + t0 * dy),
147 (a.0 + t1 * dx, a.1 + t1 * dy),
148 ))
149}
150
151pub(crate) fn clip_polyline_unit(pts: &[(f64, f64)]) -> Vec<Vec<(f64, f64)>> {
155 let mut runs: Vec<Vec<(f64, f64)>> = Vec::new();
156 let mut cur: Vec<(f64, f64)> = Vec::new();
157 for w in pts.windows(2) {
158 let (a, b) = (w[0], w[1]);
159 let finite = a.0.is_finite() && a.1.is_finite() && b.0.is_finite() && b.1.is_finite();
160 match clip_segment_unit(a, b).filter(|_| finite) {
161 Some((p, q)) => {
162 let joins = cur
163 .last()
164 .is_some_and(|l| (l.0 - p.0).abs() < 1e-12 && (l.1 - p.1).abs() < 1e-12);
165 if !joins {
166 if cur.len() >= 2 {
167 runs.push(std::mem::take(&mut cur));
168 }
169 cur.clear();
170 cur.push(p);
171 }
172 cur.push(q);
173 }
174 None => {
175 if cur.len() >= 2 {
176 runs.push(std::mem::take(&mut cur));
177 }
178 cur.clear();
179 }
180 }
181 }
182 if cur.len() >= 2 {
183 runs.push(cur);
184 }
185 runs
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn clip_inside_segment_is_unchanged() {
194 let (p, q) = clip_segment_unit((0.1, 0.2), (0.8, 0.9)).unwrap();
195 for (got, want) in [(p.0, 0.1), (p.1, 0.2), (q.0, 0.8), (q.1, 0.9)] {
196 assert!((got - want).abs() < 1e-12);
197 }
198 }
199
200 #[test]
201 fn clip_crossing_segment() {
202 let (p, q) = clip_segment_unit((-1.0, 0.5), (2.0, 0.5)).unwrap();
203 assert!((p.0 - 0.0).abs() < 1e-12 && (q.0 - 1.0).abs() < 1e-12);
204 assert!(clip_segment_unit((-1.0, 2.0), (2.0, 2.0)).is_none());
205 }
206
207 #[test]
208 fn clip_polyline_splits_runs() {
209 let pts = [(0.1, 0.5), (0.3, 0.5), (0.5, 1.5), (0.7, 0.5), (0.9, 0.5)];
211 let runs = clip_polyline_unit(&pts);
212 assert_eq!(runs.len(), 2);
213 for r in &runs {
214 assert!(r
215 .iter()
216 .all(|(x, y)| (0.0..=1.0).contains(x) && (0.0..=1.0).contains(y)));
217 }
218 }
219
220 #[test]
221 fn clip_polyline_breaks_on_nan() {
222 let pts = [
223 (0.1, 0.1),
224 (0.2, 0.2),
225 (f64::NAN, 0.3),
226 (0.4, 0.4),
227 (0.5, 0.5),
228 ];
229 let runs = clip_polyline_unit(&pts);
230 assert_eq!(runs.len(), 2);
231 }
232
233 #[test]
234 fn row_groups_by_colour() {
235 let mut df = DataFrame::new();
236 df.add_column(
237 "color".into(),
238 vec!["a", "b", "a"]
239 .into_iter()
240 .map(|s| Value::Str(s.into()))
241 .collect(),
242 );
243 assert_eq!(row_groups(&df), vec![vec![0, 2], vec![1]]);
244 let mut plain = DataFrame::new();
245 plain.add_column("x".into(), vec![Value::Float(1.0); 3]);
246 assert_eq!(row_groups(&plain), vec![vec![0, 1, 2]]);
247 }
248}
249
250#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
253pub enum Interpolate {
254 #[default]
256 Linear,
257 MonotoneCubic,
261 CatmullRom,
263}
264
265const CURVE_STEPS: usize = 16;
267
268pub(crate) fn interpolate(
273 points: &[(f64, f64)],
274 method: Interpolate,
275) -> (Vec<(f64, f64)>, Vec<usize>) {
276 if method == Interpolate::Linear || points.len() < 3 {
277 return (points.to_vec(), (0..points.len()).collect());
278 }
279 let mut out = Vec::with_capacity(points.len() * CURVE_STEPS);
280 let mut origin = Vec::with_capacity(points.len() * CURVE_STEPS);
281 let finite = |p: &(f64, f64)| p.0.is_finite() && p.1.is_finite();
282 let mut start = 0;
283 while start < points.len() {
284 if !finite(&points[start]) {
285 out.push(points[start]);
286 origin.push(start);
287 start += 1;
288 continue;
289 }
290 let mut end = start;
291 while end < points.len() && finite(&points[end]) {
292 end += 1;
293 }
294 let run = &points[start..end];
295 let curve = match method {
296 Interpolate::MonotoneCubic => monotone_run(run),
297 _ => catmull_rom_run(run),
298 };
299 for (p, k) in curve {
300 out.push(p);
301 origin.push(start + k);
302 }
303 start = end;
304 }
305 (out, origin)
306}
307
308fn slope3(p0: (f64, f64), p1: (f64, f64), p2: (f64, f64)) -> f64 {
310 let (h0, h1) = (p1.0 - p0.0, p2.0 - p1.0);
311 if h0 <= 0.0 || h1 <= 0.0 {
312 return 0.0;
313 }
314 let (s0, s1) = ((p1.1 - p0.1) / h0, (p2.1 - p1.1) / h1);
315 let p = (s0 * h1 + s1 * h0) / (h0 + h1);
316 (s0.signum() + s1.signum()) * s0.abs().min(s1.abs()).min(0.5 * p.abs())
317}
318
319fn monotone_run(run: &[(f64, f64)]) -> Vec<((f64, f64), usize)> {
320 let n = run.len();
321 if n < 3 {
322 return run.iter().enumerate().map(|(k, &p)| (p, k)).collect();
323 }
324 let mut m = vec![0.0; n];
325 for k in 1..n - 1 {
326 m[k] = slope3(run[k - 1], run[k], run[k + 1]);
327 }
328 let end_slope = |a: (f64, f64), b: (f64, f64), t: f64| {
330 let h = b.0 - a.0;
331 if h > 0.0 {
332 (3.0 * (b.1 - a.1) / h - t) / 2.0
333 } else {
334 t
335 }
336 };
337 m[0] = end_slope(run[0], run[1], m[1]);
338 m[n - 1] = end_slope(run[n - 2], run[n - 1], m[n - 2]);
339 let mut out = vec![(run[0], 0)];
340 for k in 0..n - 1 {
341 let ((x0, y0), (x1, y1)) = (run[k], run[k + 1]);
342 let h = x1 - x0;
343 if h <= 0.0 {
344 out.push((run[k + 1], k));
345 continue;
346 }
347 for s in 1..=CURVE_STEPS {
348 let t = s as f64 / CURVE_STEPS as f64;
349 let (t2, t3) = (t * t, t * t * t);
350 let y = (2.0 * t3 - 3.0 * t2 + 1.0) * y0
351 + (t3 - 2.0 * t2 + t) * h * m[k]
352 + (-2.0 * t3 + 3.0 * t2) * y1
353 + (t3 - t2) * h * m[k + 1];
354 out.push(((x0 + h * t, y), k));
355 }
356 }
357 out
358}
359
360fn catmull_rom_run(run: &[(f64, f64)]) -> Vec<((f64, f64), usize)> {
361 let n = run.len();
362 if n < 3 {
363 return run.iter().enumerate().map(|(k, &p)| (p, k)).collect();
364 }
365 let at = |i: isize| run[i.clamp(0, n as isize - 1) as usize];
366 let mut out = vec![(run[0], 0)];
367 for k in 0..n - 1 {
368 let (p0, p1, p2, p3) = (
369 at(k as isize - 1),
370 at(k as isize),
371 at(k as isize + 1),
372 at(k as isize + 2),
373 );
374 for s in 1..=CURVE_STEPS {
375 let t = s as f64 / CURVE_STEPS as f64;
376 let (t2, t3) = (t * t, t * t * t);
377 let c = |a: f64, b: f64, c: f64, d: f64| {
378 0.5 * (2.0 * b
379 + (-a + c) * t
380 + (2.0 * a - 5.0 * b + 4.0 * c - d) * t2
381 + (-a + 3.0 * b - 3.0 * c + d) * t3)
382 };
383 out.push(((c(p0.0, p1.0, p2.0, p3.0), c(p0.1, p1.1, p2.1, p3.1)), k));
384 }
385 }
386 out
387}
388
389#[cfg(test)]
390mod interpolate_tests {
391 use super::*;
392
393 #[test]
394 fn monotone_passes_through_points_without_overshoot() {
395 let pts = [(0.0, 0.0), (1.0, 1.0), (2.0, 1.0), (3.0, 5.0), (4.0, 0.0)];
396 let (out, origin) = interpolate(&pts, Interpolate::MonotoneCubic);
397 assert_eq!(out.len(), 1 + 4 * CURVE_STEPS);
398 assert_eq!(origin.len(), out.len());
399 for p in pts {
400 assert!(out
401 .iter()
402 .any(|q| (q.0 - p.0).abs() < 1e-12 && (q.1 - p.1).abs() < 1e-12));
403 }
404 for (q, &k) in out.iter().zip(&origin) {
406 let (a, b) = (pts[k].1, pts[k + 1].1);
407 assert!(
408 q.1 >= a.min(b) - 1e-9 && q.1 <= a.max(b) + 1e-9,
409 "{q:?} on {k}"
410 );
411 }
412 }
413
414 #[test]
415 fn catmull_rom_passes_through_points_and_breaks_on_nan() {
416 let pts = [
417 (0.0, 0.0),
418 (1.0, 2.0),
419 (2.0, 0.0),
420 (f64::NAN, f64::NAN),
421 (3.0, 1.0),
422 (4.0, 3.0),
423 (5.0, 2.0),
424 ];
425 let (out, origin) = interpolate(&pts, Interpolate::CatmullRom);
426 assert_eq!(out.iter().filter(|p| p.0.is_nan()).count(), 1);
427 assert!(out
428 .iter()
429 .any(|q| (q.0 - 1.0).abs() < 1e-12 && (q.1 - 2.0).abs() < 1e-12));
430 assert_eq!(origin[out.iter().position(|p| p.0.is_nan()).unwrap()], 3);
431 let (lin, _) = interpolate(&pts, Interpolate::Linear);
432 assert_eq!(lin.len(), pts.len());
433 }
434}