1use plotly_derive::FieldSetter;
4use serde::Serialize;
5
6use crate::{
7 color::Color,
8 common::{Dim, Domain, Font, HoverInfo, Label, LegendGroupTitle, Orientation, PlotType},
9 Trace,
10};
11
12#[derive(Serialize, Clone)]
13#[serde(rename_all = "lowercase")]
14pub enum Arrangement {
15 Snap,
16 Perpendicular,
17 Freeform,
18 Fixed,
19}
20
21#[serde_with::skip_serializing_none]
22#[derive(Serialize, Clone, FieldSetter)]
23pub struct Line {
24 color: Option<Dim<Box<dyn Color>>>,
25 width: Option<f64>,
26}
27
28impl Line {
29 pub fn new() -> Self {
30 Default::default()
31 }
32}
33
34#[serde_with::skip_serializing_none]
35#[derive(Serialize, Clone, FieldSetter)]
36pub struct Node {
37 color: Option<Dim<Box<dyn Color>>>,
39 #[serde(rename = "hoverinfo")]
40 hover_info: Option<HoverInfo>,
41 #[serde(rename = "hoverlabel")]
42 hover_label: Option<Label>,
43 #[serde(rename = "hovertemplate")]
44 hover_template: Option<Dim<String>>,
45 #[field_setter(skip)]
46 label: Option<Vec<String>>,
47 line: Option<Line>,
48 pad: Option<usize>,
50 thickness: Option<usize>,
52 x: Option<Vec<f64>>,
54 y: Option<Vec<f64>>,
56}
57
58impl Node {
59 pub fn new() -> Self {
60 Default::default()
61 }
62
63 pub fn label(mut self, label: Vec<&str>) -> Self {
64 self.label = Some(label.iter().map(|&el| el.to_string()).collect());
65 self
66 }
67}
68
69#[serde_with::skip_serializing_none]
70#[derive(Serialize, Clone, FieldSetter)]
71pub struct Link<V>
72where
73 V: Serialize + Clone,
74{
75 color: Option<Dim<Box<dyn Color>>>,
77 #[serde(rename = "hoverinfo")]
78 hover_info: Option<HoverInfo>,
79 #[serde(rename = "hoverlabel")]
80 hover_label: Option<Label>,
81 #[serde(rename = "hovertemplate")]
82 hover_template: Option<Dim<String>>,
83 line: Option<Line>,
84 source: Option<Vec<usize>>,
85 target: Option<Vec<usize>>,
86 value: Option<Vec<V>>,
87}
88
89impl<V> Link<V>
90where
91 V: Serialize + Clone,
92{
93 pub fn new() -> Self {
94 Default::default()
95 }
96}
97
98#[serde_with::skip_serializing_none]
151#[derive(Serialize, Clone, FieldSetter)]
152#[field_setter(box_self, kind = "trace")]
153pub struct Sankey<V>
154where
155 V: Serialize + Clone,
156{
157 #[field_setter(default = "PlotType::Sankey")]
159 r#type: PlotType,
160 arrangement: Option<Arrangement>,
167 domain: Option<Domain>,
169 ids: Option<Vec<String>>,
172 #[serde(rename = "hoverinfo")]
178 hover_info: Option<HoverInfo>,
179 #[serde(rename = "hoverlabel")]
181 hover_label: Option<Label>,
182 #[serde(rename = "legendgrouptitle")]
184 legend_group_title: Option<LegendGroupTitle>,
185 #[serde(rename = "legendrank")]
192 legend_rank: Option<usize>,
193 link: Option<Link<V>>,
195 name: Option<String>,
198 node: Option<Node>,
200 orientation: Option<Orientation>,
202 #[serde(rename = "selectedpoints")]
207 selected_points: Option<Vec<usize>>,
208 #[serde(rename = "textfont")]
210 text_font: Option<Font>,
211 #[serde(rename = "valueformat")]
214 value_format: Option<String>,
215 #[serde(rename = "valuesuffix")]
218 value_suffix: Option<String>,
219 visible: Option<bool>,
223}
224
225impl<V> Sankey<V>
226where
227 V: Serialize + Clone,
228{
229 pub fn new() -> Box<Self> {
231 Box::default()
232 }
233}
234
235impl<V> Trace for Sankey<V>
236where
237 V: Serialize + Clone,
238{
239 fn to_json(&self) -> String {
240 serde_json::to_string(self).unwrap()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use serde_json::{json, to_value};
247
248 use super::*;
249 use crate::color::NamedColor;
250
251 #[test]
252 fn serialize_default_sankey() {
253 let trace = Sankey::<i32>::default();
254 let expected = json!({"type": "sankey"});
255
256 assert_eq!(to_value(trace).unwrap(), expected);
257 }
258
259 #[test]
260 fn serialize_basic_sankey_trace() {
261 let trace = Sankey::new()
264 .orientation(Orientation::Horizontal)
265 .node(
266 Node::new()
267 .pad(15)
268 .thickness(30)
269 .line(Line::new().color(NamedColor::Black).width(0.5))
270 .label(vec!["A1", "A2", "B1", "B2", "C1", "C2"])
271 .color_array(vec![
272 NamedColor::Blue,
273 NamedColor::Blue,
274 NamedColor::Blue,
275 NamedColor::Blue,
276 NamedColor::Blue,
277 NamedColor::Blue,
278 ]),
279 )
280 .link(
281 Link::new()
282 .value(vec![8, 4, 2, 8, 4, 2])
283 .source(vec![0, 1, 0, 2, 3, 3])
284 .target(vec![2, 3, 3, 4, 4, 5]),
285 );
286
287 let expected = json!({
288 "link": {
289 "source": [0, 1, 0, 2, 3, 3],
290 "target": [2, 3, 3, 4, 4, 5],
291 "value": [8, 4, 2, 8, 4, 2]
292 },
293 "orientation": "h",
294 "type": "sankey",
295 "node": {
296 "color": ["blue", "blue", "blue", "blue", "blue", "blue"],
297 "label": ["A1", "A2", "B1", "B2", "C1", "C2"],
298 "line": {
299 "color": "black",
300 "width": 0.5
301 },
302 "pad": 15,
303 "thickness": 30
304 }
305 });
306
307 assert_eq!(to_value(trace).unwrap(), expected);
308 }
309
310 #[test]
311 fn serialize_full_sankey_trace() {
312 let trace = Sankey::<i32>::new()
313 .name("sankey")
314 .visible(true)
315 .legend_rank(1000)
316 .legend_group_title("Legend Group Title")
317 .ids(vec!["one"])
318 .hover_info(HoverInfo::All)
319 .hover_label(Label::new())
320 .domain(Domain::new())
321 .orientation(Orientation::Horizontal)
322 .node(Node::new())
323 .link(Link::new())
324 .text_font(Font::new())
325 .selected_points(vec![0])
326 .arrangement(Arrangement::Fixed)
327 .value_format(".3f")
328 .value_suffix("nT");
329
330 let expected = json!({
331 "type": "sankey",
332 "name": "sankey",
333 "visible": true,
334 "legendrank": 1000,
335 "legendgrouptitle": {"text": "Legend Group Title"},
336 "ids": ["one"],
337 "hoverinfo": "all",
338 "hoverlabel": {},
339 "domain": {},
340 "orientation": "h",
341 "node": {},
342 "link": {},
343 "textfont": {},
344 "selectedpoints": [0],
345 "arrangement": "fixed",
346 "valueformat": ".3f",
347 "valuesuffix": "nT"
348 });
349
350 assert_eq!(to_value(trace).unwrap(), expected);
351 }
352
353 #[test]
354 fn serialize_arrangement() {
355 assert_eq!(to_value(Arrangement::Snap).unwrap(), json!("snap"));
356 assert_eq!(
357 to_value(Arrangement::Perpendicular).unwrap(),
358 json!("perpendicular")
359 );
360 assert_eq!(to_value(Arrangement::Freeform).unwrap(), json!("freeform"));
361 assert_eq!(to_value(Arrangement::Fixed).unwrap(), json!("fixed"));
362 }
363
364 #[test]
365 fn serialize_line() {
366 let line = Line::new()
367 .color_array(vec![NamedColor::Black, NamedColor::Blue])
368 .color(NamedColor::Black)
369 .width(0.1);
370 let expected = json!({
371 "color": "black",
372 "width": 0.1
373 });
374
375 assert_eq!(to_value(line).unwrap(), expected)
376 }
377
378 #[test]
379 fn serialize_node() {
380 let node = Node::new()
381 .color(NamedColor::Blue)
382 .color_array(vec![NamedColor::Blue])
383 .hover_info(HoverInfo::All)
384 .hover_label(Label::new())
385 .hover_template("template")
386 .line(Line::new())
387 .pad(5)
388 .thickness(10)
389 .x(vec![0.5])
390 .y(vec![0.25]);
391 let expected = json!({
392 "color": ["blue"],
393 "hoverinfo": "all",
394 "hoverlabel": {},
395 "hovertemplate": "template",
396 "line": {},
397 "pad": 5,
398 "thickness": 10,
399 "x": [0.5],
400 "y": [0.25]
401 });
402
403 assert_eq!(to_value(node).unwrap(), expected)
404 }
405
406 #[test]
407 fn serialize_link() {
408 let link = Link::new()
409 .color_array(vec![NamedColor::Blue])
410 .color(NamedColor::Blue)
411 .hover_info(HoverInfo::All)
412 .hover_label(Label::new())
413 .hover_template("template")
414 .line(Line::new())
415 .value(vec![2, 2, 2])
416 .source(vec![0, 1, 2])
417 .target(vec![1, 2, 0]);
418 let expected = json!({
419 "color": "blue",
420 "hoverinfo": "all",
421 "hoverlabel": {},
422 "hovertemplate": "template",
423 "line": {},
424 "source": [0, 1, 2],
425 "target": [1, 2, 0],
426 "value": [2, 2, 2],
427 });
428
429 assert_eq!(to_value(link).unwrap(), expected)
430 }
431}