use super::funejson::Value; use super::charts_shape_line_path::svg_pair; use super::math_sin_cos::sin_cos; 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)) }