1use crate::aes::Aesthetic;
17use crate::coord::Coord;
18use crate::data::{DataFrame, Value};
19use crate::position::identity::PositionIdentity;
20use crate::position::Position;
21use crate::render::backend::{DrawBackend, LineStyle, Linetype};
22use crate::render::RenderError;
23use crate::scale::ScaleSet;
24use crate::stat::identity::StatIdentity;
25use crate::stat::Stat;
26use crate::theme::Theme;
27
28use super::support::{clip_polyline_unit, map_position};
29use super::{Geom, GeomParams};
30
31fn row_style(
33 data: &DataFrame,
34 i: usize,
35 scales: &ScaleSet,
36 color: (u8, u8, u8),
37 width: f64,
38 linetype: Linetype,
39 alpha: f64,
40) -> LineStyle {
41 let color = data
42 .column("color")
43 .and_then(|c| c.get(i))
44 .and_then(|v| scales.map_color(&Aesthetic::Color, v))
45 .unwrap_or(color);
46 let linetype = data
47 .column("linetype")
48 .and_then(|c| c.get(i))
49 .and_then(|v| scales.map_linetype(v))
50 .unwrap_or(linetype);
51 let alpha = data
52 .column("alpha")
53 .and_then(|c| c.get(i))
54 .and_then(|v| scales.map_alpha(v).or_else(|| v.as_f64()))
55 .filter(|a| a.is_finite())
56 .unwrap_or(alpha);
57 LineStyle {
58 color,
59 alpha,
60 width,
61 linetype,
62 }
63}
64
65fn mark_line(
69 backend: &mut dyn DrawBackend,
70 data: &DataFrame,
71 i: Option<usize>,
72 what: &str,
73 value: &Value,
74 axis: Option<String>,
75) {
76 let series = i.and_then(|i| super::series_key(data, i));
77 let raw = super::raw_value(value);
78 let tip = raw.as_ref().map(|v| match &series {
79 Some(s) => format!("{s}: {what} = {}", super::tip_value(value)),
80 None => format!("{what} = {v}"),
81 });
82 super::set_mark(backend, tip, axis, series, raw);
83}
84
85fn line_rows(data: &DataFrame, col: &str, constant: f64) -> Vec<(Option<usize>, Value)> {
88 match data.column(col) {
89 Some(c) => c
90 .iter()
91 .enumerate()
92 .filter(|(_, v)| v.as_f64().is_some_and(f64::is_finite))
93 .map(|(i, v)| (Some(i), v.clone()))
94 .collect(),
95 None if constant.is_finite() => vec![(None, Value::Float(constant))],
96 None => vec![],
97 }
98}
99
100pub struct GeomHline {
102 pub yintercept: f64,
103 pub color: (u8, u8, u8),
104 pub width: f64,
105 pub linetype: Linetype,
106 pub alpha: f64,
107}
108
109impl GeomHline {
110 pub fn new(yintercept: f64) -> Self {
111 GeomHline {
112 yintercept,
113 color: (0, 0, 0),
114 width: 1.0,
115 linetype: Linetype::Dashed,
116 alpha: 1.0,
117 }
118 }
119
120 pub fn mapped() -> Self {
123 Self::new(f64::NAN)
124 }
125}
126
127impl Geom for GeomHline {
128 fn draw(
129 &self,
130 data: &DataFrame,
131 coord: &dyn Coord,
132 scales: &ScaleSet,
133 _theme: &Theme,
134 backend: &mut dyn DrawBackend,
135 ) -> Result<(), RenderError> {
136 let plot_area = backend.plot_area();
137 let y_scale = scales.get(&Aesthetic::Y);
138 for (row, value) in line_rows(data, "yintercept", self.yintercept) {
139 let ny = map_position(y_scale, &value).unwrap_or(0.5);
140 if !ny.is_finite() {
141 continue;
142 }
143 let style = match row {
144 Some(i) => row_style(
145 data,
146 i,
147 scales,
148 self.color,
149 self.width,
150 self.linetype,
151 self.alpha,
152 ),
153 None => LineStyle {
154 color: self.color,
155 alpha: self.alpha,
156 width: self.width,
157 linetype: self.linetype,
158 },
159 };
160 mark_line(backend, data, row, "y", &value, None);
161 let pts = sample_segment(coord, &plot_area, (0.0, ny), (1.0, ny));
162 backend.draw_line(&pts, &style)?;
163 }
164 super::clear_mark(backend);
165 Ok(())
166 }
167
168 fn required_aes(&self) -> Vec<Aesthetic> {
169 vec![]
170 }
171 fn default_stat(&self) -> Box<dyn Stat> {
172 Box::new(StatIdentity)
173 }
174 fn default_position(&self) -> Box<dyn Position> {
175 Box::new(PositionIdentity)
176 }
177 fn default_params(&self) -> GeomParams {
178 GeomParams::default()
179 }
180 fn name(&self) -> &str {
181 "hline"
182 }
183 fn inherit_aes(&self) -> bool {
184 false
185 }
186}
187
188pub struct GeomVline {
190 pub xintercept: f64,
191 pub color: (u8, u8, u8),
192 pub width: f64,
193 pub linetype: Linetype,
194 pub alpha: f64,
195}
196
197impl GeomVline {
198 pub fn new(xintercept: f64) -> Self {
199 GeomVline {
200 xintercept,
201 color: (0, 0, 0),
202 width: 1.0,
203 linetype: Linetype::Dashed,
204 alpha: 1.0,
205 }
206 }
207
208 pub fn mapped() -> Self {
210 Self::new(f64::NAN)
211 }
212}
213
214impl Geom for GeomVline {
215 fn draw(
216 &self,
217 data: &DataFrame,
218 coord: &dyn Coord,
219 scales: &ScaleSet,
220 _theme: &Theme,
221 backend: &mut dyn DrawBackend,
222 ) -> Result<(), RenderError> {
223 let plot_area = backend.plot_area();
224 let x_scale = scales.get(&Aesthetic::X);
225 for (row, value) in line_rows(data, "xintercept", self.xintercept) {
226 let nx = map_position(x_scale, &value).unwrap_or(0.5);
227 if !nx.is_finite() {
228 continue;
229 }
230 let style = match row {
231 Some(i) => row_style(
232 data,
233 i,
234 scales,
235 self.color,
236 self.width,
237 self.linetype,
238 self.alpha,
239 ),
240 None => LineStyle {
241 color: self.color,
242 alpha: self.alpha,
243 width: self.width,
244 linetype: self.linetype,
245 },
246 };
247 let axis = Some(super::tip_value(&value));
248 mark_line(backend, data, row, "x", &value, axis);
249 let pts = sample_segment(coord, &plot_area, (nx, 0.0), (nx, 1.0));
250 backend.draw_line(&pts, &style)?;
251 }
252 super::clear_mark(backend);
253 Ok(())
254 }
255
256 fn required_aes(&self) -> Vec<Aesthetic> {
257 vec![]
258 }
259 fn default_stat(&self) -> Box<dyn Stat> {
260 Box::new(StatIdentity)
261 }
262 fn default_position(&self) -> Box<dyn Position> {
263 Box::new(PositionIdentity)
264 }
265 fn default_params(&self) -> GeomParams {
266 GeomParams::default()
267 }
268 fn name(&self) -> &str {
269 "vline"
270 }
271 fn inherit_aes(&self) -> bool {
272 false
273 }
274}
275
276pub struct GeomAbline {
279 pub slope: f64,
280 pub intercept: f64,
281 pub color: (u8, u8, u8),
282 pub width: f64,
283 pub linetype: Linetype,
284 pub alpha: f64,
285}
286
287impl GeomAbline {
288 pub fn new(slope: f64, intercept: f64) -> Self {
289 GeomAbline {
290 slope,
291 intercept,
292 color: (0, 0, 0),
293 width: 1.0,
294 linetype: Linetype::Dashed,
295 alpha: 1.0,
296 }
297 }
298
299 pub fn mapped() -> Self {
302 Self::new(1.0, 0.0)
303 }
304}
305
306fn panel_x_range(scales: &ScaleSet) -> (f64, f64) {
308 match scales.get(&Aesthetic::X) {
309 Some(s) if s.is_discrete() => (0.5, s.breaks().len().max(1) as f64 + 0.5),
310 Some(s) => s.expanded_domain().unwrap_or((0.0, 1.0)),
311 None => (0.0, 1.0),
312 }
313}
314
315pub(crate) fn abline_runs(scales: &ScaleSet, slope: f64, intercept: f64) -> Vec<Vec<(f64, f64)>> {
318 if !(slope.is_finite() && intercept.is_finite()) {
319 return vec![];
320 }
321 let (x0, x1) = panel_x_range(scales);
322 let y_scale = scales.get(&Aesthetic::Y);
323 let ny = |x: f64| {
324 let y = intercept + slope * x;
325 match y_scale {
326 Some(_) => map_position(y_scale, &Value::Float(y)).unwrap_or(f64::NAN),
327 None => y,
328 }
329 };
330 clip_polyline_unit(&[(0.0, ny(x0)), (1.0, ny(x1))])
331}
332
333fn sample_segment(
337 coord: &dyn Coord,
338 plot_area: &crate::render::Rect,
339 a: (f64, f64),
340 b: (f64, f64),
341) -> Vec<(f64, f64)> {
342 let n = if coord.is_polar() || coord.is_radar() {
343 64
344 } else {
345 1
346 };
347 (0..=n)
348 .map(|k| {
349 let t = k as f64 / n as f64;
350 coord.transform((a.0 + t * (b.0 - a.0), a.1 + t * (b.1 - a.1)), plot_area)
351 })
352 .collect()
353}
354
355impl Geom for GeomAbline {
356 fn draw(
357 &self,
358 data: &DataFrame,
359 coord: &dyn Coord,
360 scales: &ScaleSet,
361 _theme: &Theme,
362 backend: &mut dyn DrawBackend,
363 ) -> Result<(), RenderError> {
364 let plot_area = backend.plot_area();
365 let slope_col = data.column("slope");
366 let icpt_col = data.column("intercept");
367 let rows: Vec<(Option<usize>, f64, f64)> = if slope_col.is_some() || icpt_col.is_some() {
368 (0..data.nrows())
369 .map(|i| {
370 let get = |c: Option<&[Value]>, d: f64| {
371 c.map_or(Some(d), |c| c.get(i).and_then(|v| v.as_f64()))
372 };
373 (i, get(slope_col, 1.0), get(icpt_col, 0.0))
374 })
375 .filter_map(|(i, s, a)| Some((Some(i), s?, a?)))
376 .collect()
377 } else {
378 vec![(None, self.slope, self.intercept)]
379 };
380 for (row, slope, intercept) in rows {
381 let style = match row {
382 Some(i) => row_style(
383 data,
384 i,
385 scales,
386 self.color,
387 self.width,
388 self.linetype,
389 self.alpha,
390 ),
391 None => LineStyle {
392 color: self.color,
393 alpha: self.alpha,
394 width: self.width,
395 linetype: self.linetype,
396 },
397 };
398 let series = row.and_then(|i| super::series_key(data, i));
399 let label = format!(
400 "y = {} + {}·x",
401 super::tip_value(&Value::Float(intercept)),
402 super::tip_value(&Value::Float(slope))
403 );
404 let tip = Some(match &series {
405 Some(s) => format!("{s}: {label}"),
406 None => label,
407 });
408 super::set_mark(
409 backend,
410 tip,
411 None,
412 series,
413 super::raw_value(&Value::Float(slope)),
414 );
415 for run in abline_runs(scales, slope, intercept) {
416 let mut pts = Vec::new();
417 for w in run.windows(2) {
418 let seg = sample_segment(coord, &plot_area, w[0], w[1]);
419 if !pts.is_empty() {
420 pts.pop();
421 }
422 pts.extend(seg);
423 }
424 backend.draw_line(&pts, &style)?;
425 }
426 }
427 super::clear_mark(backend);
428 Ok(())
429 }
430
431 fn required_aes(&self) -> Vec<Aesthetic> {
432 vec![]
433 }
434 fn default_stat(&self) -> Box<dyn Stat> {
435 Box::new(StatIdentity)
436 }
437 fn default_position(&self) -> Box<dyn Position> {
438 Box::new(PositionIdentity)
439 }
440 fn default_params(&self) -> GeomParams {
441 GeomParams::default()
442 }
443 fn name(&self) -> &str {
444 "abline"
445 }
446 fn inherit_aes(&self) -> bool {
447 false
448 }
449}