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::{
12 DrawBackend, FontFace, LineStyle, Linetype, PointStyle, RectStyle, TextAnchor, TextStyle,
13};
14use crate::render::RenderError;
15use crate::scale::ScaleSet;
16use crate::stat::identity::StatIdentity;
17use crate::stat::Stat;
18use crate::theme::Theme;
19
20use super::{Geom, GeomParams};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
24pub enum FlagShape {
25 #[default]
27 Flag,
28 CirclePin,
30}
31
32pub struct GeomFlags {
34 pub shape: FlagShape,
35 pub series: Option<Vec<(f64, f64)>>,
37 pub color: (u8, u8, u8),
38 pub fill: (u8, u8, u8),
39 pub label_size: f64,
40 pub stem: f64,
42}
43
44impl Default for GeomFlags {
45 fn default() -> Self {
46 GeomFlags {
47 shape: FlagShape::Flag,
48 series: None,
49 color: (31, 119, 180),
50 fill: (255, 255, 255),
51 label_size: 9.0,
52 stem: 18.0,
53 }
54 }
55}
56
57impl GeomFlags {
58 pub fn on_series(mut self, x: &[Value], y: &[Value]) -> Self {
61 let mut pts: Vec<(f64, f64)> = x
62 .iter()
63 .zip(y)
64 .filter_map(|(a, b)| Some((a.as_f64()?, b.as_f64()?)))
65 .collect();
66 pts.sort_by(|a, b| a.0.total_cmp(&b.0));
67 self.series = Some(pts);
68 self
69 }
70}
71
72pub(crate) fn interpolate_at(pts: &[(f64, f64)], x: f64) -> Option<f64> {
74 let (first, last) = (pts.first()?, pts.last()?);
75 if x <= first.0 {
76 return Some(first.1);
77 }
78 if x >= last.0 {
79 return Some(last.1);
80 }
81 let k = pts.partition_point(|p| p.0 <= x);
82 let (a, b) = (pts[k - 1], pts[k]);
83 Some(if b.0 > a.0 {
84 a.1 + (b.1 - a.1) * (x - a.0) / (b.0 - a.0)
85 } else {
86 a.1
87 })
88}
89
90impl Geom for GeomFlags {
91 fn draw(
92 &self,
93 data: &DataFrame,
94 coord: &dyn Coord,
95 scales: &ScaleSet,
96 theme: &Theme,
97 backend: &mut dyn DrawBackend,
98 ) -> Result<(), RenderError> {
99 let x_col = data
100 .column("x")
101 .ok_or(RenderError::MissingAesthetic("x".into()))?;
102 let (label_col, color_col) = (data.column("label"), data.column("color"));
103 let area = backend.plot_area();
104 let mx = |v: &Value| scales.get(&Aesthetic::X).map(|s| s.map(v)).unwrap_or(0.5);
105 let my = |v: f64| {
106 scales
107 .get(&Aesthetic::Y)
108 .map(|s| s.map(&Value::Float(v)))
109 .unwrap_or(0.0)
110 };
111 let family = super::resolve_font_family("", theme);
112 let box_h = self.label_size + 7.0;
113 let mut order: Vec<usize> = (0..data.nrows()).collect();
114 order.sort_by(|&a, &b| mx(&x_col[a]).total_cmp(&mx(&x_col[b])));
115 let mut stacks: Vec<(f64, usize)> = Vec::new();
117 for i in order {
118 let nx = mx(&x_col[i]);
119 let ny = match (&self.series, x_col[i].as_f64()) {
120 (Some(pts), Some(x)) => interpolate_at(pts, x).map(my).unwrap_or(0.0),
121 _ => 0.0,
122 };
123 let anchor = coord.transform((nx, ny), &area);
124 if !(anchor.0.is_finite() && anchor.1.is_finite()) {
125 continue;
126 }
127 let level = match stacks.iter_mut().find(|s| (s.0 - anchor.0).abs() < 3.0) {
128 Some(s) => {
129 s.1 += 1;
130 s.1 - 1
131 }
132 None => {
133 stacks.push((anchor.0, 1));
134 0
135 }
136 };
137 let color = color_col
138 .and_then(|c| scales.map_color(&Aesthetic::Color, &c[i]))
139 .unwrap_or(self.color);
140 let label = label_col
141 .map(|c| super::tip_value(&c[i]))
142 .unwrap_or_default();
143 let top = (
144 anchor.0,
145 anchor.1 - self.stem - level as f64 * (box_h + 3.0),
146 );
147 super::set_mark(
148 backend,
149 Some(format!("{}: {label}", super::tip_value(&x_col[i]))),
150 Some(super::tip_value(&x_col[i])),
151 super::series_key(data, i),
152 None,
153 );
154 super::set_row_extras(backend, data, i);
155 backend.draw_line(
156 &[anchor, top],
157 &LineStyle {
158 color,
159 alpha: 1.0,
160 width: 1.0,
161 linetype: Linetype::Solid,
162 ..Default::default()
163 },
164 )?;
165 let text = match self.shape {
166 FlagShape::Flag => label.clone(),
167 FlagShape::CirclePin => label.chars().next().map(String::from).unwrap_or_default(),
168 };
169 let center = match self.shape {
170 FlagShape::Flag => {
171 let w = crate::render::text::text_width(&text, self.label_size) + 8.0;
172 backend.draw_rect(
173 (top.0, top.1 - box_h),
174 (top.0 + w, top.1),
175 &RectStyle {
176 fill: Some(self.fill),
177 stroke: Some(color),
178 stroke_width: 1.0,
179 alpha: 1.0,
180 clip: false,
181 corner_radius: 0.0,
182 },
183 )?;
184 (top.0 + w / 2.0, top.1 - box_h / 2.0)
185 }
186 FlagShape::CirclePin => {
187 let c = (top.0, top.1 - box_h / 2.0);
188 backend.draw_circle(
189 c,
190 box_h / 2.0,
191 &PointStyle {
192 color,
193 alpha: 1.0,
194 filled: true,
195 ..Default::default()
196 },
197 )?;
198 c
199 }
200 };
201 if !text.is_empty() {
202 super::clear_mark(backend);
203 backend.draw_text(
204 &text,
205 center,
206 &TextStyle {
207 color: if self.shape == FlagShape::CirclePin {
208 super::treemap::contrast_text(color)
209 } else {
210 color
211 },
212 size: self.label_size,
213 anchor: TextAnchor::Middle,
214 angle: 0.0,
215 family: family.clone(),
216 face: FontFace::Bold,
217 bg_colour: None,
218 bg_r: 0.0,
219 },
220 )?;
221 }
222 }
223 super::clear_mark(backend);
224 super::clear_row_extras(backend);
225 Ok(())
226 }
227
228 fn required_aes(&self) -> Vec<Aesthetic> {
229 vec![Aesthetic::X]
230 }
231 fn default_stat(&self) -> Box<dyn Stat> {
232 Box::new(StatIdentity)
233 }
234 fn default_position(&self) -> Box<dyn Position> {
235 Box::new(PositionIdentity)
236 }
237 fn default_params(&self) -> GeomParams {
238 GeomParams::default()
239 }
240 fn name(&self) -> &str {
241 "flags"
242 }
243 fn consumes_tooltips(&self) -> bool {
244 true
245 }
246 fn set_series_color(&mut self, color: (u8, u8, u8)) {
247 self.color = color;
248 }
249 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
250 if let (true, Some(paper)) = (self.fill == (255, 255, 255), el.paper) {
251 self.fill = paper;
252 }
253 }
254}
255
256#[cfg(test)]
257mod tests {
258 use super::*;
259
260 #[test]
261 fn interpolation_and_clamping() {
262 let p = [(0.0, 0.0), (10.0, 100.0)];
263 assert_eq!(interpolate_at(&p, 5.0), Some(50.0));
264 assert_eq!(interpolate_at(&p, -3.0), Some(0.0));
265 assert_eq!(interpolate_at(&p, 30.0), Some(100.0));
266 assert_eq!(interpolate_at(&[], 1.0), None);
267 }
268}