Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::charts_shape_line_path::{point_runs, points_from_value, svg_pair}; ← linePath, another function of this group · built into the same file, even by a slim install
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])))
}