1use crate::aes::Aesthetic;
7use crate::coord::Coord;
8use crate::data::{DataFrame, Value};
9use crate::position::identity::PositionIdentity;
10use crate::position::Position;
11use crate::render::backend::{DrawBackend, LineStyle, Linetype, PointStyle};
12use crate::render::RenderError;
13use crate::scale::ScaleSet;
14use crate::stat::identity::StatIdentity;
15use crate::stat::Stat;
16use crate::theme::Theme;
17
18use super::{Geom, GeomParams};
19
20pub struct GeomLollipop {
22 pub color: (u8, u8, u8),
23 pub point_size: f64,
25 pub stem_width: f64,
26 pub baseline: f64,
28 pub alpha: f64,
29}
30
31impl Default for GeomLollipop {
32 fn default() -> Self {
33 GeomLollipop {
34 color: (31, 119, 180),
35 point_size: 5.0,
36 stem_width: 1.5,
37 baseline: 0.0,
38 alpha: 1.0,
39 }
40 }
41}
42
43pub struct GeomDumbbell {
46 pub low_color: (u8, u8, u8),
48 pub high_color: (u8, u8, u8),
50 pub line_color: (u8, u8, u8),
52 pub point_size: f64,
53 pub line_width: f64,
54 pub alpha: f64,
55}
56
57impl Default for GeomDumbbell {
58 fn default() -> Self {
59 GeomDumbbell {
60 low_color: (160, 160, 160),
61 high_color: (31, 119, 180),
62 line_color: (190, 190, 190),
63 point_size: 5.0,
64 line_width: 3.0,
65 alpha: 1.0,
66 }
67 }
68}
69
70fn dot(
71 backend: &mut dyn DrawBackend,
72 at: (f64, f64),
73 r: f64,
74 color: (u8, u8, u8),
75 alpha: f64,
76) -> Result<(), RenderError> {
77 backend.draw_circle(
78 at,
79 r,
80 &PointStyle {
81 color,
82 alpha,
83 filled: true,
84 ..Default::default()
85 },
86 )
87}
88
89fn stem(
90 backend: &mut dyn DrawBackend,
91 a: (f64, f64),
92 b: (f64, f64),
93 color: (u8, u8, u8),
94 width: f64,
95 alpha: f64,
96) -> Result<(), RenderError> {
97 backend.draw_line(
98 &[a, b],
99 &LineStyle {
100 color,
101 alpha,
102 width,
103 linetype: Linetype::Solid,
104 ..Default::default()
105 },
106 )
107}
108
109impl Geom for GeomLollipop {
110 fn draw(
111 &self,
112 data: &DataFrame,
113 coord: &dyn Coord,
114 scales: &ScaleSet,
115 _theme: &Theme,
116 backend: &mut dyn DrawBackend,
117 ) -> Result<(), RenderError> {
118 let x_col = data
119 .column("x")
120 .ok_or(RenderError::MissingAesthetic("x".into()))?;
121 let y_col = data
122 .column("y")
123 .ok_or(RenderError::MissingAesthetic("y".into()))?;
124 let color_col = data.column("color");
125 let area = backend.plot_area();
126 let xm = super::support::XMapper::new(data, scales);
127 let my = |v: &Value| scales.get(&Aesthetic::Y).map(|s| s.map(v)).unwrap_or(0.0);
128 let base = my(&Value::Float(self.baseline));
129 for i in 0..data.nrows() {
130 let (nx, ny) = (xm.map(&x_col[i], i), my(&y_col[i]));
131 if !(nx.is_finite() && ny.is_finite()) {
132 continue;
133 }
134 let color = color_col
135 .and_then(|c| scales.map_color(&Aesthetic::Color, &c[i]))
136 .unwrap_or(self.color);
137 let x_txt = super::tip_value(&x_col[i]);
138 super::set_mark(
139 backend,
140 Some(format!("{x_txt}: {}", super::tip_value(&y_col[i]))),
141 Some(x_txt),
142 super::series_key(data, i),
143 super::raw_value(&y_col[i]),
144 );
145 super::set_mark_formatted(
146 backend,
147 scales,
148 Some(&x_col[i]),
149 &Aesthetic::Y,
150 Some(&y_col[i]),
151 );
152 super::set_row_extras(backend, data, i);
153 let (top, bottom) = (
154 coord.transform((nx, ny), &area),
155 coord.transform((nx, base), &area),
156 );
157 stem(backend, bottom, top, color, self.stem_width, self.alpha)?;
158 dot(backend, top, self.point_size, color, self.alpha)?;
159 }
160 super::clear_mark(backend);
161 super::clear_row_extras(backend);
162 Ok(())
163 }
164
165 fn required_aes(&self) -> Vec<Aesthetic> {
166 vec![Aesthetic::X, Aesthetic::Y]
167 }
168 fn default_stat(&self) -> Box<dyn Stat> {
169 Box::new(StatIdentity)
170 }
171 fn default_position(&self) -> Box<dyn Position> {
172 Box::new(PositionIdentity)
173 }
174 fn default_params(&self) -> GeomParams {
175 GeomParams::default()
176 }
177 fn name(&self) -> &str {
178 "lollipop"
179 }
180 fn include_zero_baseline(&self) -> bool {
181 self.baseline == 0.0
182 }
183 fn consumes_tooltips(&self) -> bool {
184 true
185 }
186 fn set_series_color(&mut self, color: (u8, u8, u8)) {
187 self.color = color;
188 }
189}
190
191impl Geom for GeomDumbbell {
192 fn draw(
193 &self,
194 data: &DataFrame,
195 coord: &dyn Coord,
196 scales: &ScaleSet,
197 _theme: &Theme,
198 backend: &mut dyn DrawBackend,
199 ) -> Result<(), RenderError> {
200 let col = |n: &str| {
201 data.column(n)
202 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
203 };
204 let (x_col, lo_col, hi_col) = (col("x")?, col("ymin")?, col("ymax")?);
205 let area = backend.plot_area();
206 let xm = super::support::XMapper::new(data, scales);
207 let my = |v: &Value| scales.get(&Aesthetic::Y).map(|s| s.map(v)).unwrap_or(0.0);
208 for i in 0..data.nrows() {
209 let (nx, lo, hi) = (xm.map(&x_col[i], i), my(&lo_col[i]), my(&hi_col[i]));
210 if ![nx, lo, hi].iter().all(|v| v.is_finite()) {
211 continue;
212 }
213 let x_txt = super::tip_value(&x_col[i]);
214 let (l, h) = (super::tip_value(&lo_col[i]), super::tip_value(&hi_col[i]));
215 super::set_mark(
216 backend,
217 Some(format!("{x_txt}: {l} → {h}")),
218 Some(x_txt),
219 super::series_key(data, i),
220 super::raw_value(&hi_col[i]),
221 );
222 super::set_row_extras(backend, data, i);
223 let (a, b) = (
224 coord.transform((nx, lo), &area),
225 coord.transform((nx, hi), &area),
226 );
227 stem(backend, a, b, self.line_color, self.line_width, self.alpha)?;
228 dot(backend, a, self.point_size, self.low_color, self.alpha)?;
229 dot(backend, b, self.point_size, self.high_color, self.alpha)?;
230 }
231 super::clear_mark(backend);
232 super::clear_row_extras(backend);
233 Ok(())
234 }
235
236 fn required_aes(&self) -> Vec<Aesthetic> {
237 vec![Aesthetic::X, Aesthetic::Ymin, Aesthetic::Ymax]
238 }
239 fn default_stat(&self) -> Box<dyn Stat> {
240 Box::new(StatIdentity)
241 }
242 fn default_position(&self) -> Box<dyn Position> {
243 Box::new(PositionIdentity)
244 }
245 fn default_params(&self) -> GeomParams {
246 GeomParams::default()
247 }
248 fn name(&self) -> &str {
249 "dumbbell"
250 }
251 fn consumes_tooltips(&self) -> bool {
252 true
253 }
254 fn set_series_color(&mut self, color: (u8, u8, u8)) {
255 self.high_color = color;
256 }
257}