charts_rs/charts/
path.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use super::util::*;
14use std::fmt;
15
16#[derive(Clone, Copy, PartialEq, Debug, Default)]
17pub struct QuadraticBezier {
18    pub x1: f32,
19    pub y1: f32,
20    pub x2: f32,
21    pub y2: f32,
22}
23impl fmt::Display for QuadraticBezier {
24    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
25        let m = format!("{} {}", format_float(self.x1), format_float(self.y1));
26        let x = (self.x1 + self.x1) / 2.0;
27        let y = self.y1 + (self.y2 - self.y1) / 2.0;
28        let q = format!("{} {}", format_float(x), format_float(y));
29        let end = format!("{} {}", format_float(self.x2), format_float(self.y2));
30        write!(f, "M{m} Q{q}, {end}")
31    }
32}
33
34#[derive(Clone, PartialEq, Debug, Default)]
35struct ControlPoint {
36    left: Option<Point>,
37    right: Option<Point>,
38}
39
40// http://scaledinnovation.com/analytics/splines/aboutSplines.html
41fn get_control_points(
42    p: &Point,
43    left: Option<&Point>,
44    right: Option<&Point>,
45    t: f32,
46) -> ControlPoint {
47    let x0 = left.unwrap_or(p).x;
48    let y0 = left.unwrap_or(p).y;
49    let x1 = p.x;
50    let y1 = p.y;
51    let x2 = right.unwrap_or(p).x;
52    let y2 = right.unwrap_or(p).y;
53
54    let d01 = ((x1 - x0).powf(2.0) + (y1 - y0).powf(2.0)).sqrt();
55    let d12 = ((x2 - x1).powf(2.0) + (y2 - y1).powf(2.0)).sqrt();
56    // scaling factor for triangle Ta
57    let fa = t * d01 / (d01 + d12);
58    // ditto for Tb, simplifies to fb=t-fa
59    let fb = t * d12 / (d01 + d12);
60    // x2-x0 is the width of triangle T
61    let p1x = x1 - fa * (x2 - x0);
62    // y2-y0 is the height of T
63    let p1y = y1 - fa * (y2 - y0);
64    let p2x = x1 + fb * (x2 - x0);
65    let p2y = y1 + fb * (y2 - y0);
66
67    let mut cpl = None;
68    let mut cpr = None;
69    if left.is_some() {
70        cpl = Some((p1x, p1y).into());
71    }
72    if right.is_some() {
73        cpr = Some((p2x, p2y).into());
74    }
75    ControlPoint {
76        left: cpl,
77        right: cpr,
78    }
79}
80
81#[derive(Clone, PartialEq, Debug, Default)]
82pub struct SmoothCurve {
83    pub points: Vec<Point>,
84    pub close: bool,
85}
86impl fmt::Display for SmoothCurve {
87    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
88        let tension = 0.25;
89
90        let close = self.close;
91        let count = self.points.len();
92        let mut control_points = vec![];
93        for (index, point) in self.points.iter().enumerate() {
94            let mut left = None;
95            let mut right = None;
96            if index >= 1 {
97                left = Some(&self.points[index - 1]);
98            } else if close {
99                // 对于第一个点的上一节点则为last
100                left = self.points.last();
101            }
102            if index + 1 < count {
103                right = Some(&self.points[index + 1]);
104            } else if close {
105                // 最后一个点的下一节点则为first
106                right = self.points.first()
107            }
108            control_points.push(get_control_points(point, left, right, tension));
109        }
110
111        let mut arr = vec![];
112        for (index, point) in self.points.iter().enumerate() {
113            if index == 0 {
114                arr.push(format!(
115                    "M{},{}",
116                    format_float(point.x),
117                    format_float(point.y)
118                ));
119            }
120            let cp1 = control_points[index].right;
121            let mut cp2 = None;
122            if let Some(value) = control_points.get(index + 1) {
123                cp2 = value.left;
124            } else if close {
125                // 最的一个点
126                cp2 = control_points[0].left;
127            }
128            let mut next_point = self.points.get(index + 1);
129            // 如果是close的才需要处理最后一个点
130            // 如果非最后一个点
131            if close && index == count - 1 {
132                next_point = self.points.first();
133            }
134            if let Some(next_point_value) = next_point {
135                let next_point = format!(
136                    "{} {}",
137                    format_float(next_point_value.x),
138                    format_float(next_point_value.y)
139                );
140                if let Some(cp1_value) = cp1 {
141                    if let Some(cp2_value) = cp2 {
142                        let c1 = format!(
143                            "{} {}",
144                            format_float(cp1_value.x),
145                            format_float(cp1_value.y)
146                        );
147                        let c2 = format!(
148                            "{} {}",
149                            format_float(cp2_value.x),
150                            format_float(cp2_value.y)
151                        );
152                        arr.push(format!("C{}, {}, {}", c1, c2, next_point));
153                        continue;
154                    }
155                }
156                let p = cp1.unwrap_or(cp2.unwrap_or_default());
157
158                let q = format!("{} {}", format_float(p.x), format_float(p.y));
159                arr.push(format!("Q{}, {}", q, next_point));
160            }
161        }
162        write!(f, "{}", arr.join(" "))
163    }
164}
165
166#[cfg(test)]
167mod tests {
168    use super::{QuadraticBezier, SmoothCurve};
169    use pretty_assertions::assert_eq;
170    #[test]
171    fn quadratic_bezier() {
172        let str = QuadraticBezier {
173            x1: 10.0,
174            y1: 30.0,
175            x2: 30.0,
176            y2: 10.0,
177        }
178        .to_string();
179        assert_eq!("M10 30 Q10 20, 30 10", str);
180    }
181
182    #[test]
183    fn smooth_curve() {
184        let str = SmoothCurve {
185            points: vec![
186                (10.0, 10.0).into(),
187                (20.0, 50.0).into(),
188                (30.0, 80.0).into(),
189                (40.0, 30.0).into(),
190                (50.0, 10.0).into(),
191            ],
192            close: false,
193        }
194        .to_string();
195        assert_eq!("M10,10 C12.5 20, 17.2 40.1, 20 50 C22.2 57.6, 28.1 81.9, 30 80 C33.1 76.9, 36.5 42.2, 40 30 C41.5 24.7, 47.5 15, 50 10", str);
196
197        let str = SmoothCurve {
198            points: vec![
199                (10.0, 10.0).into(),
200                (20.0, 50.0).into(),
201                (30.0, 80.0).into(),
202                (40.0, 30.0).into(),
203                (50.0, 10.0).into(),
204            ],
205            close: true,
206        }
207        .to_string();
208        assert_eq!("M10,10 C6.2 15.1, 17.2 40.1, 20 50 C22.2 57.6, 28.1 81.9, 30 80 C33.1 76.9, 36.5 42.2, 40 30 C41.5 24.7, 52.7 11.8, 50 10 C45.2 6.8, 13.7 5.1, 10 10", str);
209    }
210}