use kuva::backend::svg::SvgBackend;
use kuva::plot::venn::VennPlot;
use kuva::render::{layout::Layout, plots::Plot, render::render_multiple};
use std::fs;
fn write_venn(name: &str, venn: VennPlot, title: &str) -> String {
let plots = vec![Plot::Venn(venn)];
let layout = Layout::auto_from_plots(&plots).with_title(title);
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
fs::create_dir_all("test_outputs").unwrap();
fs::write(format!("test_outputs/venn_{name}.svg"), &svg).unwrap();
svg
}
fn circle_count(svg: &str) -> usize {
svg.matches("<circle").count()
}
#[test]
fn test_venn_2set_basic() {
let venn = VennPlot::new()
.with_set_size("Set A", 100)
.with_set_size("Set B", 80)
.with_overlap(["Set A", "Set B"], 30);
write_venn("2set_basic", venn, "2-Set Venn Diagram");
}
#[test]
fn test_venn_2set_proportional() {
let venn = VennPlot::new()
.with_set_size("Large", 200)
.with_set_size("Small", 50)
.with_overlap(["Large", "Small"], 20)
.with_proportional(true)
.with_loss(true);
write_venn("2set_proportional", venn, "2-Set Proportional Venn");
}
#[test]
fn test_venn_3set_basic() {
let venn = VennPlot::new()
.with_set_size("A", 120)
.with_set_size("B", 100)
.with_set_size("C", 90)
.with_overlap(["A", "B"], 35)
.with_overlap(["A", "C"], 25)
.with_overlap(["B", "C"], 30)
.with_overlap(["A", "B", "C"], 10);
write_venn("3set_basic", venn, "3-Set Venn Diagram");
}
#[test]
fn test_venn_3set_with_percentages() {
let venn = VennPlot::new()
.with_set_size("X", 150)
.with_set_size("Y", 120)
.with_set_size("Z", 100)
.with_overlap(["X", "Y"], 45)
.with_overlap(["X", "Z"], 30)
.with_overlap(["Y", "Z"], 40)
.with_overlap(["X", "Y", "Z"], 15)
.with_counts(true)
.with_percentages(true);
write_venn("3set_percentages", venn, "3-Set Venn with Percentages");
}
#[test]
fn test_venn_3set_proportional() {
let venn = VennPlot::new()
.with_set_size("A", 300)
.with_set_size("B", 150)
.with_set_size("C", 200)
.with_overlap(["A", "B"], 50)
.with_overlap(["A", "C"], 80)
.with_overlap(["B", "C"], 40)
.with_overlap(["A", "B", "C"], 20)
.with_proportional(true);
write_venn("3set_proportional", venn, "3-Set Proportional Venn");
}
#[test]
fn test_venn_4set_basic() {
let venn = VennPlot::new()
.with_set_size("W", 200)
.with_set_size("X", 180)
.with_set_size("Y", 160)
.with_set_size("Z", 140)
.with_overlap(["W", "X"], 60)
.with_overlap(["W", "Y"], 50)
.with_overlap(["W", "Z"], 40)
.with_overlap(["X", "Y"], 55)
.with_overlap(["X", "Z"], 45)
.with_overlap(["Y", "Z"], 35)
.with_overlap(["W", "X", "Y"], 20)
.with_overlap(["W", "X", "Z"], 15)
.with_overlap(["W", "Y", "Z"], 12)
.with_overlap(["X", "Y", "Z"], 18)
.with_overlap(["W", "X", "Y", "Z"], 5)
.with_legend("Sets");
write_venn("4set_basic", venn, "4-Set Venn Diagram");
}
#[test]
fn test_venn_raw_elements() {
let venn = VennPlot::new()
.with_set("A", vec!["a", "b", "c", "d", "e"])
.with_set("B", vec!["c", "d", "e", "f", "g"])
.with_set("C", vec!["e", "f", "g", "h", "i"]);
write_venn("raw_elements", venn, "Venn from Raw Elements");
}
#[test]
fn test_venn_gene_lists() {
let deseq2 = vec!["BRCA1", "TP53", "MYC", "EGFR", "VEGFA", "CDKN2A", "KRAS"];
let edger = vec!["TP53", "MYC", "KRAS", "PIK3CA", "PTEN", "RB1"];
let limma = vec!["BRCA1", "MYC", "EGFR", "PIK3CA", "CDKN2A", "MDM2"];
let venn = VennPlot::new()
.with_set("DESeq2", deseq2.iter().map(|s| s.to_string()).collect())
.with_set("edgeR", edger.iter().map(|s| s.to_string()).collect())
.with_set("limma", limma.iter().map(|s| s.to_string()).collect())
.with_percentages(true);
write_venn("gene_lists", venn, "DE Gene List Overlap");
}
#[test]
fn test_venn_precomputed_sizes() {
let venn = VennPlot::new()
.with_set_size("Group 1", 500)
.with_set_size("Group 2", 400)
.with_overlap(["Group 1", "Group 2"], 100)
.with_counts(true)
.with_percentages(true)
.with_colors(["steelblue", "tomato"]);
write_venn("precomputed_sizes", venn, "Pre-computed Size Venn");
}
#[test]
fn test_venn_legend() {
let venn = VennPlot::new()
.with_set_size("Control", 300)
.with_set_size("Treatment A", 250)
.with_set_size("Treatment B", 220)
.with_overlap(["Control", "Treatment A"], 80)
.with_overlap(["Control", "Treatment B"], 65)
.with_overlap(["Treatment A", "Treatment B"], 90)
.with_overlap(["Control", "Treatment A", "Treatment B"], 30)
.with_legend("Experimental Groups");
write_venn("legend", venn, "Venn Diagram with Legend");
}
#[test]
fn test_venn_2set_leader_lines() {
let venn = VennPlot::new()
.with_set_size("Set A", 100)
.with_set_size("Set B", 80)
.with_overlap(["Set A", "Set B"], 30)
.with_leader_lines(true);
write_venn("2set_leader_lines", venn, "2-Set Venn with Leader Lines");
}
#[test]
fn test_venn_3set_leader_lines() {
let venn = VennPlot::new()
.with_set_size("A", 120)
.with_set_size("B", 100)
.with_set_size("C", 90)
.with_overlap(["A", "B"], 35)
.with_overlap(["A", "C"], 25)
.with_overlap(["B", "C"], 30)
.with_overlap(["A", "B", "C"], 10)
.with_leader_lines(true);
write_venn("3set_leader_lines", venn, "3-Set Venn with Leader Lines");
}
#[test]
fn test_venn_4set_leader_lines() {
let venn = VennPlot::new()
.with_set_size("W", 200)
.with_set_size("X", 180)
.with_set_size("Y", 160)
.with_set_size("Z", 140)
.with_overlap(["W", "X"], 60)
.with_overlap(["W", "Y"], 50)
.with_overlap(["W", "Z"], 40)
.with_overlap(["X", "Y"], 55)
.with_overlap(["X", "Z"], 45)
.with_overlap(["Y", "Z"], 35)
.with_overlap(["W", "X", "Y"], 20)
.with_overlap(["W", "X", "Z"], 15)
.with_overlap(["W", "Y", "Z"], 12)
.with_overlap(["X", "Y", "Z"], 18)
.with_overlap(["W", "X", "Y", "Z"], 5)
.with_leader_lines(true)
.with_legend("Sets");
write_venn("4set_leader_lines", venn, "4-Set Venn with Leader Lines");
}
#[test]
fn test_venn_set_indicators_on_by_default() {
let venn = VennPlot::new()
.with_set_size("A", 120)
.with_set_size("B", 100)
.with_set_size("C", 90)
.with_overlap(["A", "B"], 35)
.with_overlap(["A", "C"], 25)
.with_overlap(["B", "C"], 30)
.with_overlap(["A", "B", "C"], 10);
let svg = write_venn("set_indicators_on", venn, "Set Indicators On (default)");
assert!(
circle_count(&svg) > 3,
"Expected indicator dots in addition to shape circles"
);
}
#[test]
fn test_venn_set_indicators_off() {
let venn = VennPlot::new()
.with_set_size("A", 120)
.with_set_size("B", 100)
.with_set_size("C", 90)
.with_overlap(["A", "B"], 35)
.with_overlap(["A", "C"], 25)
.with_overlap(["B", "C"], 30)
.with_overlap(["A", "B", "C"], 10)
.with_set_indicators(false);
let svg = write_venn("set_indicators_off", venn, "Set Indicators Off");
assert_eq!(
circle_count(&svg),
3,
"Only 3 shape circles expected when indicators are off"
);
}
#[test]
fn test_venn_set_indicators_2set() {
let venn = VennPlot::new()
.with_set_size("X", 80)
.with_set_size("Y", 70)
.with_overlap(["X", "Y"], 25);
let svg = write_venn("set_indicators_2set", venn, "2-Set with Indicators");
assert!(
circle_count(&svg) >= 6,
"Expected 2 shape circles + 4 indicator dots"
);
}
#[test]
fn test_venn_set_indicators_leader_lines_off() {
let venn = VennPlot::new()
.with_set_size("W", 200)
.with_set_size("X", 180)
.with_set_size("Y", 160)
.with_set_size("Z", 140)
.with_overlap(["W", "X"], 60)
.with_overlap(["W", "Y"], 50)
.with_overlap(["W", "Z"], 40)
.with_overlap(["X", "Y"], 55)
.with_overlap(["X", "Z"], 45)
.with_overlap(["Y", "Z"], 35)
.with_overlap(["W", "X", "Y"], 20)
.with_overlap(["W", "X", "Z"], 15)
.with_overlap(["W", "Y", "Z"], 12)
.with_overlap(["X", "Y", "Z"], 18)
.with_overlap(["W", "X", "Y", "Z"], 5)
.with_leader_lines(true)
.with_set_indicators(false);
let svg = write_venn(
"set_indicators_leader_off",
venn,
"Leader Lines, No Indicators",
);
assert_eq!(
circle_count(&svg),
0,
"No circles expected: 4-set shapes are paths and indicators are off"
);
}
#[test]
fn test_venn_loss_2set_proportional() {
let venn = VennPlot::new()
.with_set_size("Large", 500)
.with_set_size("Small", 100)
.with_overlap(["Large", "Small"], 40)
.with_proportional(true)
.with_loss(true);
let svg = write_venn("loss_2set", venn, "2-Set Proportional with Stress");
assert!(svg.contains("Layout stress"), "Stress box missing from SVG");
}
#[test]
fn test_venn_loss_3set_proportional() {
let venn = VennPlot::new()
.with_set_size("A", 300)
.with_set_size("B", 200)
.with_set_size("C", 150)
.with_overlap(["A", "B"], 80)
.with_overlap(["A", "C"], 50)
.with_overlap(["B", "C"], 40)
.with_overlap(["A", "B", "C"], 20)
.with_proportional(true)
.with_loss(true);
let svg = write_venn("loss_3set", venn, "3-Set Proportional with Stress");
assert!(svg.contains("Layout stress"), "Stress box missing from SVG");
}
#[test]
fn test_venn_loss_not_shown_without_flag() {
let venn = VennPlot::new()
.with_set_size("A", 200)
.with_set_size("B", 150)
.with_overlap(["A", "B"], 50)
.with_proportional(true);
let svg = write_venn("loss_not_shown", venn, "Proportional, No Stress Label");
assert!(
!svg.contains("Layout stress"),
"Stress box should not appear when with_loss is false"
);
}
#[test]
fn test_venn_loss_perfect_circles() {
let venn = VennPlot::new()
.with_set_size("A", 100)
.with_set_size("B", 100)
.with_overlap(["A", "B"], 0)
.with_proportional(true)
.with_loss(true);
let svg = write_venn(
"loss_no_overlap",
venn,
"Equal Circles, No Overlap, with Stress",
);
assert!(svg.contains("Layout stress"), "Stress box missing");
}