Functional Weave
Code in Rust

charts.shape@1.0.1

impl/rust/line_path.rs

1,866 bytes · the Rust implementation · view raw

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::text_format_decimal::format_decimal;  ← from text.format-decimal ^1.0.0 · built alongside by fune

/// 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<Vec<(f64, f64)>> {
    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<Point> {
    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])))
}