1use crate::aes::Aesthetic;
2use crate::coord::Coord;
3use crate::data::DataFrame;
4use crate::position::identity::PositionIdentity;
5use crate::position::Position;
6use crate::render::backend::{DrawBackend, LineStyle, Linetype};
7use crate::render::RenderError;
8use crate::scale::ScaleSet;
9use crate::stat::identity::StatIdentity;
10use crate::stat::Stat;
11use crate::theme::Theme;
12
13use super::{Geom, GeomParams};
14
15pub struct GeomCurve {
17 pub color: (u8, u8, u8),
18 pub width: f64,
19 pub alpha: f64,
20 pub curvature: f64,
21 pub ncp: usize,
22}
23
24impl Default for GeomCurve {
25 fn default() -> Self {
26 GeomCurve {
27 color: (0, 0, 0),
28 width: 1.0,
29 alpha: 1.0,
30 curvature: 0.5,
31 ncp: 5,
32 }
33 }
34}
35
36impl GeomCurve {
37 fn bezier_points(
39 p0: (f64, f64),
40 p2: (f64, f64),
41 curvature: f64,
42 ncp: usize,
43 ) -> Vec<(f64, f64)> {
44 let mx = (p0.0 + p2.0) / 2.0;
46 let my = (p0.1 + p2.1) / 2.0;
47 let dx = p2.0 - p0.0;
48 let dy = p2.1 - p0.1;
49 let p1x = mx - dy * curvature;
51 let p1y = my + dx * curvature;
52
53 let n = ncp + 2; (0..n)
55 .map(|i| {
56 let t = i as f64 / (n - 1) as f64;
57 let u = 1.0 - t;
58 let x = u * u * p0.0 + 2.0 * u * t * p1x + t * t * p2.0;
59 let y = u * u * p0.1 + 2.0 * u * t * p1y + t * t * p2.1;
60 (x, y)
61 })
62 .collect()
63 }
64}
65
66impl Geom for GeomCurve {
67 fn draw(
68 &self,
69 data: &DataFrame,
70 coord: &dyn Coord,
71 scales: &ScaleSet,
72 _theme: &Theme,
73 backend: &mut dyn DrawBackend,
74 ) -> Result<(), RenderError> {
75 let x_col = data
76 .column("x")
77 .ok_or(RenderError::MissingAesthetic("x".into()))?;
78 let y_col = data
79 .column("y")
80 .ok_or(RenderError::MissingAesthetic("y".into()))?;
81 let xend_col = data
82 .column("xend")
83 .ok_or(RenderError::MissingAesthetic("xend".into()))?;
84 let yend_col = data
85 .column("yend")
86 .ok_or(RenderError::MissingAesthetic("yend".into()))?;
87 let color_col = data.column("color");
88
89 let plot_area = backend.plot_area();
90 let x_scale = scales.get(&Aesthetic::X);
91 let y_scale = scales.get(&Aesthetic::Y);
92
93 for i in 0..data.nrows() {
94 let nx1 = x_scale.map(|s| s.map(&x_col[i])).unwrap_or(0.0);
95 let ny1 = y_scale.map(|s| s.map(&y_col[i])).unwrap_or(0.0);
96 let nx2 = x_scale.map(|s| s.map(&xend_col[i])).unwrap_or(0.0);
97 let ny2 = y_scale.map(|s| s.map(¥d_col[i])).unwrap_or(0.0);
98
99 let p0 = coord.transform((nx1, ny1), &plot_area);
100 let p2 = coord.transform((nx2, ny2), &plot_area);
101
102 let curve_points = Self::bezier_points(p0, p2, self.curvature, self.ncp);
103
104 let line_color = color_col
105 .and_then(|cc| scales.map_color(&Aesthetic::Color, &cc[i]))
106 .unwrap_or(self.color);
107
108 if curve_points.len() >= 2 {
109 backend.draw_line(
110 &curve_points,
111 &LineStyle {
112 color: line_color,
113 alpha: self.alpha,
114 width: self.width,
115 linetype: Linetype::Solid,
116 },
117 )?;
118 }
119 }
120
121 Ok(())
122 }
123
124 fn required_aes(&self) -> Vec<Aesthetic> {
125 vec![Aesthetic::X, Aesthetic::Y, Aesthetic::Xend, Aesthetic::Yend]
126 }
127
128 fn default_stat(&self) -> Box<dyn Stat> {
129 Box::new(StatIdentity)
130 }
131
132 fn default_position(&self) -> Box<dyn Position> {
133 Box::new(PositionIdentity)
134 }
135
136 fn default_params(&self) -> GeomParams {
137 GeomParams::default()
138 }
139
140 fn name(&self) -> &str {
141 "curve"
142 }
143
144 fn set_series_color(&mut self, color: (u8, u8, u8)) {
145 self.color = color;
146 }
147}