use plotly_derive::FieldSetter;
use serde::Serialize;
use crate::{
color::Color,
common::{Dim, Domain, Font, HoverInfo, Label, LegendGroupTitle, Orientation, PlotType},
Trace,
};
#[derive(Serialize, Clone)]
#[serde(rename_all = "lowercase")]
pub enum Arrangement {
Snap,
Perpendicular,
Freeform,
Fixed,
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, FieldSetter)]
pub struct Line {
color: Option<Dim<Box<dyn Color>>>,
width: Option<f64>,
}
impl Line {
pub fn new() -> Self {
Default::default()
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, FieldSetter)]
pub struct Node {
color: Option<Dim<Box<dyn Color>>>,
#[serde(rename = "hoverinfo")]
hover_info: Option<HoverInfo>,
#[serde(rename = "hoverlabel")]
hover_label: Option<Label>,
#[serde(rename = "hovertemplate")]
hover_template: Option<Dim<String>>,
#[field_setter(skip)]
label: Option<Vec<String>>,
line: Option<Line>,
pad: Option<usize>,
thickness: Option<usize>,
x: Option<Vec<f64>>,
y: Option<Vec<f64>>,
}
impl Node {
pub fn new() -> Self {
Default::default()
}
pub fn label(mut self, label: Vec<&str>) -> Self {
self.label = Some(label.iter().map(|&el| el.to_string()).collect());
self
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, FieldSetter)]
pub struct Link<V>
where
V: Serialize + Clone,
{
color: Option<Dim<Box<dyn Color>>>,
#[serde(rename = "hoverinfo")]
hover_info: Option<HoverInfo>,
#[serde(rename = "hoverlabel")]
hover_label: Option<Label>,
#[serde(rename = "hovertemplate")]
hover_template: Option<Dim<String>>,
line: Option<Line>,
source: Option<Vec<usize>>,
target: Option<Vec<usize>>,
value: Option<Vec<V>>,
}
impl<V> Link<V>
where
V: Serialize + Clone,
{
pub fn new() -> Self {
Default::default()
}
}
#[serde_with::skip_serializing_none]
#[derive(Serialize, Clone, FieldSetter)]
#[field_setter(box_self, kind = "trace")]
pub struct Sankey<V>
where
V: Serialize + Clone,
{
#[field_setter(default = "PlotType::Sankey")]
r#type: PlotType,
arrangement: Option<Arrangement>,
domain: Option<Domain>,
ids: Option<Vec<String>>,
#[serde(rename = "hoverinfo")]
hover_info: Option<HoverInfo>,
#[serde(rename = "hoverlabel")]
hover_label: Option<Label>,
#[serde(rename = "legendgrouptitle")]
legend_group_title: Option<LegendGroupTitle>,
#[serde(rename = "legendrank")]
legend_rank: Option<usize>,
link: Option<Link<V>>,
name: Option<String>,
node: Option<Node>,
orientation: Option<Orientation>,
#[serde(rename = "selectedpoints")]
selected_points: Option<Vec<usize>>,
#[serde(rename = "textfont")]
text_font: Option<Font>,
#[serde(rename = "valueformat")]
value_format: Option<String>,
#[serde(rename = "valuesuffix")]
value_suffix: Option<String>,
visible: Option<bool>,
}
impl<V> Sankey<V>
where
V: Serialize + Clone,
{
pub fn new() -> Box<Self> {
Box::default()
}
}
impl<V> Trace for Sankey<V>
where
V: Serialize + Clone,
{
fn to_json(&self) -> String {
serde_json::to_string(self).unwrap()
}
}
#[cfg(test)]
mod tests {
use serde_json::{json, to_value};
use super::*;
use crate::color::NamedColor;
#[test]
fn serialize_default_sankey() {
let trace = Sankey::<i32>::default();
let expected = json!({"type": "sankey"});
assert_eq!(to_value(trace).unwrap(), expected);
}
#[test]
fn serialize_basic_sankey_trace() {
let trace = Sankey::new()
.orientation(Orientation::Horizontal)
.node(
Node::new()
.pad(15)
.thickness(30)
.line(Line::new().color(NamedColor::Black).width(0.5))
.label(vec!["A1", "A2", "B1", "B2", "C1", "C2"])
.color_array(vec![
NamedColor::Blue,
NamedColor::Blue,
NamedColor::Blue,
NamedColor::Blue,
NamedColor::Blue,
NamedColor::Blue,
]),
)
.link(
Link::new()
.value(vec![8, 4, 2, 8, 4, 2])
.source(vec![0, 1, 0, 2, 3, 3])
.target(vec![2, 3, 3, 4, 4, 5]),
);
let expected = json!({
"link": {
"source": [0, 1, 0, 2, 3, 3],
"target": [2, 3, 3, 4, 4, 5],
"value": [8, 4, 2, 8, 4, 2]
},
"orientation": "h",
"type": "sankey",
"node": {
"color": ["blue", "blue", "blue", "blue", "blue", "blue"],
"label": ["A1", "A2", "B1", "B2", "C1", "C2"],
"line": {
"color": "black",
"width": 0.5
},
"pad": 15,
"thickness": 30
}
});
assert_eq!(to_value(trace).unwrap(), expected);
}
#[test]
fn serialize_full_sankey_trace() {
let trace = Sankey::<i32>::new()
.name("sankey")
.visible(true)
.legend_rank(1000)
.legend_group_title("Legend Group Title")
.ids(vec!["one"])
.hover_info(HoverInfo::All)
.hover_label(Label::new())
.domain(Domain::new())
.orientation(Orientation::Horizontal)
.node(Node::new())
.link(Link::new())
.text_font(Font::new())
.selected_points(vec![0])
.arrangement(Arrangement::Fixed)
.value_format(".3f")
.value_suffix("nT");
let expected = json!({
"type": "sankey",
"name": "sankey",
"visible": true,
"legendrank": 1000,
"legendgrouptitle": {"text": "Legend Group Title"},
"ids": ["one"],
"hoverinfo": "all",
"hoverlabel": {},
"domain": {},
"orientation": "h",
"node": {},
"link": {},
"textfont": {},
"selectedpoints": [0],
"arrangement": "fixed",
"valueformat": ".3f",
"valuesuffix": "nT"
});
assert_eq!(to_value(trace).unwrap(), expected);
}
#[test]
fn serialize_arrangement() {
assert_eq!(to_value(Arrangement::Snap).unwrap(), json!("snap"));
assert_eq!(
to_value(Arrangement::Perpendicular).unwrap(),
json!("perpendicular")
);
assert_eq!(to_value(Arrangement::Freeform).unwrap(), json!("freeform"));
assert_eq!(to_value(Arrangement::Fixed).unwrap(), json!("fixed"));
}
#[test]
fn serialize_line() {
let line = Line::new()
.color_array(vec![NamedColor::Black, NamedColor::Blue])
.color(NamedColor::Black)
.width(0.1);
let expected = json!({
"color": "black",
"width": 0.1
});
assert_eq!(to_value(line).unwrap(), expected)
}
#[test]
fn serialize_node() {
let node = Node::new()
.color(NamedColor::Blue)
.color_array(vec![NamedColor::Blue])
.hover_info(HoverInfo::All)
.hover_label(Label::new())
.hover_template("template")
.line(Line::new())
.pad(5)
.thickness(10)
.x(vec![0.5])
.y(vec![0.25]);
let expected = json!({
"color": ["blue"],
"hoverinfo": "all",
"hoverlabel": {},
"hovertemplate": "template",
"line": {},
"pad": 5,
"thickness": 10,
"x": [0.5],
"y": [0.25]
});
assert_eq!(to_value(node).unwrap(), expected)
}
#[test]
fn serialize_link() {
let link = Link::new()
.color_array(vec![NamedColor::Blue])
.color(NamedColor::Blue)
.hover_info(HoverInfo::All)
.hover_label(Label::new())
.hover_template("template")
.line(Line::new())
.value(vec![2, 2, 2])
.source(vec![0, 1, 2])
.target(vec![1, 2, 0]);
let expected = json!({
"color": "blue",
"hoverinfo": "all",
"hoverlabel": {},
"hovertemplate": "template",
"line": {},
"source": [0, 1, 2],
"target": [1, 2, 0],
"value": [2, 2, 2],
});
assert_eq!(to_value(link).unwrap(), expected)
}
}