chartml_core/shapes/
area.rs1use super::line::{CurveType, LineGenerator};
4
5pub struct AreaGenerator {
7 curve: CurveType,
8}
9
10impl AreaGenerator {
11 pub fn new() -> Self {
13 Self {
14 curve: CurveType::Linear,
15 }
16 }
17
18 pub fn curve(mut self, curve: CurveType) -> Self {
20 self.curve = curve;
21 self
22 }
23
24 pub fn generate_line(&self, points: &[(f64, f64, f64)]) -> String {
29 if points.is_empty() {
30 return String::new();
31 }
32 let top_points: Vec<(f64, f64)> = points.iter().map(|&(x, _y0, y1)| (x, y1)).collect();
33 let line_gen = LineGenerator::new().curve(self.curve);
34 line_gen.generate(&top_points)
35 }
36
37 pub fn generate(&self, points: &[(f64, f64, f64)]) -> String {
46 if points.is_empty() {
47 return String::new();
48 }
49
50 let top_points: Vec<(f64, f64)> = points.iter().map(|&(x, _y0, y1)| (x, y1)).collect();
52 let bottom_points: Vec<(f64, f64)> = points.iter().rev().map(|&(x, y0, _y1)| (x, y0)).collect();
54
55 let line_gen = LineGenerator::new().curve(self.curve);
56 let top_path = line_gen.generate(&top_points);
57 let bottom_path = line_gen.generate(&bottom_points);
58
59 let bottom_continuation = if bottom_path.len() > 1 {
61 format!("L{}", &bottom_path[1..])
62 } else {
63 String::new()
64 };
65
66 format!("{}{}Z", top_path, bottom_continuation)
67 }
68}
69
70impl Default for AreaGenerator {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 #![allow(clippy::unwrap_used)]
79 use super::*;
80
81 #[test]
82 fn area_basic() {
83 let gen = AreaGenerator::new();
84 let path = gen.generate(&[
85 (0.0, 100.0, 10.0),
86 (50.0, 100.0, 20.0),
87 (100.0, 100.0, 5.0),
88 ]);
89 assert!(path.starts_with("M"), "Path should start with M, got: {}", path);
90 assert!(path.contains("L"), "Path should contain L commands, got: {}", path);
91 assert!(path.ends_with("Z"), "Path should end with Z, got: {}", path);
92 }
93
94 #[test]
95 fn area_empty() {
96 let gen = AreaGenerator::new();
97 let path = gen.generate(&[]);
98 assert_eq!(path, "");
99 }
100}