1use gpui::{Path, PathBuilder, Pixels, Point, px};
4
5use crate::plot::origin_point;
6
7const STAGGER_RATIO: f32 = 0.15;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
16pub enum SankeyAlign {
17 Left,
18 Right,
19 Center,
20 #[default]
21 Justify,
22}
23
24#[derive(Clone, Copy, Debug, PartialEq)]
29pub struct SankeyLink {
30 pub source: usize,
31 pub target: usize,
32 pub value: f64,
33}
34
35impl SankeyLink {
36 pub fn new(source: usize, target: usize, value: f64) -> Self {
39 Self {
40 source,
41 target,
42 value,
43 }
44 }
45}
46
47#[derive(Clone, Debug, Default)]
49pub struct SankeyNodeLayout {
50 pub index: usize,
51 pub value: f64,
55 pub depth: usize,
57 pub height: usize,
59 pub layer: usize,
61 pub x0: f32,
62 pub x1: f32,
63 pub y0: f32,
64 pub y1: f32,
65 pub source_links: Vec<usize>,
67 pub target_links: Vec<usize>,
69}
70
71#[derive(Clone, Debug)]
81pub struct SankeyLinkLayout {
82 pub index: usize,
83 pub source: usize,
84 pub target: usize,
85 pub value: f64,
88 pub y0: f32,
89 pub y1: f32,
90 pub width: f32,
93 pub source_width: f32,
95 pub target_width: f32,
97}
98
99#[derive(Clone, Debug, Default)]
101pub struct SankeyGraph {
102 pub nodes: Vec<SankeyNodeLayout>,
103 pub links: Vec<SankeyLinkLayout>,
104}
105
106impl SankeyGraph {
107 pub fn layer_count(&self) -> usize {
109 self.nodes
110 .iter()
111 .map(|node| node.layer + 1)
112 .max()
113 .unwrap_or(0)
114 }
115}
116
117#[derive(Clone, Copy, Debug, PartialEq, Eq)]
119pub enum SankeyError {
120 MissingNode(usize),
122 CircularLink,
124}
125
126impl std::fmt::Display for SankeyError {
127 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128 match self {
129 Self::MissingNode(index) => write!(f, "missing node: {}", index),
130 Self::CircularLink => write!(f, "circular link"),
131 }
132 }
133}
134
135impl std::error::Error for SankeyError {}
136
137#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
139pub enum SankeyValueScale {
140 #[default]
142 Linear,
143 Sqrt,
147}
148
149impl SankeyValueScale {
150 fn apply(self, value: f64) -> f64 {
151 match self {
152 Self::Linear => value,
153 Self::Sqrt => value.max(0.).sqrt(),
155 }
156 }
157}
158
159pub struct Sankey {
161 node_width: f32,
162 node_padding: f32,
163 align: SankeyAlign,
164 iterations: usize,
165 value_scale: SankeyValueScale,
166 x0: f32,
167 y0: f32,
168 x1: f32,
169 y1: f32,
170}
171
172impl Default for Sankey {
173 fn default() -> Self {
174 Self {
175 node_width: 24.,
176 node_padding: 8.,
177 align: SankeyAlign::default(),
178 iterations: 6,
179 value_scale: SankeyValueScale::default(),
180 x0: 0.,
181 y0: 0.,
182 x1: 1.,
183 y1: 1.,
184 }
185 }
186}
187
188impl Sankey {
189 pub fn new() -> Self {
191 Self::default()
192 }
193
194 pub fn node_width(mut self, node_width: f32) -> Self {
196 self.node_width = node_width;
197 self
198 }
199
200 pub fn node_padding(mut self, node_padding: f32) -> Self {
202 self.node_padding = node_padding;
203 self
204 }
205
206 pub fn node_align(mut self, align: SankeyAlign) -> Self {
208 self.align = align;
209 self
210 }
211
212 pub fn iterations(mut self, iterations: usize) -> Self {
214 self.iterations = iterations;
215 self
216 }
217
218 pub fn value_scale(mut self, value_scale: SankeyValueScale) -> Self {
220 self.value_scale = value_scale;
221 self
222 }
223
224 pub fn extent(mut self, x0: f32, y0: f32, x1: f32, y1: f32) -> Self {
226 self.x0 = x0;
227 self.y0 = y0;
228 self.x1 = x1;
229 self.y1 = y1;
230 self
231 }
232
233 pub fn size(self, width: f32, height: f32) -> Self {
235 self.extent(0., 0., width, height)
236 }
237
238 pub fn topology(
247 &self,
248 node_count: usize,
249 links: &[SankeyLink],
250 ) -> Result<SankeyGraph, SankeyError> {
251 for link in links {
252 if link.source >= node_count {
253 return Err(SankeyError::MissingNode(link.source));
254 }
255 if link.target >= node_count {
256 return Err(SankeyError::MissingNode(link.target));
257 }
258 }
259
260 let mut graph = SankeyGraph {
261 nodes: (0..node_count)
262 .map(|index| SankeyNodeLayout {
263 index,
264 ..Default::default()
265 })
266 .collect(),
267 links: links
268 .iter()
269 .enumerate()
270 .map(|(index, link)| SankeyLinkLayout {
271 index,
272 source: link.source,
273 target: link.target,
274 value: self.value_scale.apply(link.value),
279 y0: 0.,
280 y1: 0.,
281 width: 0.,
282 source_width: 0.,
283 target_width: 0.,
284 })
285 .collect(),
286 };
287 if node_count == 0 {
288 return Ok(graph);
289 }
290
291 compute_node_links(&mut graph);
292 compute_node_values(&mut graph);
293 compute_node_ranks(&mut graph)?;
294 self.compute_node_layers(&mut graph);
295
296 Ok(graph)
297 }
298
299 pub fn layout(
302 &self,
303 node_count: usize,
304 links: &[SankeyLink],
305 ) -> Result<SankeyGraph, SankeyError> {
306 Ok(self.layout_from(self.topology(node_count, links)?))
307 }
308
309 pub fn layout_from(&self, mut graph: SankeyGraph) -> SankeyGraph {
316 if graph.nodes.is_empty() {
317 return graph;
318 }
319
320 self.compute_node_layers(&mut graph);
321
322 let mut columns = vec![Vec::new(); graph.layer_count()];
323 for node in &graph.nodes {
324 columns[node.layer].push(node.index);
325 }
326
327 self.compute_node_breadths(&mut graph, &mut columns);
328 compute_link_breadths(&mut graph);
329 self.center_columns(&mut graph);
330 self.stagger_flat_columns(&mut graph);
331
332 graph
333 }
334
335 fn center_columns(&self, graph: &mut SankeyGraph) {
344 let layers = graph.layer_count();
345 if layers == 0 {
346 return;
347 }
348
349 let mut lo = vec![f32::INFINITY; layers];
351 let mut hi = vec![f32::NEG_INFINITY; layers];
352 for node in &graph.nodes {
353 lo[node.layer] = lo[node.layer].min(node.y0);
354 hi[node.layer] = hi[node.layer].max(node.y1);
355 }
356 let offsets: Vec<f32> = (0..layers)
357 .map(|l| {
358 if lo[l].is_finite() && hi[l] > lo[l] {
359 (self.y0 + self.y1 - lo[l] - hi[l]) / 2.
360 } else {
361 0.
362 }
363 })
364 .collect();
365
366 apply_layer_offsets(graph, &offsets);
367 }
368
369 fn stagger_flat_columns(&self, graph: &mut SankeyGraph) {
376 let layers = graph.layer_count();
377 if layers < 2 {
378 return;
379 }
380
381 let mut count = vec![0usize; layers];
382 let mut single = vec![usize::MAX; layers];
383 for node in &graph.nodes {
384 count[node.layer] += 1;
385 single[node.layer] = node.index;
386 }
387 let heights: Vec<f32> = (0..layers)
388 .map(|l| {
389 if count[l] == 1 {
390 let n = &graph.nodes[single[l]];
391 n.y1 - n.y0
392 } else {
393 0.
394 }
395 })
396 .collect();
397
398 let mut offsets = vec![0f32; layers];
401 let mut run = 0usize;
402 for l in 1..layers {
403 let flat =
404 count[l] == 1 && count[l - 1] == 1 && (heights[l] - heights[l - 1]).abs() < 1e-3;
405 if flat {
406 run += 1;
407 if run % 2 == 1 {
408 let slack = (self.y1 - self.y0 - heights[l]).max(0.);
411 offsets[l] = (heights[l] * STAGGER_RATIO).min(slack / 2.);
412 }
413 } else {
414 run = 0;
415 }
416 }
417
418 apply_layer_offsets(graph, &offsets);
419 }
420
421 fn align_layer(&self, graph: &SankeyGraph, index: usize, n: usize) -> usize {
422 let node = &graph.nodes[index];
423 match self.align {
424 SankeyAlign::Left => node.depth,
425 SankeyAlign::Right => n - 1 - node.height,
426 SankeyAlign::Justify => {
427 if node.source_links.is_empty() {
428 n - 1
429 } else {
430 node.depth
431 }
432 }
433 SankeyAlign::Center => {
434 if !node.target_links.is_empty() {
435 node.depth
436 } else if !node.source_links.is_empty() {
437 node.source_links
438 .iter()
439 .map(|&link| graph.nodes[graph.links[link].target].depth)
440 .min()
441 .unwrap_or(1)
442 .saturating_sub(1)
443 } else {
444 0
445 }
446 }
447 }
448 }
449
450 fn compute_node_layers(&self, graph: &mut SankeyGraph) {
451 let n = graph
452 .nodes
453 .iter()
454 .map(|node| node.depth + 1)
455 .max()
456 .unwrap_or(0);
457 let kx = if n > 1 {
458 (self.x1 - self.x0 - self.node_width) / (n - 1) as f32
459 } else {
460 0.
461 };
462
463 let layers: Vec<usize> = (0..graph.nodes.len())
464 .map(|index| self.align_layer(graph, index, n).min(n - 1))
465 .collect();
466
467 for (index, layer) in layers.into_iter().enumerate() {
468 let node = &mut graph.nodes[index];
469 node.layer = layer;
470 node.x0 = self.x0 + layer as f32 * kx;
471 node.x1 = node.x0 + self.node_width;
472 }
473 }
474
475 fn compute_node_breadths(&self, graph: &mut SankeyGraph, columns: &mut [Vec<usize>]) {
476 let max_column_len = columns.iter().map(|column| column.len()).max().unwrap_or(0);
477 let py = if max_column_len > 1 {
478 self.node_padding
479 .min((self.y1 - self.y0) / (max_column_len - 1) as f32)
480 } else {
481 self.node_padding
482 };
483
484 self.initialize_node_breadths(graph, columns, py);
485
486 for i in 0..self.iterations {
487 let alpha = 0.99_f32.powi(i as i32);
488 let beta = (1. - alpha).max((i + 1) as f32 / self.iterations as f32);
489 self.relax_right_to_left(graph, columns, alpha, beta, py);
490 self.relax_left_to_right(graph, columns, alpha, beta, py);
491 }
492 }
493
494 fn initialize_node_breadths(&self, graph: &mut SankeyGraph, columns: &[Vec<usize>], py: f32) {
495 let mut ky = f32::INFINITY;
498 for column in columns {
499 let value_sum: f64 = column.iter().map(|&index| graph.nodes[index].value).sum();
500 if value_sum > 0. {
501 let k = (self.y1 - self.y0 - (column.len() - 1) as f32 * py) / value_sum as f32;
502 ky = ky.min(k);
503 }
504 }
505 if !ky.is_finite() {
506 ky = 0.;
507 }
508 ky = ky.max(0.);
509
510 for column in columns {
511 let mut y = self.y0;
512 for &index in column {
513 let node_height = graph.nodes[index].value as f32 * ky;
514 let node = &mut graph.nodes[index];
515 node.y0 = y;
516 node.y1 = y + node_height;
517 y = node.y1 + py;
518 }
519
520 let leftover = (self.y1 - y + py) / (column.len() + 1) as f32;
523 for (i, &index) in column.iter().enumerate() {
524 let node = &mut graph.nodes[index];
525 let dy = leftover * (i + 1) as f32;
526 node.y0 += dy;
527 node.y1 += dy;
528 }
529 }
530
531 for link in &mut graph.links {
532 link.width = link.value as f32 * ky;
533 }
534
535 for column in columns {
536 for &index in column {
537 sort_source_links(graph, index);
538 sort_target_links(graph, index);
539 }
540 }
541 }
542
543 fn relax_left_to_right(
545 &self,
546 graph: &mut SankeyGraph,
547 columns: &mut [Vec<usize>],
548 alpha: f32,
549 beta: f32,
550 py: f32,
551 ) {
552 for i in 1..columns.len() {
553 for &target in &columns[i] {
554 let mut y = 0.;
555 let mut w = 0.;
556 for &link_index in &graph.nodes[target].target_links {
557 let link = &graph.links[link_index];
558 let v = link.value as f32
559 * (graph.nodes[target].layer as f32
560 - graph.nodes[link.source].layer as f32);
561 y += target_top(graph, link.source, target, py) * v;
562 w += v;
563 }
564 if w <= 0. {
565 continue;
566 }
567 let dy = (y / w - graph.nodes[target].y0) * alpha;
568 graph.nodes[target].y0 += dy;
569 graph.nodes[target].y1 += dy;
570 reorder_node_links(graph, target);
571 }
572 sort_column(graph, &mut columns[i]);
573 self.resolve_collisions(graph, &columns[i], beta, py);
574 }
575 }
576
577 fn relax_right_to_left(
579 &self,
580 graph: &mut SankeyGraph,
581 columns: &mut [Vec<usize>],
582 alpha: f32,
583 beta: f32,
584 py: f32,
585 ) {
586 for i in (0..columns.len().saturating_sub(1)).rev() {
587 for &source in &columns[i] {
588 let mut y = 0.;
589 let mut w = 0.;
590 for &link_index in &graph.nodes[source].source_links {
591 let link = &graph.links[link_index];
592 let v = link.value as f32
593 * (graph.nodes[link.target].layer as f32
594 - graph.nodes[source].layer as f32);
595 y += source_top(graph, source, link.target, py) * v;
596 w += v;
597 }
598 if w <= 0. {
599 continue;
600 }
601 let dy = (y / w - graph.nodes[source].y0) * alpha;
602 graph.nodes[source].y0 += dy;
603 graph.nodes[source].y1 += dy;
604 reorder_node_links(graph, source);
605 }
606 sort_column(graph, &mut columns[i]);
607 self.resolve_collisions(graph, &columns[i], beta, py);
608 }
609 }
610
611 fn resolve_collisions(&self, graph: &mut SankeyGraph, column: &[usize], beta: f32, py: f32) {
614 if column.is_empty() {
615 return;
616 }
617
618 let i = column.len() >> 1;
619 let subject_y0 = graph.nodes[column[i]].y0;
620 let subject_y1 = graph.nodes[column[i]].y1;
621 push_up(graph, &column[..i], subject_y0 - py, beta, py);
622 push_down(graph, &column[i + 1..], subject_y1 + py, beta, py);
623 push_up(graph, column, self.y1, beta, py);
624 push_down(graph, column, self.y0, beta, py);
625 }
626}
627
628fn apply_layer_offsets(graph: &mut SankeyGraph, offsets: &[f32]) {
631 let node_offset: Vec<f32> = graph.nodes.iter().map(|n| offsets[n.layer]).collect();
634 for node in &mut graph.nodes {
635 let dy = node_offset[node.index];
636 node.y0 += dy;
637 node.y1 += dy;
638 }
639 for link in &mut graph.links {
640 link.y0 += node_offset[link.source];
641 link.y1 += node_offset[link.target];
642 }
643}
644
645fn compute_node_links(graph: &mut SankeyGraph) {
646 for index in 0..graph.links.len() {
647 let (source, target) = (graph.links[index].source, graph.links[index].target);
648 graph.nodes[source].source_links.push(index);
649 graph.nodes[target].target_links.push(index);
650 }
651}
652
653fn compute_node_values(graph: &mut SankeyGraph) {
654 for index in 0..graph.nodes.len() {
655 let outgoing: f64 = graph.nodes[index]
656 .source_links
657 .iter()
658 .map(|&link| graph.links[link].value)
659 .sum();
660 let incoming: f64 = graph.nodes[index]
661 .target_links
662 .iter()
663 .map(|&link| graph.links[link].value)
664 .sum();
665 graph.nodes[index].value = outgoing.max(incoming);
666 }
667}
668
669fn compute_node_ranks(graph: &mut SankeyGraph) -> Result<(), SankeyError> {
673 let n = graph.nodes.len();
674 let mut incoming: Vec<usize> = graph
675 .nodes
676 .iter()
677 .map(|node| node.target_links.len())
678 .collect();
679 let mut order: Vec<usize> = (0..n).filter(|&index| incoming[index] == 0).collect();
681
682 let mut depths = vec![0usize; n];
686 let mut visited = 0;
687 while visited < order.len() {
688 let index = order[visited];
689 visited += 1;
690 let depth = depths[index] + 1;
691 for &link in &graph.nodes[index].source_links {
692 let target = graph.links[link].target;
693 depths[target] = depths[target].max(depth);
694 incoming[target] -= 1;
695 if incoming[target] == 0 {
696 order.push(target);
697 }
698 }
699 }
700 if order.len() != n {
701 return Err(SankeyError::CircularLink);
702 }
703
704 let mut heights = vec![0usize; n];
707 for &index in order.iter().rev() {
708 for &link in &graph.nodes[index].source_links {
709 heights[index] = heights[index].max(heights[graph.links[link].target] + 1);
710 }
711 }
712
713 for (node, (depth, height)) in graph.nodes.iter_mut().zip(depths.into_iter().zip(heights)) {
714 node.depth = depth;
715 node.height = height;
716 }
717 Ok(())
718}
719
720fn sort_source_links(graph: &mut SankeyGraph, index: usize) {
722 let mut links = std::mem::take(&mut graph.nodes[index].source_links);
723 links.sort_unstable_by(|&a, &b| {
726 let ya = graph.nodes[graph.links[a].target].y0;
727 let yb = graph.nodes[graph.links[b].target].y0;
728 ya.partial_cmp(&yb)
729 .unwrap_or(std::cmp::Ordering::Equal)
730 .then(a.cmp(&b))
731 });
732 graph.nodes[index].source_links = links;
733}
734
735fn sort_target_links(graph: &mut SankeyGraph, index: usize) {
737 let mut links = std::mem::take(&mut graph.nodes[index].target_links);
738 links.sort_unstable_by(|&a, &b| {
739 let ya = graph.nodes[graph.links[a].source].y0;
740 let yb = graph.nodes[graph.links[b].source].y0;
741 ya.partial_cmp(&yb)
742 .unwrap_or(std::cmp::Ordering::Equal)
743 .then(a.cmp(&b))
744 });
745 graph.nodes[index].target_links = links;
746}
747
748fn reorder_node_links(graph: &mut SankeyGraph, index: usize) {
754 for i in 0..graph.nodes[index].target_links.len() {
755 let link = graph.nodes[index].target_links[i];
756 let source = graph.links[link].source;
757 sort_source_links(graph, source);
758 }
759 for i in 0..graph.nodes[index].source_links.len() {
760 let link = graph.nodes[index].source_links[i];
761 let target = graph.links[link].target;
762 sort_target_links(graph, target);
763 }
764}
765
766fn sort_column(graph: &SankeyGraph, column: &mut [usize]) {
767 column.sort_by(|&a, &b| {
768 graph.nodes[a]
769 .y0
770 .partial_cmp(&graph.nodes[b].y0)
771 .unwrap_or(std::cmp::Ordering::Equal)
772 });
773}
774
775fn push_down(graph: &mut SankeyGraph, column: &[usize], mut y: f32, alpha: f32, py: f32) {
777 for &index in column {
778 let node = &mut graph.nodes[index];
779 let dy = (y - node.y0) * alpha;
780 if dy > 1e-6 {
781 node.y0 += dy;
782 node.y1 += dy;
783 }
784 y = node.y1 + py;
785 }
786}
787
788fn push_up(graph: &mut SankeyGraph, column: &[usize], mut y: f32, alpha: f32, py: f32) {
790 for &index in column.iter().rev() {
791 let node = &mut graph.nodes[index];
792 let dy = (node.y1 - y) * alpha;
793 if dy > 1e-6 {
794 node.y0 -= dy;
795 node.y1 -= dy;
796 }
797 y = node.y0 - py;
798 }
799}
800
801fn target_top(graph: &SankeyGraph, source: usize, target: usize, py: f32) -> f32 {
805 let source_node = &graph.nodes[source];
806 let mut y = source_node.y0 - source_node.source_links.len().saturating_sub(1) as f32 * py / 2.;
807 for &link_index in &source_node.source_links {
808 let link = &graph.links[link_index];
809 if link.target == target {
810 break;
811 }
812 y += link.width + py;
813 }
814 for &link_index in &graph.nodes[target].target_links {
815 let link = &graph.links[link_index];
816 if link.source == source {
817 break;
818 }
819 y -= link.width;
820 }
821 y
822}
823
824fn source_top(graph: &SankeyGraph, source: usize, target: usize, py: f32) -> f32 {
828 let target_node = &graph.nodes[target];
829 let mut y = target_node.y0 - target_node.target_links.len().saturating_sub(1) as f32 * py / 2.;
830 for &link_index in &target_node.target_links {
831 let link = &graph.links[link_index];
832 if link.source == source {
833 break;
834 }
835 y += link.width + py;
836 }
837 for &link_index in &graph.nodes[source].source_links {
838 let link = &graph.links[link_index];
839 if link.target == target {
840 break;
841 }
842 y -= link.width;
843 }
844 y
845}
846
847fn compute_link_breadths(graph: &mut SankeyGraph) {
853 for index in 0..graph.nodes.len() {
854 let node = &graph.nodes[index];
855 let node_y0 = node.y0;
856 let node_height = node.y1 - node.y0;
857
858 let outgoing: f64 = node
859 .source_links
860 .iter()
861 .map(|&link| graph.links[link].value)
862 .sum();
863 let mut y0 = node_y0;
864 for i in 0..graph.nodes[index].source_links.len() {
865 let link = &mut graph.links[graph.nodes[index].source_links[i]];
866 let width = if outgoing > 0. {
867 (link.value / outgoing) as f32 * node_height
868 } else {
869 0.
870 };
871 link.source_width = width;
872 link.y0 = y0 + width / 2.;
873 y0 += width;
874 }
875
876 let node = &graph.nodes[index];
877 let incoming: f64 = node
878 .target_links
879 .iter()
880 .map(|&link| graph.links[link].value)
881 .sum();
882 let mut y1 = node_y0;
883 for i in 0..graph.nodes[index].target_links.len() {
884 let link = &mut graph.links[graph.nodes[index].target_links[i]];
885 let width = if incoming > 0. {
886 (link.value / incoming) as f32 * node_height
887 } else {
888 0.
889 };
890 link.target_width = width;
891 link.y1 = y1 + width / 2.;
892 y1 += width;
893 }
894 }
895}
896
897pub fn sankey_link_path(
902 source: &SankeyNodeLayout,
903 target: &SankeyNodeLayout,
904 link: &SankeyLinkLayout,
905 min_width: f32,
906 origin: Point<Pixels>,
907) -> Option<Path<Pixels>> {
908 let source_half = link.source_width.max(min_width) / 2.;
909 let target_half = link.target_width.max(min_width) / 2.;
910 let sx = source.x1;
911 let tx = target.x0;
912 let mx = (sx + tx) / 2.;
913
914 let mut builder = PathBuilder::fill();
915 builder.move_to(origin_point(px(sx), px(link.y0 - source_half), origin));
916 builder.cubic_bezier_to(
917 origin_point(px(tx), px(link.y1 - target_half), origin),
918 origin_point(px(mx), px(link.y0 - source_half), origin),
919 origin_point(px(mx), px(link.y1 - target_half), origin),
920 );
921 builder.line_to(origin_point(px(tx), px(link.y1 + target_half), origin));
922 builder.cubic_bezier_to(
923 origin_point(px(sx), px(link.y0 + source_half), origin),
924 origin_point(px(mx), px(link.y1 + target_half), origin),
925 origin_point(px(mx), px(link.y0 + source_half), origin),
926 );
927 builder.close();
928 builder.build().ok()
929}
930
931#[cfg(test)]
932mod tests {
933 use super::*;
934
935 const EPSILON: f32 = 1e-3;
936
937 fn links(links: &[(usize, usize, f64)]) -> Vec<SankeyLink> {
938 links
939 .iter()
940 .map(|&(source, target, value)| SankeyLink::new(source, target, value))
941 .collect()
942 }
943
944 #[test]
945 fn test_sankey_builder() {
946 let sankey = Sankey::new();
947 assert_eq!(sankey.node_width, 24.);
948 assert_eq!(sankey.node_padding, 8.);
949 assert_eq!(sankey.align, SankeyAlign::Justify);
950 assert_eq!(sankey.iterations, 6);
951 assert_eq!(
952 (sankey.x0, sankey.y0, sankey.x1, sankey.y1),
953 (0., 0., 1., 1.)
954 );
955
956 let sankey = Sankey::new()
957 .node_width(12.)
958 .node_padding(10.)
959 .node_align(SankeyAlign::Left)
960 .iterations(10)
961 .size(400., 300.);
962 assert_eq!(sankey.node_width, 12.);
963 assert_eq!(sankey.node_padding, 10.);
964 assert_eq!(sankey.align, SankeyAlign::Left);
965 assert_eq!(sankey.iterations, 10);
966 assert_eq!(
967 (sankey.x0, sankey.y0, sankey.x1, sankey.y1),
968 (0., 0., 400., 300.)
969 );
970
971 let sankey = Sankey::new().extent(10., 20., 30., 40.);
972 assert_eq!(
973 (sankey.x0, sankey.y0, sankey.x1, sankey.y1),
974 (10., 20., 30., 40.)
975 );
976 }
977
978 #[test]
979 fn test_sankey_layout_chain() {
980 let graph = Sankey::new()
982 .node_width(10.)
983 .size(100., 100.)
984 .layout(3, &links(&[(0, 1, 5.), (1, 2, 5.)]))
985 .unwrap();
986
987 let depths: Vec<usize> = graph.nodes.iter().map(|n| n.depth).collect();
988 let heights: Vec<usize> = graph.nodes.iter().map(|n| n.height).collect();
989 let layers: Vec<usize> = graph.nodes.iter().map(|n| n.layer).collect();
990 assert_eq!(depths, vec![0, 1, 2]);
991 assert_eq!(heights, vec![2, 1, 0]);
992 assert_eq!(layers, vec![0, 1, 2]);
993 assert_eq!(graph.layer_count(), 3);
994
995 assert_eq!(graph.nodes[0].x0, 0.);
996 assert_eq!(graph.nodes[1].x0, 45.);
997 assert_eq!(graph.nodes[2].x0, 90.);
998 for node in &graph.nodes {
999 assert_eq!(node.x1 - node.x0, 10.);
1000 assert_eq!(node.value, 5.);
1001 assert!((node.y1 - node.y0 - 100.).abs() < EPSILON);
1003 }
1004 for link in &graph.links {
1005 assert!((link.width - 100.).abs() < EPSILON);
1006 assert!((link.source_width - 100.).abs() < EPSILON);
1008 assert!((link.target_width - 100.).abs() < EPSILON);
1009 }
1010
1011 let topology = Sankey::new()
1013 .node_width(10.)
1014 .size(100., 100.)
1015 .topology(3, &links(&[(0, 1, 5.), (1, 2, 5.)]))
1016 .unwrap();
1017 assert_eq!(topology.layer_count(), 3);
1018 for (a, b) in topology.nodes.iter().zip(&graph.nodes) {
1019 assert_eq!(a.depth, b.depth);
1020 assert_eq!(a.height, b.height);
1021 assert_eq!(a.layer, b.layer);
1022 assert_eq!(a.value, b.value);
1023 assert_eq!(a.x0, b.x0);
1024 }
1025
1026 let topology = Sankey::new()
1029 .node_width(10.)
1030 .topology(3, &links(&[(0, 1, 5.), (1, 2, 5.)]))
1031 .unwrap();
1032 let completed = Sankey::new()
1033 .node_width(10.)
1034 .size(100., 100.)
1035 .layout_from(topology);
1036 for (a, b) in completed.nodes.iter().zip(&graph.nodes) {
1037 assert_eq!((a.x0, a.y0, a.x1, a.y1), (b.x0, b.y0, b.x1, b.y1));
1038 }
1039 for (a, b) in completed.links.iter().zip(&graph.links) {
1040 assert_eq!((a.y0, a.y1, a.width), (b.y0, b.y1, b.width));
1041 }
1042 }
1043
1044 #[test]
1045 fn test_sankey_topology_large_chain() {
1046 const NODE_COUNT: usize = 50_000;
1047 let links: Vec<SankeyLink> = (0..NODE_COUNT - 1)
1048 .map(|source| SankeyLink::new(source, source + 1, 1.))
1049 .collect();
1050 let graph = Sankey::new().topology(NODE_COUNT, &links).unwrap();
1051
1052 assert_eq!(graph.nodes[0].height, NODE_COUNT - 1);
1053 assert_eq!(graph.nodes[NODE_COUNT - 1].depth, NODE_COUNT - 1);
1054 }
1055
1056 #[test]
1057 fn test_sankey_alignment() {
1058 let links = links(&[(0, 1, 1.), (1, 2, 1.), (0, 3, 1.)]);
1060 let layers = |align: SankeyAlign| -> Vec<usize> {
1061 Sankey::new()
1062 .node_align(align)
1063 .size(100., 100.)
1064 .layout(4, &links)
1065 .unwrap()
1066 .nodes
1067 .iter()
1068 .map(|n| n.layer)
1069 .collect()
1070 };
1071
1072 assert_eq!(layers(SankeyAlign::Left), vec![0, 1, 2, 1]);
1073 assert_eq!(layers(SankeyAlign::Right), vec![0, 1, 2, 2]);
1074 assert_eq!(layers(SankeyAlign::Justify), vec![0, 1, 2, 2]);
1075 assert_eq!(layers(SankeyAlign::Center), vec![0, 1, 2, 1]);
1076 }
1077
1078 #[test]
1079 fn test_sankey_link_offsets() {
1080 let graph = Sankey::new()
1082 .node_width(10.)
1083 .size(100., 100.)
1084 .layout(3, &links(&[(0, 1, 30.), (0, 2, 10.)]))
1085 .unwrap();
1086
1087 let source = &graph.nodes[0];
1088 let source_height = source.y1 - source.y0;
1089 let total_width: f32 = graph.links.iter().map(|l| l.width).sum();
1090 assert!((total_width - source_height).abs() < EPSILON);
1091
1092 assert!((graph.links[0].width / graph.links[1].width - 3.).abs() < EPSILON);
1094
1095 let (first, second) = if graph.links[0].y0 < graph.links[1].y0 {
1097 (&graph.links[0], &graph.links[1])
1098 } else {
1099 (&graph.links[1], &graph.links[0])
1100 };
1101 assert!((first.y0 - first.source_width / 2. - source.y0).abs() < EPSILON);
1102 assert!(
1103 (first.y0 + first.source_width / 2. - (second.y0 - second.source_width / 2.)).abs()
1104 < EPSILON
1105 );
1106
1107 for link in &graph.links {
1109 let target = &graph.nodes[link.target];
1110 assert!((link.y1 - link.target_width / 2. - target.y0).abs() < EPSILON);
1111 assert!((link.y1 + link.target_width / 2. - target.y1).abs() < EPSILON);
1112 }
1113 }
1114
1115 #[test]
1116 fn test_sankey_imbalanced_link_widths() {
1117 let graph = Sankey::new()
1121 .size(100., 100.)
1122 .layout(3, &links(&[(0, 1, 10.), (1, 2, 7.)]))
1123 .unwrap();
1124
1125 let node_b = &graph.nodes[1];
1126 let node_c = &graph.nodes[2];
1127 let out_link = &graph.links[1];
1128 assert!((out_link.source_width - (node_b.y1 - node_b.y0)).abs() < EPSILON);
1129 assert!((out_link.target_width - (node_c.y1 - node_c.y0)).abs() < EPSILON);
1130 assert!(out_link.source_width > out_link.target_width);
1132
1133 assert!((out_link.y0 - (node_b.y0 + node_b.y1) / 2.).abs() < EPSILON);
1135 assert!((out_link.y1 - (node_c.y0 + node_c.y1) / 2.).abs() < EPSILON);
1136 }
1137
1138 #[test]
1139 fn test_sankey_sqrt_scale_fills_nodes() {
1140 let links = links(&[(0, 1, 90.), (1, 2, 40.), (1, 3, 50.)]);
1144 let sqrt = Sankey::new()
1145 .value_scale(SankeyValueScale::Sqrt)
1146 .size(100., 100.)
1147 .layout(4, &links)
1148 .unwrap();
1149
1150 for node in &sqrt.nodes {
1151 let node_height = node.y1 - node.y0;
1152 let incoming: f32 = node
1153 .target_links
1154 .iter()
1155 .map(|&l| sqrt.links[l].target_width)
1156 .sum();
1157 let outgoing: f32 = node
1158 .source_links
1159 .iter()
1160 .map(|&l| sqrt.links[l].source_width)
1161 .sum();
1162 if !node.target_links.is_empty() {
1163 assert!((incoming - node_height).abs() < EPSILON);
1164 }
1165 if !node.source_links.is_empty() {
1166 assert!((outgoing - node_height).abs() < EPSILON);
1167 }
1168 }
1169
1170 let ratio = (sqrt.nodes[3].y1 - sqrt.nodes[3].y0) / (sqrt.nodes[2].y1 - sqrt.nodes[2].y0);
1173 assert!((ratio - (50f32 / 40.).sqrt()).abs() < 0.02);
1174 }
1175
1176 #[test]
1177 fn test_sankey_value_conservation() {
1178 let graph = Sankey::new()
1180 .size(100., 100.)
1181 .layout(3, &links(&[(0, 1, 10.), (1, 2, 7.)]))
1182 .unwrap();
1183
1184 assert_eq!(graph.nodes[1].value, 10.);
1185
1186 for node in &graph.nodes {
1187 assert!(node.y0 <= node.y1);
1188 assert!(node.y0 >= -EPSILON);
1189 assert!(node.y1 <= 100. + EPSILON);
1190 }
1191 }
1192
1193 #[test]
1194 fn test_sankey_vertical_centering() {
1195 let graph = Sankey::new()
1199 .node_padding(20.)
1200 .extent(0., 10., 100., 90.)
1201 .layout(
1202 5,
1203 &links(&[(0, 2, 40.), (1, 2, 10.), (2, 3, 25.), (2, 4, 25.)]),
1204 )
1205 .unwrap();
1206
1207 let layers = graph.layer_count();
1208 let mut lo = vec![f32::INFINITY; layers];
1209 let mut hi = vec![f32::NEG_INFINITY; layers];
1210 for node in &graph.nodes {
1211 lo[node.layer] = lo[node.layer].min(node.y0);
1212 hi[node.layer] = hi[node.layer].max(node.y1);
1213 }
1214 for l in 0..layers {
1216 assert!(((lo[l] + hi[l]) / 2. - 50.).abs() < EPSILON);
1217 }
1218 for node in &graph.nodes {
1220 assert!(node.y0 >= 10. - EPSILON);
1221 assert!(node.y1 <= 90. + EPSILON);
1222 }
1223 }
1224
1225 #[test]
1226 fn test_sankey_stagger_flat_columns() {
1227 let graph = Sankey::new()
1231 .node_padding(20.)
1232 .size(100., 100.)
1233 .layout(
1234 6,
1235 &links(&[
1236 (0, 1, 100.),
1237 (1, 2, 40.),
1238 (1, 3, 30.),
1239 (1, 4, 20.),
1240 (1, 5, 10.),
1241 ]),
1242 )
1243 .unwrap();
1244
1245 let c0 = (graph.nodes[0].y0 + graph.nodes[0].y1) / 2.;
1249 let c1 = (graph.nodes[1].y0 + graph.nodes[1].y1) / 2.;
1250 assert!((c0 - c1).abs() > EPSILON);
1251 for node in &graph.nodes {
1252 assert!(node.y0 >= -EPSILON);
1253 assert!(node.y1 <= 100. + EPSILON);
1254 }
1255
1256 for node in &graph.nodes {
1261 let mut y = node.y0;
1262 for &l in &node.source_links {
1263 let link = &graph.links[l];
1264 assert!((link.y0 - (y + link.source_width / 2.)).abs() < EPSILON);
1265 y += link.source_width;
1266 }
1267 let mut y = node.y0;
1268 for &l in &node.target_links {
1269 let link = &graph.links[l];
1270 assert!((link.y1 - (y + link.target_width / 2.)).abs() < EPSILON);
1271 y += link.target_width;
1272 }
1273 }
1274 }
1275
1276 #[test]
1277 fn test_sankey_circular_link() {
1278 let sankey = Sankey::new().size(100., 100.);
1279
1280 assert_eq!(
1281 sankey
1282 .layout(2, &links(&[(0, 1, 1.), (1, 0, 1.)]))
1283 .unwrap_err(),
1284 SankeyError::CircularLink
1285 );
1286 assert_eq!(
1287 sankey.layout(1, &links(&[(0, 0, 1.)])).unwrap_err(),
1288 SankeyError::CircularLink
1289 );
1290 assert_eq!(
1291 sankey.layout(2, &links(&[(0, 5, 1.)])).unwrap_err(),
1292 SankeyError::MissingNode(5)
1293 );
1294 }
1295
1296 #[test]
1297 fn test_sankey_degenerate() {
1298 let graph = Sankey::new().size(100., 100.).layout(0, &[]).unwrap();
1300 assert!(graph.nodes.is_empty());
1301 assert!(graph.links.is_empty());
1302 assert_eq!(graph.layer_count(), 0);
1303
1304 let graph = Sankey::new()
1306 .size(100., 100.)
1307 .layout(2, &links(&[(0, 1, 0.)]))
1308 .unwrap();
1309 for node in &graph.nodes {
1310 assert!((node.y1 - node.y0).abs() < EPSILON);
1311 assert!(node.x0.is_finite() && node.x1.is_finite());
1312 assert!(node.y0.is_finite() && node.y1.is_finite());
1313 }
1314
1315 let graph = Sankey::new().size(100., 100.).layout(2, &[]).unwrap();
1317 assert_eq!(graph.layer_count(), 1);
1318 for node in &graph.nodes {
1319 assert_eq!(node.x0, 0.);
1320 assert!(node.y0.is_finite() && node.y1.is_finite());
1321 }
1322 }
1323}