use kuva::backend::svg::SvgBackend;
use kuva::plot::radar::RadarPlot;
#[allow(unused_imports)]
use kuva::plot::radar::RadarReference;
use kuva::render::{layout::Layout, plots::Plot, render::render_multiple};
fn render_svg(plots: Vec<Plot>, layout: Layout) -> String {
SvgBackend.render_scene(&render_multiple(plots, layout))
}
#[test]
fn test_radar_basic() {
let plot = RadarPlot::new(vec!["Speed", "Power", "Agility", "Stamina", "Technique"])
.with_series(vec![0.8_f64, 0.6, 0.9, 0.7, 0.75]);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Basic");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_basic.svg", &svg).unwrap();
assert!(svg.contains("Speed"), "should contain axis label");
assert!(svg.contains("Agility"), "should contain axis label");
assert!(svg.contains(" Z"), "should contain closed polygon path");
}
#[test]
fn test_radar_two_series() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E"])
.with_series_labeled(vec![1.0_f64, 0.8, 0.6, 0.9, 0.7], "Group 1")
.with_series_labeled(vec![0.5_f64, 0.7, 1.0, 0.4, 0.8], "Group 2");
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Two Series");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_two_series.svg", &svg).unwrap();
assert!(svg.contains("A"));
assert!(svg.contains("B"));
}
#[test]
fn test_radar_filled() {
let plot = RadarPlot::new(vec!["Alpha", "Beta", "Gamma", "Delta"])
.with_series_labeled(vec![0.9_f64, 0.6, 0.8, 0.4], "Series A")
.with_filled(true)
.with_opacity(0.3);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Filled");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_filled.svg", &svg).unwrap();
assert!(svg.contains("Alpha"));
assert!(svg.contains(" Z"));
}
#[test]
fn test_radar_with_legend() {
let plot = RadarPlot::new(vec!["X", "Y", "Z", "W"])
.with_series_labeled(vec![1.0_f64, 0.5, 0.8, 0.3], "Method A")
.with_series_labeled(vec![0.6_f64, 0.9, 0.4, 0.7], "Method B")
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Legend");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_legend.svg", &svg).unwrap();
assert!(svg.contains("Method A"), "legend entry A");
assert!(svg.contains("Method B"), "legend entry B");
}
#[test]
fn test_radar_normalize() {
let plot = RadarPlot::new(vec!["Precision", "Recall", "F1", "AUC"])
.with_series_labeled(vec![0.92_f64, 0.85, 0.88, 0.94], "Model A")
.with_series_labeled(vec![0.78_f64, 0.91, 0.84, 0.89], "Model B")
.with_normalize(true)
.with_filled(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Normalized");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_normalize.svg", &svg).unwrap();
assert!(svg.contains("%"), "normalize mode should show % labels");
}
#[test]
fn test_radar_explicit_range() {
let plot = RadarPlot::new(vec!["Memory", "Speed", "Accuracy", "Throughput", "Latency"])
.with_series_color(vec![80.0_f64, 95.0, 88.0, 72.0, 91.0], "Tool A", "#e41a1c")
.with_series_color(vec![65.0_f64, 78.0, 92.0, 88.0, 75.0], "Tool B", "#377eb8")
.with_range(0.0, 100.0)
.with_grid_lines(4)
.with_filled(true)
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Explicit Range");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_explicit_range.svg", &svg).unwrap();
assert!(svg.contains("Memory"));
assert!(svg.contains("Tool A"));
assert!(svg.contains("Tool B"));
}
#[test]
fn test_radar_with_dots() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E", "F"])
.with_series(vec![0.5_f64, 0.8, 0.6, 0.9, 0.7, 0.4])
.with_dot_size(4.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Dots");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_dots.svg", &svg).unwrap();
assert!(svg.contains("<circle"), "should render vertex dots");
}
#[test]
fn test_radar_no_grid() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D"])
.with_series(vec![0.7_f64, 0.5, 0.9, 0.6])
.with_grid(false);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar No Grid");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_no_grid.svg", &svg).unwrap();
assert!(!svg.contains("4,3"), "dashed grid rings should be absent");
}
#[test]
fn test_radar_six_axes() {
let plot = RadarPlot::new(vec![
"Sensitivity",
"Specificity",
"PPV",
"NPV",
"F1",
"MCC",
])
.with_series_labeled(
vec![0.91_f64, 0.87, 0.84, 0.93, 0.875, 0.78],
"Classifier A",
)
.with_series_labeled(
vec![0.85_f64, 0.92, 0.90, 0.88, 0.875, 0.77],
"Classifier B",
)
.with_filled(true)
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Six Axes");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_six_axes.svg", &svg).unwrap();
assert!(svg.contains("Sensitivity"));
assert!(svg.contains("MCC"));
assert!(svg.contains("Classifier A"));
}
#[test]
fn test_radar_three_axes_minimum() {
let plot = RadarPlot::new(vec!["X", "Y", "Z"]).with_series(vec![1.0_f64, 0.5, 0.8]);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Three Axes");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_three_axes.svg", &svg).unwrap();
assert!(svg.contains("<svg"));
assert!(svg.contains(" Z"));
}
#[test]
fn test_radar_custom_stroke_width() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E"])
.with_series(vec![0.6_f64, 0.8, 0.5, 0.9, 0.7])
.with_stroke_width(3.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Radar Stroke Width");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_stroke_width.svg", &svg).unwrap();
assert!(svg.contains("stroke-width=\"3\"") || svg.contains("stroke-width=\"3.0\""));
}
#[test]
fn test_radar_into_plot() {
let plot: Plot = RadarPlot::new(vec!["A", "B", "C", "D"])
.with_series(vec![1.0_f64, 0.5, 0.8, 0.6])
.into();
assert!(matches!(plot, Plot::Radar(_)));
}
#[test]
fn test_radar_benchmarking_use_case() {
let plot = RadarPlot::new(vec![
"Precision",
"Recall",
"Speed",
"Memory",
"Scalability",
])
.with_series_color(vec![0.94_f64, 0.91, 0.78, 0.85, 0.72], "Tool A", "#e41a1c")
.with_series_color(vec![0.88_f64, 0.95, 0.92, 0.70, 0.88], "Tool B", "#377eb8")
.with_series_color(vec![0.81_f64, 0.83, 0.96, 0.92, 0.95], "Tool C", "#4daf4a")
.with_filled(true)
.with_opacity(0.2)
.with_legend(true)
.with_range(0.0, 1.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Tool Comparison");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_benchmarking.svg", &svg).unwrap();
assert!(svg.contains("Precision"));
assert!(svg.contains("Tool A"));
assert!(svg.contains("Tool C"));
}
#[test]
fn test_radar_inverted_axis() {
let plot = RadarPlot::new(vec!["Latency", "Throughput", "Precision", "Recall"])
.with_series_labeled(vec![5.0_f64, 0.8, 0.9, 0.85], "System A")
.with_series_labeled(vec![2.0_f64, 0.6, 0.95, 0.78], "System B")
.with_inverted_axis(0) .with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Inverted Axis");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_inverted_axis.svg", &svg).unwrap();
assert!(svg.contains("Latency"));
assert!(svg.contains("System A"));
}
#[test]
fn test_radar_inverted_multiple_axes() {
let plot = RadarPlot::new(vec!["Error Rate", "Latency", "Recall", "F1"])
.with_series(vec![0.02_f64, 10.0, 0.88, 0.85])
.with_inverted_axes([0, 1]) .with_range(0.0, 1.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Inverted Axes");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_inverted_axes.svg", &svg).unwrap();
assert!(svg.contains("Error Rate"));
assert!(svg.contains(" Z"));
}
#[test]
fn test_radar_per_axis_range() {
let plot = RadarPlot::new(vec!["Speed (ms)", "Accuracy (%)", "Memory (MB)", "Score"])
.with_series_labeled(vec![120.0_f64, 94.0, 512.0, 0.88], "Model A")
.with_series_labeled(vec![85.0_f64, 97.0, 1024.0, 0.92], "Model B")
.with_axis_range(0, 0.0, 200.0)
.with_axis_range(1, 80.0, 100.0)
.with_axis_range(2, 0.0, 2048.0)
.with_axis_range(3, 0.0, 1.0)
.with_filled(true)
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Per-Axis Ranges");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_per_axis_range.svg", &svg).unwrap();
assert!(svg.contains("Speed"));
assert!(svg.contains("Model A"));
}
#[test]
fn test_radar_error_band() {
let plot = RadarPlot::new(vec!["Sensitivity", "Specificity", "Precision", "F1", "AUC"])
.with_series_labeled(vec![0.88_f64, 0.92, 0.87, 0.875, 0.95], "Classifier")
.with_series_errors(vec![0.04_f64, 0.03, 0.05, 0.04, 0.02])
.with_filled(true)
.with_opacity(0.3);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Error Bands");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_error_band.svg", &svg).unwrap();
assert!(svg.contains("Sensitivity"));
assert!(svg.contains(" Z"), "should have filled polygons");
}
#[test]
fn test_radar_vertex_labels() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E"])
.with_series(vec![0.8_f64, 0.6, 0.9, 0.7, 0.75])
.with_vertex_labels(true)
.with_dot_size(3.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Vertex Labels");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_vertex_labels.svg", &svg).unwrap();
assert!(
svg.contains("0.80") || svg.contains("0.8"),
"vertex value label present"
);
}
#[test]
fn test_radar_circular_grid() {
let plot = RadarPlot::new(vec!["Alpha", "Beta", "Gamma", "Delta", "Epsilon"])
.with_series_labeled(vec![0.9_f64, 0.6, 0.8, 0.7, 0.5], "Series A")
.with_circular_grid(true)
.with_grid_lines(4);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Circular Grid");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_circular_grid.svg", &svg).unwrap();
assert!(svg.contains("Alpha"));
assert!(svg.contains("4,3"), "dashed ring should be present");
}
#[test]
fn test_radar_reference_polygon() {
let plot = RadarPlot::new(vec!["Speed", "Power", "Agility", "Stamina", "Technique"])
.with_series_labeled(vec![0.8_f64, 0.6, 0.9, 0.7, 0.75], "Model")
.with_reference(vec![0.7_f64, 0.7, 0.7, 0.7, 0.7], "Baseline")
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Reference Polygon");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_reference.svg", &svg).unwrap();
assert!(svg.contains("Baseline"), "reference label in legend");
assert!(svg.contains("6,3"), "reference polygon uses 6,3 dasharray");
}
#[test]
fn test_radar_reference_with_color() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D"])
.with_series_labeled(vec![0.9_f64, 0.7, 0.8, 0.6], "Current")
.with_reference_color(vec![0.6_f64, 0.6, 0.6, 0.6], "Target", "#ff7f00")
.with_legend(true);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Reference With Color");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_reference_color.svg", &svg).unwrap();
assert!(svg.contains("#ff7f00"), "custom reference color");
assert!(svg.contains("Target"));
}
#[test]
fn test_radar_start_angle() {
let plot = RadarPlot::new(vec!["N", "E", "S", "W"])
.with_series(vec![0.8_f64, 0.5, 0.6, 0.9])
.with_start_angle(0.0); let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Start Angle East");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_start_angle.svg", &svg).unwrap();
assert!(svg.contains("<svg"));
assert!(svg.contains(" Z"));
}
#[test]
fn test_radar_start_axis() {
let plot = RadarPlot::new(vec!["Alpha", "Beta", "Gamma", "Delta", "Epsilon"])
.with_series_labeled(vec![0.9_f64, 0.6, 0.8, 0.7, 0.5], "Data")
.with_start_axis(2); let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Start Axis");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_start_axis.svg", &svg).unwrap();
assert!(svg.contains("Gamma"));
assert!(svg.contains("Alpha"));
}
#[test]
fn test_radar_per_series_dasharray() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E"])
.with_series_labeled(vec![0.8_f64, 0.6, 0.9, 0.7, 0.75], "Solid")
.with_series_labeled(vec![0.5_f64, 0.8, 0.6, 0.9, 0.4], "Dashed")
.with_series_dasharray("6,3");
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Series Dash Patterns");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_dasharray.svg", &svg).unwrap();
assert!(svg.contains("6,3"), "dashed series stroke-dasharray");
}
#[test]
fn test_radar_axis_ticks() {
let plot = RadarPlot::new(vec!["A", "B", "C", "D", "E"])
.with_series(vec![0.7_f64, 0.5, 0.9, 0.6, 0.8])
.with_axis_ticks(true)
.with_grid_lines(4);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Axis Ticks");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_axis_ticks.svg", &svg).unwrap();
assert!(svg.contains("<line"), "tick marks are line primitives");
}
#[test]
fn test_radar_long_label_wrap() {
let plot = RadarPlot::new(vec![
"Positive Predictive Value",
"Negative Predictive Value",
"True Positive Rate",
"False Positive Rate",
"Matthews Correlation",
])
.with_series(vec![0.88_f64, 0.91, 0.87, 0.12, 0.79]);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Long Label Wrap");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_long_labels.svg", &svg).unwrap();
assert!(svg.contains("Positive"), "label text present");
assert!(svg.contains("Predictive"), "wrapped line present");
}
#[test]
fn test_radar_full_features() {
let plot = RadarPlot::new(vec!["Sensitivity", "Specificity", "Precision", "F1", "AUC"])
.with_series_color(vec![0.91_f64, 0.88, 0.89, 0.90, 0.95], "Model A", "#e41a1c")
.with_series_errors(vec![0.03_f64, 0.04, 0.03, 0.03, 0.02])
.with_series_color(vec![0.84_f64, 0.93, 0.92, 0.88, 0.91], "Model B", "#377eb8")
.with_series_dasharray("5,2")
.with_reference(vec![0.80_f64, 0.80, 0.80, 0.80, 0.80], "Threshold")
.with_filled(true)
.with_opacity(0.2)
.with_circular_grid(true)
.with_axis_ticks(true)
.with_vertex_labels(true)
.with_legend(true)
.with_range(0.0, 1.0);
let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Full Feature Demo");
let svg = render_svg(plots, layout);
std::fs::write("test_outputs/radar_full_features.svg", &svg).unwrap();
assert!(svg.contains("Model A"));
assert!(svg.contains("Model B"));
assert!(svg.contains("Threshold"));
assert!(svg.contains("5,2"), "dashed series present");
assert!(svg.contains("6,3"), "reference polygon present");
}