use kuva::backend::svg::SvgBackend;
use kuva::plot::brick::{BrickAnchor, BrickTemplate};
use kuva::plot::BrickPlot;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
use kuva::render::render::render_multiple;
#[test]
fn test_brickplot_svg_output_builder() {
let sequences: Vec<String> = vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATCATCATCATCATGGTCATCATCATCATCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
];
let names: Vec<String> = vec![
"read_1".to_string(),
"read_2".to_string(),
"read_3".to_string(),
"read_4".to_string(),
"read_5".to_string(),
"read_6".to_string(),
"read_7".to_string(),
"read_8".to_string(),
];
let colours = BrickTemplate::new();
let b = colours.dna().clone();
let brickplot = BrickPlot::new()
.with_sequences(sequences)
.with_names(names)
.with_template(b.template)
.with_x_offset(18.0);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BrickPlot - DNA");
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_DNA_builder.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
}
#[test]
fn test_brickplot_per_read_offsets() {
let sequences: Vec<String> = vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCAT".to_string(), "GCACTCATCATCATCATCATCATCATCATCATCAT".to_string(), "ATCAGGCCGCACTCATCATCATCATCATCATCATCATCAT".to_string(), "CACTCATCATCATCATCATCAT".to_string(), ];
let names: Vec<String> = vec![
"read_1".to_string(),
"read_2".to_string(),
"read_3".to_string(),
"read_4".to_string(),
];
let colours = BrickTemplate::new();
let b = colours.dna();
let brickplot = BrickPlot::new()
.with_sequences(sequences)
.with_names(names)
.with_template(b.template)
.with_x_offsets(vec![18.0, 10.0, 16.0, 5.0]);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BrickPlot - per-read offsets");
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_per_read_offsets.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
}
#[test]
fn test_brickplot_per_read_offsets_fallback() {
let sequences: Vec<String> = vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCAT".to_string(), "GCACTCATCATCATCATCATCATCATCATCATCAT".to_string(), "ATCAGGCCGCACTCATCATCATCATCATCATCATCATCAT".to_string(), "CACTCATCATCATCATCATCAT".to_string(), ];
let names: Vec<String> = vec![
"read_1".to_string(),
"read_2".to_string(),
"read_3".to_string(),
"read_4".to_string(),
];
let colours = BrickTemplate::new();
let b = colours.dna();
let brickplot = BrickPlot::new()
.with_sequences(sequences)
.with_names(names)
.with_template(b.template)
.with_x_offset(12.0)
.with_x_offsets(vec![Some(18.0), Some(10.0), None, Some(5.0_f64)]);
let plots = vec![Plot::Brick(brickplot)];
let layout =
Layout::auto_from_plots(&plots).with_title("BrickPlot - per-read offsets with fallback");
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write(
"test_outputs/brickplot_per_read_offsets_fallback.svg",
svg.clone(),
)
.unwrap();
assert!(svg.contains("<svg"));
}
#[test]
fn test_brickplot_strigar_svg_output_builder() {
let sequences: Vec<String> = vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATCATCATCATCATGGTCATCATCATCATCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATCAT".to_string(),
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCATCATCCATCATCATCATTCAT".to_string(),
];
let strigars: Vec<(String, String)> = vec![
("CAT:A,C:B,T:C".to_string(), "10A1B4A1C1A".to_string()),
("CAT:A,T:B".to_string(), "14A1B1A".to_string()),
("CAT:A,T:B".to_string(), "14A1B1A".to_string()),
("CAT:A,C:B,T:C".to_string(), "10A1B4A1C1A".to_string()),
("CAT:A,C:B,T:C".to_string(), "10A1B4A1C1A".to_string()),
("CAT:A,C:B,GGT:C".to_string(), "10A1B8A1C5A".to_string()),
("CAT:A,C:B".to_string(), "10A1B5A".to_string()),
("CAT:A,C:B,T:C".to_string(), "10A1B4A1C1A".to_string()),
];
let names: Vec<String> = vec![
"read_1".to_string(),
"read_2".to_string(),
"read_3".to_string(),
"read_4".to_string(),
"read_5".to_string(),
"read_6".to_string(),
"read_7".to_string(),
"read_8".to_string(),
];
let colours = BrickTemplate::new();
let b = colours.dna().clone();
let brickplot = BrickPlot::new()
.with_sequences(sequences)
.with_names(names)
.with_template(b.template)
.with_strigars(strigars)
.with_x_offset(18.0);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BrickPlot - strigar");
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_strigar_builder.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
}
#[test]
fn test_brick_legend_order() {
let sequences: Vec<String> = vec![
"CATCATCATCATCATCATCATCATCATCATT".to_string(),
"CATCATCATCATCATCATCATCATCATCATCATCAT".to_string(),
"CATCATCATCATCATCATCATCATT".to_string(),
];
let names: Vec<String> = vec!["r1".to_string(), "r2".to_string(), "r3".to_string()];
let strigars: Vec<(String, String)> = vec![
("CAT:A,T:B".to_string(), "10A1B1A".to_string()),
("CAT:A".to_string(), "12A".to_string()),
("CAT:A,T:B".to_string(), "8A1B1A".to_string()),
];
let brickplot = BrickPlot::new()
.with_sequences(sequences)
.with_names(names)
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_legend_order.svg", svg.clone()).unwrap();
let pos_cat = svg
.find(">CAT<")
.expect("legend should contain 'CAT' label");
let pos_t = svg.find(">T<").expect("legend should contain 'T' label");
assert!(
pos_cat < pos_t,
"legend entry 'CAT' (global letter A, most frequent) must appear before 'T' (global letter B)"
);
}
#[test]
fn test_brick_canonical_freq_counts_bricks_not_reads() {
let strigars: Vec<(String, String)> = vec![
("CAG:A,C:B".to_string(), "14A1B".to_string()), ("CAG:A,C:B".to_string(), "10A1B".to_string()), ("CAG:A,C:B".to_string(), "8A1B".to_string()), ];
let brickplot = BrickPlot::new()
.with_names(vec!["r1", "r2", "r3"])
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
let pos_cag = svg.find(">CAG<").expect("legend must contain 'CAG'");
let pos_c = svg.find(">C<").expect("legend must contain 'C'");
assert!(
pos_cag < pos_c,
"CAG (32 bricks) must be global letter A and appear before C (3 bricks) in the legend"
);
}
#[test]
fn test_brick_stitched_format_with_gaps() {
let strigars: Vec<(String, String)> = vec![
(
"A:A | @:GAA | AGA:B".to_string(),
"16A | 1@ | 9B".to_string(),
),
("AGA:A".to_string(), "12A".to_string()),
];
let brickplot = BrickPlot::new()
.with_names(vec!["read_1", "read_2"])
.with_strigars(strigars)
.with_x_origin(19.0)
.with_start_positions(vec![0.0_f64, 19.0]);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_stitched_gaps.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
assert!(svg.contains("#c8c8c8"), "gap bricks should use grey color");
}
#[test]
fn test_brick_flanked_strigars() {
let flanked = vec![
("ACGTACGT", "CAG:A,C:B", "12A1B", "TGCATGCA"),
("ACGTACGT", "CAG:A,C:B", "10A1B", "TGCATGCA"),
("ACGT", "CAG:A", "8A", "TGCA"),
];
let brickplot = BrickPlot::new()
.with_names(vec!["consensus", "read_1", "read_2"])
.with_flanked_strigars(flanked);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BrickPlot - flanked strigars");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("test_outputs/brickplot_flanked.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
assert!(
svg.contains("#009600"),
"DNA A colour should appear in left/right flanks"
);
assert!(
svg.contains("#1f77b4"),
"primary STR motif should use the default first palette colour"
);
}
#[test]
fn test_brick_right_anchor() {
let strigars: Vec<(String, String)> = vec![
("CAG:A".to_string(), "14A".to_string()),
("CAG:A".to_string(), "10A".to_string()),
("CAG:A".to_string(), "8A".to_string()),
];
let brickplot = BrickPlot::new()
.with_names(vec!["r1", "r2", "r3"])
.with_anchor(BrickAnchor::Right)
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BrickPlot - right anchor");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("test_outputs/brickplot_right_anchor.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
}
#[test]
fn test_brick_mark_primary() {
let strigars: Vec<(String, String)> = vec![
("CAG:A,C:B".to_string(), "12A1B".to_string()),
("CAG:A,C:B".to_string(), "10A1B".to_string()),
];
let brickplot = BrickPlot::new()
.with_names(vec!["r1", "r2"])
.with_mark_primary()
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
assert!(
svg.contains(">CAG*<"),
"primary motif label must end with '*'"
);
assert!(!svg.contains(">C*<"), "non-primary motif must not have '*'");
}
#[test]
fn test_brick_consensus_row() {
let strigars: Vec<(String, String)> = vec![
("CAG:A".to_string(), "12A".to_string()), ("AGC:A".to_string(), "10A".to_string()), ("GCA:A".to_string(), "8A".to_string()), ];
let brickplot = BrickPlot::new()
.with_names(vec!["consensus", "read_1", "read_2"])
.with_consensus_row(0)
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
assert!(
svg.contains(">CAG<"),
"legend must use the consensus row's rotation (CAG)"
);
assert!(
!svg.contains(">AGC<"),
"AGC rotation must not appear in legend when consensus_row=0"
);
assert!(
!svg.contains(">GCA<"),
"GCA rotation must not appear in legend when consensus_row=0"
);
}
#[test]
fn test_brick_notations() {
let strigars: Vec<(String, String)> = vec![
("CAG:A".to_string(), "12A".to_string()),
("CAG:A".to_string(), "10A".to_string()),
];
let brickplot = BrickPlot::new()
.with_names(vec!["consensus", "read_1"])
.with_strigars(strigars)
.with_notations(vec![Some("".to_string()), None]);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
assert!(
svg.contains("(CAG)12"),
"per-block notation must appear for enabled row"
);
assert!(
!svg.contains("(CAG)10"),
"disabled row must not get per-block notation"
);
}
#[test]
fn test_brick_stitched_per_segment_canonical() {
let strigars: Vec<(String, String)> = vec![
(
"ACCCTA:A | ACCCTA:A | TAACCC:A,T:B | CCCTAA:A,ACCTAACCCTTAA:B".to_string(),
"2A | 36@ | 2A | 213@ | 2A1B3A | 31@ | 2A1B2A".to_string(),
),
("ACCCTA:A".to_string(), "5A".to_string()),
];
let brickplot = BrickPlot::new()
.with_names(vec!["read_1", "read_2"])
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots);
let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_stitched_canonical.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"));
assert!(svg.contains("#c8c8c8"), "gap bricks should be grey");
assert!(
svg.contains("#1f77b4"),
"ACCCTA-family should be blue (global A)"
);
}
#[test]
fn test_brick_spec_form_b_gap_width() {
let bp = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"CAG:A | TGC:A".to_string(),
"3A | 30@ | 2A".to_string(),
)]);
let x_max = Plot::Brick(bp).bounds().expect("should have bounds").0 .1;
assert!(
(x_max - 45.0).abs() < 0.01,
"form B 30@ gap: expected total width 45 nt, got {}",
x_max
);
}
#[test]
fn test_brick_spec_form_a_gap_width() {
let bp = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"CAG:A | @:ATGAT | TGC:A".to_string(),
"3A | 1@ | 2A".to_string(),
)]);
let x_max = Plot::Brick(bp).bounds().expect("should have bounds").0 .1;
assert!(
(x_max - 20.0).abs() < 0.01,
"form A @:ATGAT gap: expected total width 20 nt, got {}",
x_max
);
}
#[test]
fn test_brick_spec_form_a_vs_b_disambiguation_of_1at() {
let form_a = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"CAG:A | @:AT | TGC:A".to_string(),
"3A | 1@ | 2A".to_string(),
)]);
let form_b = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"CAG:A | TGC:A".to_string(),
"3A | 1@ | 2A".to_string(),
)]);
let x_max_a = Plot::Brick(form_a).bounds().expect("form A bounds").0 .1;
let x_max_b = Plot::Brick(form_b).bounds().expect("form B bounds").0 .1;
assert!(
(x_max_a - 17.0).abs() < 0.01,
"form A 1@ with @:AT: expected 17 nt (2-nt gap), got {}",
x_max_a
);
assert!(
(x_max_b - 16.0).abs() < 0.01,
"form B 1@ no motifs entry: expected 16 nt (1-nt gap), got {}",
x_max_b
);
}
#[test]
fn test_brick_spec_bean1_sca31_renders() {
let strigars = vec![(
"ATAAA:A,AT:B | ATA:A | ATGGA:A,TGGA:B,ATGA:C,AGA:D | ATA:A | @:AT | GAATG:A,AATG:B | @:GAA | TAA:A,A:B".to_string(),
"22A1B22A | 27A | 61A1B154A1C78A1C18A1D24A1C2A1C75A1C80A1C74A1C117A | 9A | 1@ | 129A1B93A | 1@ | 11A1B1A1B2A2B1A2B2A".to_string(),
)];
let brickplot = BrickPlot::new()
.with_names(vec!["SCA31_read"])
.with_strigars(strigars);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots).with_title("BEAN1/SCA31 locus (spec example)");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("test_outputs/brickplot_bean1_sca31.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"), "must produce valid SVG");
assert!(
svg.contains("#c8c8c8"),
"form A gap segments must render as grey bricks"
);
assert!(
svg.contains("#1f77b4"),
"primary motif (global A) must use the first palette colour"
);
}
#[test]
fn test_brick_spec_bean1_sca31_gap_widths() {
let strigars = vec![(
"ATAAA:A,AT:B | ATA:A | ATGGA:A,TGGA:B,ATGA:C,AGA:D | ATA:A | @:AT | GAATG:A,AATG:B | @:GAA | TAA:A,A:B".to_string(),
"22A1B22A | 27A | 61A1B154A1C78A1C18A1D24A1C2A1C75A1C80A1C74A1C117A | 9A | 1@ | 129A1B93A | 1@ | 11A1B1A1B2A2B1A2B2A".to_string(),
)];
let bp = BrickPlot::new()
.with_names(vec!["SCA31_read"])
.with_strigars(strigars);
let x_max = Plot::Brick(bp).bounds().expect("should have bounds").0 .1;
assert!(
(x_max - 4956.0).abs() < 0.01,
"BEAN1/SCA31 total width: expected 4956 nt, got {}",
x_max
);
}
#[test]
fn test_brick_spec_stitched_with_traditional_notation() {
let flanked = vec![
("ACGTACGT", "CAG:A,CAA:B,CCG:C", "6A1B2A1C10A", "TGCATGCA"),
("ACGTACGT", "CAG:A,CCG:B", "8A1B10A", "TGCATGCA"),
("ACGTACGT", "CAG:A", "20A", "TGCA"),
];
let brickplot = BrickPlot::new()
.with_names(vec!["consensus", "read_1", "read_2"])
.with_consensus_row(0)
.with_mark_primary()
.with_flanked_strigars(flanked)
.with_notations(vec![
Some("(CAG)6(CAA)1(CAG)2(CCG)1(CAG)10".to_string()),
None,
None,
]);
let plots = vec![Plot::Brick(brickplot)];
let layout = Layout::auto_from_plots(&plots)
.with_title("BrickPlot — bladerunner flanked+notation pipeline");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("test_outputs/brickplot_spec_full_pipeline.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"), "must produce valid SVG");
assert!(
svg.contains("(CAG)6"),
"run of 6 A bricks must produce (CAG)6 label"
);
assert!(
svg.contains("(CAA)1"),
"run of 1 B brick must produce (CAA)1 label"
);
assert!(
svg.contains("(CAG)2"),
"run of 2 A bricks must produce (CAG)2 label"
);
assert!(
svg.contains("(CCG)1"),
"run of 1 C brick must produce (CCG)1 label"
);
assert!(
svg.contains("(CAG)10"),
"run of 10 A bricks must produce (CAG)10 label"
);
assert!(
svg.contains("*"),
"mark_primary must append * to primary motif legend label"
);
assert!(svg.contains("#009600"), "DNA flank bricks must appear");
}
#[test]
fn test_brick_spec_multi_segment_single_candidate() {
let bp = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"CAG:A,CAA:B,CCG:C".to_string(),
"2A1B2A1C10A".to_string(),
)]);
let x_max = Plot::Brick(bp).bounds().expect("bounds").0 .1;
assert!(
(x_max - 48.0).abs() < 0.01,
"single-segment 3-motif: expected 48 nt, got {}",
x_max
);
}
#[test]
fn test_brick_spec_segment_count_mismatch_form_b() {
let bp = BrickPlot::new().with_names(vec!["r1"]).with_strigars(vec![(
"ATAAA:A | ATA:A".to_string(),
"10A | 50@ | 5A".to_string(),
)]);
let x_max = Plot::Brick(bp).bounds().expect("bounds").0 .1;
assert!(
(x_max - 115.0).abs() < 0.01,
"form B 50@ gap (2 motif segs, 3 strigar segs): expected 115 nt, got {}",
x_max
);
}
#[test]
fn test_brickplot_figure_haplotypes_shared_x() {
use kuva::render::figure::Figure;
let tmpl = BrickTemplate::new().dna();
let hap1 = BrickPlot::new()
.with_sequences(vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCAT",
])
.with_names(vec!["hap1_r1", "hap1_r2", "hap1_r3"])
.with_template(tmpl.template.clone())
.with_row_height(20.0);
let hap2 = BrickPlot::new()
.with_sequences(vec![
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCAT",
"CGGCGATCAGGCCGCACTCATCATCATCATCATCATCAT",
])
.with_names(vec![
"hap2_r1", "hap2_r2", "hap2_r3", "hap2_r4", "hap2_r5", "hap2_r6", "hap2_r7", "hap2_r8",
])
.with_template(tmpl.template.clone())
.with_row_height(20.0);
let figure = Figure::new(2, 1)
.with_plots(vec![vec![Plot::Brick(hap1)], vec![Plot::Brick(hap2)]])
.with_shared_x_all()
.with_title("Haplotype brick plots — shared x, equal row height");
let scene = figure.render();
let svg = SvgBackend.render_scene(&scene);
std::fs::write("test_outputs/brickplot_haplotypes_figure.svg", svg.clone()).unwrap();
assert!(svg.contains("<svg"), "expected SVG output");
assert!(
svg.contains("hap1_r1"),
"hap1 read labels should be present"
);
assert!(
svg.contains("hap2_r1"),
"hap2 read labels should be present"
);
}
#[test]
fn test_brickplot_row_height_standalone_sizing() {
let tmpl = BrickTemplate::new().dna();
let brick3 = BrickPlot::new()
.with_sequences(vec!["ACGT", "ACGT", "ACGT"])
.with_names(vec!["r1", "r2", "r3"])
.with_template(tmpl.template.clone())
.with_row_height(20.0);
let brick8 = BrickPlot::new()
.with_sequences(vec!["ACGT"; 8].to_vec())
.with_names((1..=8).map(|i| format!("r{i}")).collect::<Vec<_>>())
.with_template(tmpl.template.clone())
.with_row_height(20.0);
let plots3 = vec![Plot::Brick(brick3)];
let plots8 = vec![Plot::Brick(brick8)];
let layout3 = Layout::auto_from_plots(&plots3);
let layout8 = Layout::auto_from_plots(&plots8);
assert!(
layout3.height.is_some(),
"layout for 3-row brick should have height set"
);
assert!(
layout8.height.is_some(),
"layout for 8-row brick should have height set"
);
let h3 = layout3.height.unwrap();
let h8 = layout8.height.unwrap();
let diff = h8 - h3;
assert!(
(diff - 100.0).abs() < 1.0,
"canvas height difference should be 5 * row_height = 100 px, got {diff}"
);
}