1use crate::aes::Aesthetic;
6use crate::coord::Coord;
7use crate::data::{DataFrame, Value};
8use crate::position::identity::PositionIdentity;
9use crate::position::Position;
10use crate::render::backend::{
11 DrawBackend, FontFace, LineStyle, Linetype, PointStyle, RectStyle, TextAnchor, TextStyle,
12};
13use crate::render::RenderError;
14use crate::scale::ScaleSet;
15use crate::stat::tree::{StatTree, TreeOrientation};
16use crate::stat::Stat;
17use crate::theme::Theme;
18
19use super::treemap::contrast_text;
20use super::{Geom, GeomParams};
21
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
24pub enum TreeLink {
25 #[default]
27 Elbow,
28 Step,
30 Diagonal,
32 Straight,
34}
35
36#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
38pub enum TreeNode {
39 #[default]
41 Point,
42 Box,
44 None,
46}
47
48pub struct GeomTree {
50 pub orientation: TreeOrientation,
51 pub align_leaves: bool,
52 pub height: Option<String>,
54 pub link: TreeLink,
55 pub node: TreeNode,
56 pub fill: (u8, u8, u8),
58 pub link_color: (u8, u8, u8),
59 pub link_width: f64,
60 pub labels: bool,
61 pub leaf_labels_only: bool,
63 pub label_size: f64,
64 pub label_color: (u8, u8, u8),
65}
66
67impl Default for GeomTree {
68 fn default() -> Self {
69 GeomTree {
70 orientation: TreeOrientation::TopDown,
71 align_leaves: false,
72 height: None,
73 link: TreeLink::Diagonal,
74 node: TreeNode::Point,
75 fill: (31, 119, 180),
76 link_color: (150, 150, 150),
77 link_width: 1.2,
78 labels: true,
79 leaf_labels_only: false,
80 label_size: 9.0,
81 label_color: (40, 40, 40),
82 }
83 }
84}
85
86impl GeomTree {
87 pub fn org_chart() -> Self {
89 GeomTree {
90 link: TreeLink::Elbow,
91 node: TreeNode::Box,
92 ..Default::default()
93 }
94 }
95
96 pub fn dendrogram() -> Self {
99 GeomTree {
100 height: Some("height".into()),
101 link: TreeLink::Step,
102 node: TreeNode::None,
103 leaf_labels_only: true,
104 link_color: (60, 60, 60),
105 ..Default::default()
106 }
107 }
108
109 pub fn oriented(mut self, orientation: TreeOrientation) -> Self {
111 self.orientation = orientation;
112 self
113 }
114
115 fn link_path(&self, p: (f64, f64), c: (f64, f64)) -> Vec<(f64, f64)> {
117 let horizontal = self.orientation == TreeOrientation::LeftRight;
118 match (self.link, self.orientation) {
119 (TreeLink::Straight, _)
120 | (TreeLink::Elbow | TreeLink::Step, TreeOrientation::Radial) => {
121 vec![p, c]
122 }
123 (TreeLink::Elbow, _) if horizontal => {
124 let m = (p.0 + c.0) / 2.0;
125 vec![p, (m, p.1), (m, c.1), c]
126 }
127 (TreeLink::Elbow, _) => {
128 let m = (p.1 + c.1) / 2.0;
129 vec![p, (p.0, m), (c.0, m), c]
130 }
131 (TreeLink::Step, _) if horizontal => vec![p, (p.0, c.1), c],
132 (TreeLink::Step, _) => vec![p, (c.0, p.1), c],
133 (TreeLink::Diagonal, _) => {
134 let (c1, c2) = if horizontal {
137 let m = (p.0 + c.0) / 2.0;
138 ((m, p.1), (m, c.1))
139 } else if self.orientation == TreeOrientation::TopDown {
140 let m = (p.1 + c.1) / 2.0;
141 ((p.0, m), (c.0, m))
142 } else {
143 let mid = ((p.0 + c.0) / 2.0, (p.1 + c.1) / 2.0);
144 (mid, mid)
145 };
146 (0..=16)
147 .map(|k| {
148 let t = k as f64 / 16.0;
149 let u = 1.0 - t;
150 let b = |a: f64, b1: f64, b2: f64, d: f64| {
151 u * u * u * a
152 + 3.0 * u * u * t * b1
153 + 3.0 * u * t * t * b2
154 + t * t * t * d
155 };
156 (b(p.0, c1.0, c2.0, c.0), b(p.1, c1.1, c2.1, c.1))
157 })
158 .collect()
159 }
160 }
161 }
162}
163
164impl Geom for GeomTree {
165 fn draw(
166 &self,
167 data: &DataFrame,
168 coord: &dyn Coord,
169 scales: &ScaleSet,
170 theme: &Theme,
171 backend: &mut dyn DrawBackend,
172 ) -> Result<(), RenderError> {
173 let col = |n: &str| {
174 data.column(n)
175 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
176 };
177 let (part, xc, yc, xe, ye) = (
178 col("part")?,
179 col("x")?,
180 col("y")?,
181 col("xend")?,
182 col("yend")?,
183 );
184 let (node, from, to, leaf) = (col("node")?, col("from")?, col("to")?, col("leaf")?);
185 let fill_col = data.column("fill");
186 let area = backend.plot_area();
187 let px = |(x, y): (f64, f64)| {
188 let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
189 let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
190 coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
191 };
192 let is = |i: usize, p: &str| part[i].key_str() == p;
193 let family = super::resolve_font_family("", theme);
194 let text = |_s: &str, pos: (f64, f64), anchor, color, face| {
195 (
196 pos,
197 TextStyle {
198 color,
199 size: self.label_size,
200 anchor,
201 angle: 0.0,
202 family: family.clone(),
203 face,
204 bg_colour: None,
205 bg_r: 0.0,
206 },
207 )
208 };
209
210 for i in (0..data.nrows()).filter(|&i| is(i, "link")) {
211 let (Some(x0), Some(y0), Some(x1), Some(y1)) = (
212 xc[i].as_f64(),
213 yc[i].as_f64(),
214 xe[i].as_f64(),
215 ye[i].as_f64(),
216 ) else {
217 continue;
218 };
219 let pts: Vec<(f64, f64)> = self
220 .link_path((x0, y0), (x1, y1))
221 .into_iter()
222 .map(px)
223 .collect();
224 let (f, t) = (super::tip_value(&from[i]), super::tip_value(&to[i]));
225 super::set_mark(backend, Some(format!("{f} → {t}")), Some(t), Some(f), None);
226 backend.draw_line(
227 &pts,
228 &LineStyle {
229 color: self.link_color,
230 alpha: 1.0,
231 width: self.link_width,
232 linetype: Linetype::Solid,
233 ..Default::default()
234 },
235 )?;
236 }
237
238 for i in (0..data.nrows()).filter(|&i| is(i, "node")) {
239 let (Some(x), Some(y)) = (xc[i].as_f64(), yc[i].as_f64()) else {
240 continue;
241 };
242 let c = px((x, y));
243 let name = super::tip_value(&node[i]);
244 let is_leaf = matches!(leaf[i], Value::Bool(true));
245 let fill = fill_col
246 .and_then(|f| scales.map_color(&Aesthetic::Fill, &f[i]))
247 .unwrap_or(self.fill);
248 super::set_mark(
249 backend,
250 Some(name.clone()),
251 Some(name.clone()),
252 Some(name.clone()),
253 None,
254 );
255 match self.node {
256 TreeNode::Box => {
257 let w = name.chars().count() as f64 * self.label_size * 0.62 + 14.0;
258 let h = self.label_size + 12.0;
259 backend.draw_rect(
260 (c.0 - w / 2.0, c.1 - h / 2.0),
261 (c.0 + w / 2.0, c.1 + h / 2.0),
262 &RectStyle {
263 fill: Some(fill),
264 stroke: None,
265 stroke_width: 0.0,
266 alpha: 1.0,
267 clip: false,
268 corner_radius: 4.0,
269 },
270 )?;
271 if self.labels {
272 super::clear_mark(backend);
273 let (pos, style) = text(
274 &name,
275 c,
276 TextAnchor::Middle,
277 contrast_text(fill),
278 FontFace::Bold,
279 );
280 backend.draw_text(&name, pos, &style)?;
281 }
282 continue;
283 }
284 TreeNode::Point => {
285 backend.draw_circle(
286 c,
287 4.0,
288 &PointStyle {
289 color: fill,
290 alpha: 1.0,
291 filled: true,
292 ..Default::default()
293 },
294 )?;
295 }
296 TreeNode::None => {}
297 }
298 if !self.labels || (self.leaf_labels_only && !is_leaf) {
299 continue;
300 }
301 super::clear_mark(backend);
302 let (pos, anchor) = match self.orientation {
303 TreeOrientation::TopDown if is_leaf => ((c.0, c.1 + 12.0), TextAnchor::Middle),
304 TreeOrientation::TopDown => ((c.0 + 7.0, c.1 - 8.0), TextAnchor::Start),
305 TreeOrientation::LeftRight => ((c.0 + 7.0, c.1), TextAnchor::Start),
306 TreeOrientation::Radial => {
307 let o = px((0.0, 0.0));
309 let (dx, dy) = (c.0 - o.0, c.1 - o.1);
310 let len = dx.hypot(dy);
311 if len < 1e-6 {
312 ((c.0, c.1 - 10.0), TextAnchor::Middle)
313 } else {
314 let at = (c.0 + dx / len * 8.0, c.1 + dy / len * 8.0);
315 let anchor = if dx.abs() < 0.2 * len {
316 TextAnchor::Middle
317 } else if dx > 0.0 {
318 TextAnchor::Start
319 } else {
320 TextAnchor::End
321 };
322 (at, anchor)
323 }
324 }
325 };
326 let (pos, style) = text(&name, pos, anchor, self.label_color, FontFace::Plain);
327 backend.draw_text(&name, pos, &style)?;
328 }
329 super::clear_mark(backend);
330 Ok(())
331 }
332
333 fn required_aes(&self) -> Vec<Aesthetic> {
334 vec![Aesthetic::From, Aesthetic::To]
335 }
336
337 fn default_stat(&self) -> Box<dyn Stat> {
338 Box::new(StatTree {
339 orientation: self.orientation,
340 align_leaves: self.align_leaves,
341 height: self.height.clone(),
342 })
343 }
344
345 fn default_position(&self) -> Box<dyn Position> {
346 Box::new(PositionIdentity)
347 }
348
349 fn default_params(&self) -> GeomParams {
350 GeomParams::default()
351 }
352
353 fn name(&self) -> &str {
354 "tree"
355 }
356
357 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
358 if let Some(ink) = el.ink {
359 self.label_color = ink;
360 }
361 }
362
363 fn set_series_color(&mut self, color: (u8, u8, u8)) {
364 self.fill = color;
365 }
366}