Functional Weave
Code in Rust

charts.shape@1.0.0

impl/rust/arc_path.rs

2,703 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::charts_shape_line_path::svg_pair;  ← linePath, another function of this group · built into the same file, even by a slim install
use super::math_sin_cos::sin_cos;  ← from math.sin-cos ^1.0.0 · built alongside by fune

const PI: f64 = 3.141592653589793;
const TAU: f64 = 6.283185307179586;

fn arc_point(arc: &Arc, r: f64, angle: f64) -> String {
    let sc = sin_cos(angle);
    svg_pair(arc.cx + r * sc.sin, arc.cy - r * sc.cos)
}

fn arc_to(arc: &Arc, r: f64, large: i64, sweep: i64, angle: f64) -> String {
    format!("A{},0,{},{},{}", svg_pair(r, r), large, sweep, arc_point(arc, r, angle))
}

/// An annular sector as SVG path data. Angles are radians from 12 o'clock,
/// clockwise, as in d3.arc. See the README for the exact path format.
///
/// # Panics
/// Panics unless 0 <= innerRadius <= outerRadius.
pub fn arc_path(arc: &Arc) -> String {
    let (ri, ro, a0, a1) = (arc.inner_radius, arc.outer_radius, arc.start_angle, arc.end_angle);
    if !(ri >= 0.0) || !(ro >= ri) {
        panic!("arc radii must satisfy 0 <= innerRadius <= outerRadius, received {} and {}", ri, ro);
    }
    let span = a1 - a0;
    if span == 0.0 || ro == 0.0 {
        return String::new();
    }
    let sweep = if span > 0.0 { 1 } else { 0 };
    let back = 1 - sweep;
    let dir = if span > 0.0 { 1.0 } else { -1.0 };
    if span.abs() >= TAU {
        // One SVG arc cannot start and end at the same point, so a full turn is
        // two half turns; the hole is drawn the other way so it stays empty.
        let half = a0 + dir * PI;
        let mut out = format!(
            "M{}{}{}Z",
            arc_point(arc, ro, a0),
            arc_to(arc, ro, 1, sweep, half),
            arc_to(arc, ro, 1, sweep, a0)
        );
        if ri > 0.0 {
            let other = a0 - dir * PI;
            out.push_str(&format!(
                "M{}{}{}Z",
                arc_point(arc, ri, a0),
                arc_to(arc, ri, 1, back, other),
                arc_to(arc, ri, 1, back, a0)
            ));
        }
        return out;
    }
    let large = if span.abs() > PI { 1 } else { 0 };
    let mut out = format!("M{}{}", arc_point(arc, ro, a0), arc_to(arc, ro, large, sweep, a1));
    if ri > 0.0 {
        out.push_str(&format!("L{}{}", arc_point(arc, ri, a1), arc_to(arc, ri, large, back, a0)));
    } else {
        out.push_str(&format!("L{}", svg_pair(arc.cx, arc.cy)));
    }
    out.push('Z');
    out
}

pub fn fune_vector(args: &[Value]) -> Value {
    let a = &args[0];
    let arc = Arc {
        cx: a.get("cx").as_f64(),
        cy: a.get("cy").as_f64(),
        inner_radius: a.get("innerRadius").as_f64(),
        outer_radius: a.get("outerRadius").as_f64(),
        start_angle: a.get("startAngle").as_f64(),
        end_angle: a.get("endAngle").as_f64(),
    };
    Value::str(&arc_path(&arc))
}