Radar / Spider Chart

A radar (spider) chart displays multivariate data on radial axes emanating from a common centre. Each axis represents one variable; the distance from the centre encodes the value. Multiple series are drawn as filled or stroked polygons, making it easy to compare profiles across observations or groups.

Radar charts are popular for displaying skill profiles, product comparisons, and quality metrics — any context where you want to compare several dimensions at once.

Import path: kuva::plot::radar::RadarPlot


Basic usage

Pass axis names to RadarPlot::new(), then add series with .with_series_labeled(). Colors are assigned from the category10 palette.

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let plot = RadarPlot::new(["Speed", "Power", "Agility", "Stamina", "Technique"])
    .with_series_labeled([0.80, 0.60, 0.90, 0.70, 0.75], "Group A")
    .with_series_labeled([0.60, 0.90, 0.50, 0.80, 0.70], "Group B")
    .with_legend(true);

let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Team Performance");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("radar.svg", svg).unwrap();
}
Basic radar chart with two series

Filled polygons

.with_filled(true) shades each series polygon with a semi-transparent fill. Adjust transparency with .with_opacity().

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::render::plots::Plot;
use kuva::render::layout::Layout;
use kuva::render::render::render_multiple;
use kuva::backend::svg::SvgBackend;

let plot = RadarPlot::new(["Attack", "Defense", "Speed", "Magic", "Stamina"])
    .with_series_labeled([9.0, 5.0, 7.0, 3.0, 8.0], "Warrior")
    .with_series_labeled([4.0, 7.0, 6.0, 9.0, 5.0], "Mage")
    .with_series_labeled([6.0, 4.0, 10.0, 5.0, 6.0], "Rogue")
    .with_filled(true)
    .with_opacity(0.25)
    .with_range(0.0, 10.0)
    .with_legend(true);

let plots = vec![Plot::Radar(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("Character Stats");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Filled radar chart with three series

Normalised axes

When axes have different units or scales, use .with_normalize(true) to map each axis independently to [0, 1]. Grid ring labels become percentages.

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::render::plots::Plot;
use kuva::render::layout::Layout;
use kuva::render::render::render_multiple;

// Axes in different units: km/h, kg, %, %, m
let plot = RadarPlot::new(["Top Speed", "Weight", "Win Rate", "Accuracy", "Jump Height"])
    .with_series_labeled([230.0, 85.0, 0.62, 0.78, 1.20], "Athlete A")
    .with_series_labeled([195.0, 72.0, 0.55, 0.91, 1.45], "Athlete B")
    .with_normalize(true)
    .with_filled(true)
    .with_legend(true);

let plots = vec![Plot::Radar(plot)];
}

Per-axis error bands

Attach ±error values to a series with .with_series_errors(), called immediately after adding the series. A shaded band is drawn between value − error and value + error on each axis.

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::render::plots::Plot;
use kuva::render::layout::Layout;
use kuva::render::render::render_multiple;

let plot = RadarPlot::new(["Recall", "Precision", "F1", "AUC", "Speed"])
    .with_series_labeled([0.82, 0.75, 0.78, 0.89, 0.70], "Model A")
    .with_series_errors([0.04, 0.06, 0.05, 0.03, 0.08])
    .with_series_labeled([0.70, 0.88, 0.78, 0.84, 0.90], "Model B")
    .with_series_errors([0.05, 0.04, 0.04, 0.04, 0.06])
    .with_range(0.0, 1.0)
    .with_legend(true);

let plots = vec![Plot::Radar(plot)];
}

Reference overlay

.with_reference() adds a dashed grey polygon — useful for showing a target, average, or population norm that series should be compared against.

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::render::plots::Plot;
use kuva::render::layout::Layout;
use kuva::render::render::render_multiple;

let plot = RadarPlot::new(["Endurance", "Strength", "Flexibility", "Balance", "Power"])
    .with_series_labeled([7.0, 8.0, 5.0, 6.0, 9.0], "Athlete")
    .with_reference([6.0, 6.0, 6.0, 6.0, 6.0], "Population avg")
    .with_range(0.0, 10.0)
    .with_filled(true)
    .with_dot_size(4.0)
    .with_legend(true);

let plots = vec![Plot::Radar(plot)];
}

Circular grid

By default grid rings are polygons. Use .with_circular_grid(true) for a more traditional spider-chart appearance.

#![allow(unused)]
fn main() {
use kuva::plot::radar::RadarPlot;
use kuva::render::plots::Plot;
use kuva::render::layout::Layout;
use kuva::render::render::render_multiple;

let plot = RadarPlot::new(["N", "NE", "E", "SE", "S", "SW", "W", "NW"])
    .with_series_labeled([8.0, 3.0, 6.0, 9.0, 5.0, 2.0, 7.0, 4.0], "Signal strength")
    .with_circular_grid(true)
    .with_range(0.0, 10.0)
    .with_filled(true);

let plots = vec![Plot::Radar(plot)];
}

RadarPlot API reference

RadarPlot builders

MethodDefaultDescription
RadarPlot::new(axes)Create a plot; axes are names rendered clockwise from top
.with_series(values)Add an unlabelled series
.with_series_labeled(values, label)Add a labelled series
.with_series_color(values, label, color)Add a labelled series with an explicit color
.with_series_errors(errors)Attach per-axis ±errors to the most recently added series
.with_series_dasharray(s)Set SVG stroke-dasharray on the most recently added series
.with_reference(values, label)Add a dashed reference polygon
.with_reference_color(values, label, color)Add a dashed reference polygon with explicit color
.with_filled(bool)falseFill polygons with a semi-transparent color
.with_opacity(f)0.25Fill opacity (used when filled is true)
.with_range(min, max)autoShared value range for all axes
.with_axis_range(i, min, max)Override value range for axis i
.with_normalize(bool)falseNormalise each axis to [0, 1] independently
.with_inverted_axis(i)Invert axis i (high values plot near the centre)
.with_inverted_axes(iter)Invert multiple axes
.with_grid_lines(n)5Number of concentric grid rings
.with_grid(bool)trueShow grid rings and radial axis lines
.with_circular_grid(bool)falseDraw grid rings as circles instead of polygons
.with_dot_size(px)Draw filled dots at polygon vertices
.with_stroke_width(px)1.5Series polygon stroke width
.with_vertex_labels(bool)falseShow data value at each polygon vertex
.with_start_angle(deg)-90Angle of axis 0 in degrees (clockwise from north)
.with_start_axis(k)Place axis k at the top (north) position
.with_axis_ticks(bool)falseTick marks on each axis at grid ring intersections
.with_legend(bool)falseShow a legend box