mod common;
use kuva::backend::svg::SvgBackend;
use kuva::plot::volcano::VolcanoPlot;
use kuva::prelude::*;
use kuva::render::figure::Figure;
use kuva::render::layout::Layout;
fn make_scatter_svg(interactive: bool) -> String {
let mut layout = Layout::auto_from_plots(&[Plot::Scatter(ScatterPlot::new().with_data(vec![
(1.0_f64, 2.0),
(3.0, 4.0),
(5.0, 1.0),
]))]);
if interactive {
layout = layout.with_interactive();
}
let plots = vec![Plot::Scatter(ScatterPlot::new().with_data(vec![
(1.0_f64, 2.0),
(3.0, 4.0),
(5.0, 1.0),
]))];
let scene = render_multiple(plots, layout);
SvgBackend::new().render_scene(&scene)
}
fn make_multi_group_scatter_svg() -> String {
let plots: Vec<Plot> = vec![
ScatterPlot::new()
.with_data(vec![(1.0_f64, 2.0), (2.0, 3.0)])
.with_color("steelblue")
.with_legend("Group A")
.into(),
ScatterPlot::new()
.with_data(vec![(3.0_f64, 4.0), (4.0, 5.0)])
.with_color("tomato")
.with_legend("Group B")
.into(),
];
let layout = Layout::auto_from_plots(&plots).with_interactive();
let scene = render_multiple(plots, layout);
SvgBackend::new().render_scene(&scene)
}
fn make_volcano_svg(interactive: bool) -> String {
let mut vp = VolcanoPlot::new();
vp = vp
.with_point("gene1", 2.5_f64, 0.001_f64)
.with_point("gene2", -3.0_f64, 0.0001_f64)
.with_point("gene3", 0.5_f64, 0.5_f64);
let plots = vec![Plot::Volcano(vp)];
let mut layout = Layout::auto_from_plots(&plots);
if interactive {
layout = layout.with_interactive();
}
let scene = render_multiple(plots, layout);
SvgBackend::new().render_scene(&scene)
}
#[test]
fn test_interactive_off_by_default() {
let svg = make_scatter_svg(false);
common::write_test_output("test_outputs/interactive_off.svg", &svg).unwrap();
assert!(
!svg.contains("data-xmin"),
"non-interactive SVG must not have data-xmin"
);
assert!(
!svg.contains("<script"),
"non-interactive SVG must not have <script"
);
assert!(
!svg.contains("<foreignObject"),
"non-interactive SVG must not have <foreignObject"
);
}
#[test]
fn test_interactive_root_attrs() {
let svg = make_scatter_svg(true);
common::write_test_output("test_outputs/interactive_root_attrs.svg", &svg).unwrap();
assert!(svg.contains("data-xmin="), "missing data-xmin");
assert!(svg.contains("data-xmax="), "missing data-xmax");
assert!(svg.contains("data-ymin="), "missing data-ymin");
assert!(svg.contains("data-ymax="), "missing data-ymax");
assert!(svg.contains("data-plot-left="), "missing data-plot-left");
assert!(svg.contains("data-plot-top="), "missing data-plot-top");
assert!(svg.contains("data-plot-right="), "missing data-plot-right");
assert!(
svg.contains("data-plot-bottom="),
"missing data-plot-bottom"
);
}
#[test]
fn test_interactive_log_axis_flag() {
let plots = vec![Plot::Scatter(
ScatterPlot::new().with_data(vec![(1.0_f64, 2.0), (10.0, 100.0)]),
)];
let layout = Layout::auto_from_plots(&plots)
.with_interactive()
.with_log_y();
let scene = render_multiple(plots, layout);
let svg = SvgBackend::new().render_scene(&scene);
common::write_test_output("test_outputs/interactive_log_y.svg", &svg).unwrap();
assert!(
svg.contains(r#"data-log-y="1""#),
"expected data-log-y=\"1\" in SVG root"
);
}
#[test]
fn test_interactive_scatter_data_attrs() {
let svg = make_scatter_svg(true);
common::write_test_output("test_outputs/interactive_scatter_data_attrs.svg", &svg).unwrap();
assert!(
svg.contains("data-x="),
"expected data-x= on scatter point groups"
);
assert!(
svg.contains("data-y="),
"expected data-y= on scatter point groups"
);
}
#[test]
fn test_interactive_no_circle_batch() {
let svg = make_scatter_svg(true);
assert!(
svg.contains("data-x="),
"scatter points should be individually wrapped in interactive mode"
);
let svg_off = make_scatter_svg(false);
assert!(
!svg_off.contains("data-x="),
"non-interactive scatter must not have data-x"
);
}
#[test]
fn test_interactive_volcano_threshold() {
let svg = make_volcano_svg(true);
common::write_test_output("test_outputs/interactive_volcano_threshold.svg", &svg).unwrap();
assert!(
svg.contains(r#"class="kuva-threshold""#),
"expected class=\"kuva-threshold\" on volcano threshold lines"
);
}
#[test]
fn test_interactive_volcano_point_logfc() {
let svg = make_volcano_svg(true);
assert!(
svg.contains("data-logfc="),
"expected data-logfc= on volcano point groups"
);
}
#[test]
fn test_interactive_legend_data_group() {
let svg = make_multi_group_scatter_svg();
common::write_test_output("test_outputs/interactive_legend_data_group.svg", &svg).unwrap();
assert!(
svg.contains(r#"class="legend-entry""#),
"expected class=\"legend-entry\" on legend entry groups"
);
assert!(
svg.contains("data-group="),
"expected data-group= on legend entry groups"
);
}
#[test]
fn test_interactive_script_present() {
let svg = make_scatter_svg(true);
assert!(
svg.contains("<script"),
"expected <script in interactive SVG"
);
assert!(svg.contains("CDATA"), "expected CDATA section in script");
}
#[test]
fn test_interactive_foreignobject() {
let svg = make_scatter_svg(true);
assert!(
svg.contains("<foreignObject"),
"expected <foreignObject in interactive SVG"
);
assert!(
svg.contains("kuva-search"),
"expected search input in foreignObject"
);
}
#[test]
fn test_interactive_css_pinned() {
let svg = make_scatter_svg(true);
assert!(
svg.contains(".pinned"),
"expected .pinned CSS rule in interactive SVG"
);
}
#[test]
fn test_noninteractive_unchanged() {
let svg = make_scatter_svg(false);
assert!(svg.contains("<svg"), "SVG root missing");
assert!(svg.contains("<circle"), "expected circles in scatter");
assert!(
!svg.contains("data-xmin"),
"non-interactive must not have axis metadata"
);
assert!(
!svg.contains(".pinned"),
"non-interactive must not have pinned CSS"
);
}
#[test]
fn test_interactive_figure_propagates() {
let make_scatter = || {
vec![Plot::Scatter(
ScatterPlot::new().with_data(vec![(1.0_f64, 2.0), (3.0, 4.0)]),
)]
};
let make_layout = || {
let plots = make_scatter();
Layout::auto_from_plots(&plots).with_interactive()
};
let figure = Figure::new(1, 2)
.with_plots(vec![make_scatter(), make_scatter()])
.with_layouts(vec![make_layout(), make_layout()]);
let scene = figure.render();
let svg = SvgBackend::new().render_scene(&scene);
common::write_test_output("test_outputs/interactive_figure.svg", &svg).unwrap();
assert!(
svg.contains("data-x="),
"figure panels should have data-x on scatter points when interactive"
);
}