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::{DrawBackend, FontFace, RectStyle, TextAnchor, TextStyle};
11use crate::render::RenderError;
12use crate::scale::ScaleSet;
13use crate::stat::chord::StatChord;
14use crate::stat::Stat;
15use crate::theme::Theme;
16
17use super::{Geom, GeomParams};
18
19pub struct GeomChord {
22 pub pad_angle: f64,
24 pub arc_width: f64,
26 pub fill: (u8, u8, u8),
28 pub node_alpha: f64,
29 pub link_alpha: f64,
30 pub labels: bool,
32 pub label_size: f64,
33 pub label_color: (u8, u8, u8),
34}
35
36impl Default for GeomChord {
37 fn default() -> Self {
38 GeomChord {
39 pad_angle: 0.04,
40 arc_width: 0.07,
41 fill: (97, 156, 255),
42 node_alpha: 1.0,
43 link_alpha: 0.55,
44 labels: true,
45 label_size: 10.0,
46 label_color: (40, 40, 40),
47 }
48 }
49}
50
51impl GeomChord {
52 pub(crate) fn extents(&self) -> (f64, f64) {
55 let s = StatChord::default();
56 (s.x_extent, s.y_extent)
57 }
58}
59
60fn polar(a: f64, r: f64) -> (f64, f64) {
62 (r * a.sin(), r * a.cos())
63}
64
65fn arc(a0: f64, a1: f64, r: f64, out: &mut Vec<(f64, f64)>) {
67 let steps = (((a1 - a0).abs() / 0.03).ceil() as usize).clamp(2, 400);
68 for k in 0..=steps {
69 out.push(polar(a0 + (a1 - a0) * k as f64 / steps as f64, r));
70 }
71}
72
73fn through_centre(p: (f64, f64), q: (f64, f64), out: &mut Vec<(f64, f64)>) {
75 const STEPS: usize = 24;
76 for k in 1..STEPS {
77 let t = k as f64 / STEPS as f64;
78 let (u, v) = ((1.0 - t) * (1.0 - t), t * t);
79 out.push((u * p.0 + v * q.0, u * p.1 + v * q.1));
80 }
81}
82
83impl Geom for GeomChord {
84 fn draw(
85 &self,
86 data: &DataFrame,
87 coord: &dyn Coord,
88 scales: &ScaleSet,
89 theme: &Theme,
90 backend: &mut dyn DrawBackend,
91 ) -> Result<(), RenderError> {
92 let col = |n: &str| {
93 data.column(n)
94 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
95 };
96 let (part, a0c, a1c, b0c, b1c) =
97 (col("part")?, col("a0")?, col("a1")?, col("b0")?, col("b1")?);
98 let (node, from, to, value) = (col("node")?, col("from")?, col("to")?, col("value")?);
99 let fill_col = data.column("fill");
100 let area = backend.plot_area();
101 let map = |(x, y): (f64, f64)| {
102 let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
103 let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
104 coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
105 };
106 let fill_of = |i: usize| {
107 fill_col
108 .and_then(|c| scales.map_color(&Aesthetic::Fill, &c[i]))
109 .unwrap_or(self.fill)
110 };
111 let f = |c: &[Value], i: usize| c[i].as_f64().unwrap_or(f64::NAN);
112 let is = |i: usize, p: &str| part[i].key_str() == p;
113 let r_in = 1.0 - self.arc_width;
114 let style = |fill, alpha| RectStyle {
115 fill: Some(fill),
116 stroke: None,
117 stroke_width: 0.0,
118 alpha,
119 clip: true,
120 corner_radius: 0.0,
121 };
122
123 for i in (0..data.nrows()).filter(|&i| is(i, "link")) {
124 let (a0, a1, b0, b1) = (f(a0c, i), f(a1c, i), f(b0c, i), f(b1c, i));
125 if ![a0, a1, b0, b1].iter().all(|v| v.is_finite()) {
126 continue;
127 }
128 let r = r_in * 0.99;
129 let mut pts = Vec::new();
130 arc(a0, a1, r, &mut pts);
131 through_centre(polar(a1, r), polar(b0, r), &mut pts);
132 arc(b0, b1, r, &mut pts);
133 through_centre(polar(b1, r), polar(a0, r), &mut pts);
134 let pts: Vec<(f64, f64)> = pts.into_iter().map(map).collect();
135 let (a, b) = (super::tip_value(&from[i]), super::tip_value(&to[i]));
136 super::set_mark(
137 backend,
138 Some(format!("{a} → {b}: {}", super::tip_value(&value[i]))),
139 Some(a.clone()),
140 Some(a),
141 super::raw_value(&value[i]),
142 );
143 backend.draw_polygon(&pts, &style(fill_of(i), self.link_alpha))?;
144 }
145
146 let mut labels = Vec::new();
147 for i in (0..data.nrows()).filter(|&i| is(i, "node")) {
148 let (a0, a1) = (f(a0c, i), f(a1c, i));
149 if !(a0.is_finite() && a1.is_finite()) {
150 continue;
151 }
152 let mut pts = Vec::new();
153 arc(a0, a1, 1.0, &mut pts);
154 let mut inner = Vec::new();
155 arc(a0, a1, r_in, &mut inner);
156 pts.extend(inner.into_iter().rev());
157 let pts: Vec<(f64, f64)> = pts.into_iter().map(map).collect();
158 let name = super::tip_value(&node[i]);
159 super::set_mark(
160 backend,
161 Some(format!("{name}: {}", super::tip_value(&value[i]))),
162 Some(name.clone()),
163 Some(name.clone()),
164 super::raw_value(&value[i]),
165 );
166 backend.draw_polygon(&pts, &style(fill_of(i), self.node_alpha))?;
167 if self.labels {
168 let mid = (a0 + a1) / 2.0;
169 let side = mid.sin();
170 let anchor = if side.abs() < 0.12 {
171 TextAnchor::Middle
172 } else if side > 0.0 {
173 TextAnchor::Start
174 } else {
175 TextAnchor::End
176 };
177 let r = 1.06 + 0.06 * mid.cos().abs().powi(4);
180 labels.push((name, map(polar(mid, r)), anchor));
181 }
182 }
183 super::clear_mark(backend);
184 let family = super::resolve_font_family("", theme);
185 for (text, pos, anchor) in labels {
186 backend.draw_text(
187 &text,
188 pos,
189 &TextStyle {
190 color: self.label_color,
191 size: self.label_size,
192 anchor,
193 angle: 0.0,
194 family: family.clone(),
195 face: FontFace::Plain,
196 bg_colour: None,
197 bg_r: 0.0,
198 },
199 )?;
200 }
201 Ok(())
202 }
203
204 fn required_aes(&self) -> Vec<Aesthetic> {
205 vec![Aesthetic::From, Aesthetic::To]
206 }
207
208 fn default_stat(&self) -> Box<dyn Stat> {
209 Box::new(StatChord {
210 pad_angle: self.pad_angle,
211 ..Default::default()
212 })
213 }
214
215 fn default_position(&self) -> Box<dyn Position> {
216 Box::new(PositionIdentity)
217 }
218
219 fn default_params(&self) -> GeomParams {
220 GeomParams::default()
221 }
222
223 fn name(&self) -> &str {
224 "chord"
225 }
226
227 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
228 if let Some(ink) = el.ink {
229 self.label_color = ink;
230 }
231 }
232}