use super::funejson::Value; use super::charts_shape_line_path::{point_runs, points_from_value, svg_pair}; fn sign(x: f64) -> f64 { if x < 0.0 { -1.0 } else { 1.0 } } // Fritsch–Carlson: the tangent at the middle point, limited so the curve // cannot overshoot either neighbouring value. d3's slope3. fn slope3(x0: f64, y0: f64, x1: f64, y1: f64, x2: f64, y2: f64) -> f64 { let h0 = x1 - x0; let h1 = x2 - x1; let s0 = (y1 - y0) / h0; let s1 = (y2 - y1) / h1; let p = (s0 * h1 + s1 * h0) / (h0 + h1); let t = (sign(s0) + sign(s1)) * s0.abs().min(s1.abs()).min(0.5 * p.abs()); if t == 0.0 { 0.0 } else { t } } fn slope2(x0: f64, y0: f64, x1: f64, y1: f64, t: f64) -> f64 { (3.0 * (y1 - y0) / (x1 - x0) - t) / 2.0 } fn bezier(x0: f64, y0: f64, x1: f64, y1: f64, t0: f64, t1: f64) -> String { let dx = (x1 - x0) / 3.0; format!("C{},{},{}", svg_pair(x0 + dx, y0 + dx * t0), svg_pair(x1 - dx, y1 - dx * t1), svg_pair(x1, y1)) } /// A smooth line that stays monotone wherever the data is: d3.curveMonotoneX, /// Hermite segments with Fritsch–Carlson tangents, as cubic Béziers. /// /// # Panics /// Panics if x does not strictly increase within an unbroken run. pub fn monotone_curve(points: &[Point]) -> String { let mut out = String::new(); for run in point_runs(points) { for i in 1..run.len() { if !(run[i].0 > run[i - 1].0) { panic!("monotone curve needs x strictly increasing, but {} follows {}", run[i].0, run[i - 1].0); } } out.push('M'); out.push_str(&svg_pair(run[0].0, run[0].1)); let n = run.len(); if n == 1 { out.push('Z'); continue; } if n == 2 { out.push('L'); out.push_str(&svg_pair(run[1].0, run[1].1)); continue; } let mut t0 = 0.0; for i in 2..n { let (ax, ay) = run[i - 2]; let (bx, by) = run[i - 1]; let (cx, cy) = run[i]; let t1 = slope3(ax, ay, bx, by, cx, cy); let start = if i == 2 { slope2(ax, ay, bx, by, t1) } else { t0 }; out.push_str(&bezier(ax, ay, bx, by, start, t1)); t0 = t1; } let (ax, ay) = run[n - 2]; let (bx, by) = run[n - 1]; out.push_str(&bezier(ax, ay, bx, by, t0, slope2(ax, ay, bx, by, t0))); } out } pub fn fune_vector(args: &[Value]) -> Value { Value::str(&monotone_curve(&points_from_value(&args[0]))) }