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