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, TextAnchor, TextStyle,
13};
14use crate::render::RenderError;
15use crate::scale::ScaleSet;
16use crate::stat::network::{NetworkLayout, StatNetwork};
17use crate::stat::Stat;
18use crate::theme::Theme;
19
20use super::arrow::{arrow, draw_heads, heads, ArrowType};
21use super::{Geom, GeomParams};
22
23pub struct GeomNetwork {
25 pub layout: NetworkLayout,
26 pub directed: bool,
28 pub color_components: bool,
30 pub fill: (u8, u8, u8),
32 pub node_size: (f64, f64),
34 pub edge_color: (u8, u8, u8),
35 pub edge_width: (f64, f64),
37 pub edge_alpha: f64,
38 pub labels: bool,
39 pub label_size: f64,
40 pub label_color: (u8, u8, u8),
41}
42
43impl Default for GeomNetwork {
44 fn default() -> Self {
45 GeomNetwork {
46 layout: NetworkLayout::default(),
47 directed: false,
48 color_components: false,
49 fill: (31, 119, 180),
50 node_size: (4.0, 11.0),
51 edge_color: (150, 150, 150),
52 edge_width: (0.8, 4.0),
53 edge_alpha: 0.7,
54 labels: true,
55 label_size: 9.0,
56 label_color: (40, 40, 40),
57 }
58 }
59}
60
61impl Geom for GeomNetwork {
62 fn draw(
63 &self,
64 data: &DataFrame,
65 coord: &dyn Coord,
66 scales: &ScaleSet,
67 theme: &Theme,
68 backend: &mut dyn DrawBackend,
69 ) -> Result<(), RenderError> {
70 let col = |n: &str| {
71 data.column(n)
72 .ok_or_else(|| RenderError::MissingAesthetic(n.into()))
73 };
74 let (part, xc, yc, xe, ye) = (
75 col("part")?,
76 col("x")?,
77 col("y")?,
78 col("xend")?,
79 col("yend")?,
80 );
81 let (node, from, to, value, degree) = (
82 col("node")?,
83 col("from")?,
84 col("to")?,
85 col("value")?,
86 col("degree")?,
87 );
88 let fill_col = data.column("fill");
89 let area = backend.plot_area();
90 let px = |x: f64, y: f64| {
91 let sx = scales.get(&Aesthetic::X).map(|s| s.map(&Value::Float(x)));
92 let sy = scales.get(&Aesthetic::Y).map(|s| s.map(&Value::Float(y)));
93 coord.transform((sx.unwrap_or(0.5), sy.unwrap_or(0.5)), &area)
94 };
95 let is = |i: usize, p: &str| part[i].key_str() == p;
96 let max_of = |p: &str, c: &[Value]| {
97 (0..data.nrows())
98 .filter(|&i| is(i, p))
99 .filter_map(|i| c[i].as_f64())
100 .fold(0.0, f64::max)
101 };
102 let (max_w, max_d) = (max_of("edge", value), max_of("node", degree));
103 let lerp = |(lo, hi): (f64, f64), t: f64| lo + (hi - lo) * t.clamp(0.0, 1.0);
104 let radius = |d: f64| {
105 lerp(
106 self.node_size,
107 if max_d > 0.0 { (d / max_d).sqrt() } else { 0.0 },
108 )
109 };
110 let node_radius: std::collections::HashMap<String, f64> = (0..data.nrows())
112 .filter(|&i| is(i, "node"))
113 .map(|i| {
114 (
115 node[i].key_str().into_owned(),
116 radius(degree[i].as_f64().unwrap_or(0.0)),
117 )
118 })
119 .collect();
120 let head = arrow().kind(ArrowType::Closed).length(8.0);
121
122 for i in (0..data.nrows()).filter(|&i| is(i, "edge")) {
123 let (Some(x0), Some(y0), Some(x1), Some(y1)) = (
124 xc[i].as_f64(),
125 yc[i].as_f64(),
126 xe[i].as_f64(),
127 ye[i].as_f64(),
128 ) else {
129 continue;
130 };
131 let (a, mut b) = (px(x0, y0), px(x1, y1));
132 let w = lerp(
133 self.edge_width,
134 value[i]
135 .as_f64()
136 .map(|v| if max_w > 0.0 { v / max_w } else { 1.0 })
137 .unwrap_or(0.0),
138 );
139 if self.directed {
140 let r = node_radius
141 .get(to[i].key_str().as_ref())
142 .copied()
143 .unwrap_or(0.0)
144 + 1.0;
145 let (dx, dy) = (b.0 - a.0, b.1 - a.1);
146 let len = dx.hypot(dy);
147 if len > r {
148 b = (b.0 - dx / len * r, b.1 - dy / len * r);
149 }
150 }
151 let (f, t) = (super::tip_value(&from[i]), super::tip_value(&to[i]));
152 let sep = if self.directed { "→" } else { "—" };
153 super::set_mark(
154 backend,
155 Some(format!("{f} {sep} {t}: {}", super::tip_value(&value[i]))),
156 Some(f.clone()),
157 Some(f),
158 super::raw_value(&value[i]),
159 );
160 backend.draw_line(
161 &[a, b],
162 &LineStyle {
163 color: self.edge_color,
164 alpha: self.edge_alpha,
165 width: w,
166 linetype: Linetype::Solid,
167 ..Default::default()
168 },
169 )?;
170 if self.directed {
171 let hs = heads(&[a, b], &head, w);
172 draw_heads(backend, &hs, &head, self.edge_color, self.edge_alpha, w)?;
173 backend.set_mark_part(None);
174 }
175 }
176
177 let mut labels = Vec::new();
178 for i in (0..data.nrows()).filter(|&i| is(i, "node")) {
179 let (Some(x), Some(y)) = (xc[i].as_f64(), yc[i].as_f64()) else {
180 continue;
181 };
182 let c = px(x, y);
183 let r = radius(degree[i].as_f64().unwrap_or(0.0));
184 let fill = fill_col
185 .and_then(|f| scales.map_color(&Aesthetic::Fill, &f[i]))
186 .unwrap_or(self.fill);
187 let name = super::tip_value(&node[i]);
188 super::set_mark(
189 backend,
190 Some(format!("{name} (degree {})", super::tip_value(°ree[i]))),
191 Some(name.clone()),
192 Some(name.clone()),
193 super::raw_value(°ree[i]),
194 );
195 backend.draw_circle(
196 c,
197 r,
198 &PointStyle {
199 color: fill,
200 alpha: 1.0,
201 filled: true,
202 ..Default::default()
203 },
204 )?;
205 if self.labels {
206 labels.push((name, (c.0 + r + 3.0, c.1)));
207 }
208 }
209 super::clear_mark(backend);
210 let family = super::resolve_font_family("", theme);
211 for (text, pos) in labels {
212 backend.draw_text(
213 &text,
214 pos,
215 &TextStyle {
216 color: self.label_color,
217 size: self.label_size,
218 anchor: TextAnchor::Start,
219 angle: 0.0,
220 family: family.clone(),
221 face: FontFace::Plain,
222 bg_colour: Some((255, 255, 255)),
223 bg_r: 1.5,
224 },
225 )?;
226 }
227 Ok(())
228 }
229
230 fn required_aes(&self) -> Vec<Aesthetic> {
231 vec![Aesthetic::From, Aesthetic::To]
232 }
233
234 fn default_stat(&self) -> Box<dyn Stat> {
235 Box::new(StatNetwork {
236 layout: self.layout,
237 })
238 }
239
240 fn default_position(&self) -> Box<dyn Position> {
241 Box::new(PositionIdentity)
242 }
243
244 fn default_params(&self) -> GeomParams {
245 GeomParams::default()
246 }
247
248 fn name(&self) -> &str {
249 "network"
250 }
251
252 fn apply_element_geom(&mut self, el: &crate::theme::elements::ElementGeom) {
253 if let Some(ink) = el.ink {
254 self.label_color = ink;
255 }
256 }
257
258 fn set_series_color(&mut self, color: (u8, u8, u8)) {
259 self.fill = color;
260 }
261}