1use std::f64::consts::PI;
6
7use crate::aes::Aesthetic;
8use crate::coord::Coord;
9use crate::data::{DataFrame, Value};
10use crate::position::identity::PositionIdentity;
11use crate::position::Position;
12use crate::render::backend::{DrawBackend, FontFace, PointStyle, RectStyle, TextAnchor, TextStyle};
13use crate::render::RenderError;
14use crate::scale::ScaleSet;
15use crate::stat::arcdiagram::StatArcdiagram;
16use crate::stat::Stat;
17use crate::theme::Theme;
18
19use super::{Geom, GeomParams};
20
21pub struct GeomArcdiagram {
24 pub sort_by_value: bool,
26 pub directed_below: bool,
28 pub node_size: (f64, f64),
30 pub link_width: (f64, f64),
32 pub color: (u8, u8, u8),
34 pub link_alpha: f64,
35 pub labels: bool,
37 pub label_size: f64,
38 pub label_color: (u8, u8, u8),
39}
40
41impl Default for GeomArcdiagram {
42 fn default() -> Self {
43 GeomArcdiagram {
44 sort_by_value: false,
45 directed_below: false,
46 node_size: (3.0, 10.0),
47 link_width: (1.0, 8.0),
48 color: (97, 156, 255),
49 link_alpha: 0.5,
50 labels: true,
51 label_size: 10.0,
52 label_color: (40, 40, 40),
53 }
54 }
55}
56
57pub(crate) fn stroke_band(pts: &[(f64, f64)], width: f64) -> Vec<(f64, f64)> {
60 let n = pts.len();
61 let h = width / 2.0;
62 let normal = |i: usize| {
63 let (a, b) = (pts[i.saturating_sub(1)], pts[(i + 1).min(n - 1)]);
64 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
65 let len = dx.hypot(dy);
66 if len > 0.0 {
67 (-dy / len, dx / len)
68 } else {
69 (0.0, 0.0)
70 }
71 };
72 let mut band: Vec<(f64, f64)> = (0..n)
73 .map(|i| {
74 let (nx, ny) = normal(i);
75 (pts[i].0 + nx * h, pts[i].1 + ny * h)
76 })
77 .collect();
78 band.extend((0..n).rev().map(|i| {
79 let (nx, ny) = normal(i);
80 (pts[i].0 - nx * h, pts[i].1 - ny * h)
81 }));
82 band
83}
84
85impl Geom for GeomArcdiagram {
86 fn draw(
87 &self,
88 data: &DataFrame,
89 coord: &dyn Coord,
90 scales: &ScaleSet,
91 theme: &Theme,
92 backend: &mut dyn DrawBackend,
93 ) -> Result<(), RenderError> {
94 let col = |n: &str| {
95 data.column(n)
96 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
97 };
98 let (part, xc, xe) = (col("part")?, col("x")?, col("xend")?);
99 let (node, from, to, value) = (col("node")?, col("from")?, col("to")?, col("value")?);
100 let fill_col = data.column("fill");
101 let area = backend.plot_area();
102 let map = |(x, y): (f64, f64)| {
103 let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
104 let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
105 coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
106 };
107 let color_of = |i: usize| {
108 fill_col
109 .and_then(|c| scales.map_color(&Aesthetic::Fill, &c[i]))
110 .unwrap_or(self.color)
111 };
112 let is = |i: usize, p: &str| part[i].key_str() == p;
113 let max_of = |p: &str| {
114 (0..data.nrows())
115 .filter(|&i| is(i, p))
116 .filter_map(|i| value[i].as_f64())
117 .fold(0.0, f64::max)
118 };
119 let (max_w, max_v) = (max_of("link"), max_of("node"));
120 let lerp = |(lo, hi): (f64, f64), t: f64| lo + (hi - lo) * t.clamp(0.0, 1.0);
121
122 let mut links: Vec<usize> = (0..data.nrows()).filter(|&i| is(i, "link")).collect();
124 links.sort_by(|&a, &b| {
125 let w = |i: usize| value[i].as_f64().unwrap_or(0.0);
126 w(b).total_cmp(&w(a))
127 });
128 for i in links {
129 let (Some(x0), Some(x1), Some(w)) = (xc[i].as_f64(), xe[i].as_f64(), value[i].as_f64())
130 else {
131 continue;
132 };
133 let (c, r) = ((x0 + x1) / 2.0, (x1 - x0).abs() / 2.0);
134 let below = self.directed_below && x1 < x0;
135 let steps = ((r * 24.0).ceil() as usize).clamp(12, 180);
136 let pts: Vec<(f64, f64)> = (0..=steps)
137 .map(|k| {
138 let a = PI * k as f64 / steps as f64;
139 let y = r * a.sin();
140 map((c - r * a.cos(), if below { -y } else { y }))
141 })
142 .collect();
143 let (a, b) = (super::tip_value(&from[i]), super::tip_value(&to[i]));
144 super::set_mark(
145 backend,
146 Some(format!("{a} → {b}: {}", super::tip_value(&value[i]))),
147 Some(a.clone()),
148 Some(a),
149 super::raw_value(&value[i]),
150 );
151 let t = if max_w > 0.0 { w / max_w } else { 1.0 };
152 backend.draw_polygon(
155 &stroke_band(&pts, lerp(self.link_width, t)),
156 &RectStyle {
157 fill: Some(color_of(i)),
158 stroke: None,
159 stroke_width: 0.0,
160 alpha: self.link_alpha,
161 clip: true,
162 corner_radius: 0.0,
163 },
164 )?;
165 }
166
167 let mut labels = Vec::new();
168 for i in (0..data.nrows()).filter(|&i| is(i, "node")) {
169 let (Some(x), Some(v)) = (xc[i].as_f64(), value[i].as_f64()) else {
170 continue;
171 };
172 let p = map((x, 0.0));
173 let radius = lerp(
174 self.node_size,
175 if max_v > 0.0 { (v / max_v).sqrt() } else { 1.0 },
176 );
177 let name = super::tip_value(&node[i]);
178 super::set_mark(
179 backend,
180 Some(format!("{name}: {}", super::tip_value(&value[i]))),
181 Some(name.clone()),
182 Some(name.clone()),
183 super::raw_value(&value[i]),
184 );
185 backend.draw_circle(
186 p,
187 radius,
188 &PointStyle {
189 color: color_of(i),
190 alpha: 1.0,
191 filled: true,
192 ..Default::default()
193 },
194 )?;
195 if self.labels {
196 labels.push((name, (p.0, p.1 + self.node_size.1 + self.label_size * 0.8)));
197 }
198 }
199 super::clear_mark(backend);
200 let family = super::resolve_font_family("", theme);
201 for (text, pos) in labels {
202 backend.draw_text(
203 &text,
204 pos,
205 &TextStyle {
206 color: self.label_color,
207 size: self.label_size,
208 anchor: TextAnchor::Middle,
209 angle: 0.0,
210 family: family.clone(),
211 face: FontFace::Plain,
212 bg_colour: None,
213 bg_r: 0.0,
214 },
215 )?;
216 }
217 Ok(())
218 }
219
220 fn required_aes(&self) -> Vec<Aesthetic> {
221 vec![Aesthetic::From, Aesthetic::To]
222 }
223
224 fn default_stat(&self) -> Box<dyn Stat> {
225 Box::new(StatArcdiagram {
226 sort_by_value: self.sort_by_value,
227 directed_below: self.directed_below,
228 })
229 }
230
231 fn default_position(&self) -> Box<dyn Position> {
232 Box::new(PositionIdentity)
233 }
234
235 fn default_params(&self) -> GeomParams {
236 GeomParams::default()
237 }
238
239 fn name(&self) -> &str {
240 "arcdiagram"
241 }
242
243 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
244 if let Some(ink) = el.ink {
245 self.label_color = ink;
246 }
247 }
248}