use super::funejson::Value; use super::text_format_decimal::format_decimal; /// A polyline as SVG path data, in d3.line's format ("M10,20L30,40"). /// A null y ends the current subpath; a run of one point is "Mx,yZ", as d3 /// closes it so a round line cap still shows a dot. pub fn line_path(points: &[Point]) -> String { let mut out = String::new(); for run in point_runs(points) { for (i, (x, y)) in run.iter().enumerate() { out.push_str(if i == 0 { "M" } else { "L" }); out.push_str(&svg_pair(*x, *y)); } if run.len() == 1 { out.push('Z'); } } out } // Shared with the other path functions of this group. /// "x,y" with each number rounded to 2 places, trailing zeros dropped. pub fn svg_pair(x: f64, y: f64) -> String { format!("{},{}", format_decimal(x, 2, true, ""), format_decimal(y, 2, true, "")) } /// The unbroken runs of a series: a point with a null y separates them. pub fn point_runs(points: &[Point]) -> Vec> { let mut runs = Vec::new(); let mut current: Vec<(f64, f64)> = Vec::new(); for p in points { match p.y { None => { if !current.is_empty() { runs.push(std::mem::take(&mut current)); } } Some(y) => current.push((p.x, y)), } } if !current.is_empty() { runs.push(current); } runs } /// Points from vector JSON, for the adapters. pub fn points_from_value(v: &Value) -> Vec { v.as_arr() .iter() .map(|p| Point { x: p.get("x").as_f64(), y: if p.get("y").is_null() { None } else { Some(p.get("y").as_f64()) }, }) .collect() } pub fn fune_vector(args: &[Value]) -> Value { Value::str(&line_path(&points_from_value(&args[0]))) }