Parallel Coordinates Plot

A parallel coordinates plot displays multivariate data by drawing one vertical axis per dimension and connecting each observation as a polyline that passes through its value on each axis. Groups of observations that share a similar pattern appear as bundles of lines with similar trajectories; divergent groups cross each other clearly.

Parallel coordinates are useful for exploring high-dimensional datasets, comparing groups across many measured attributes, and identifying which dimensions best separate groups.

Import path: kuva::plot::parallel::{ParallelPlot, ParallelRow}


Basic usage

Set axis names with .with_axis_names(), then add rows with .with_row_group(group, values). Each row is one observation; values must be in the same order as the axis names.

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

let plot = ParallelPlot::new()
    .with_axis_names(["Sepal L", "Sepal W", "Petal L", "Petal W"])
    .with_row_group("setosa",     vec![5.1, 3.5, 1.4, 0.2])
    .with_row_group("setosa",     vec![4.9, 3.0, 1.4, 0.2])
    .with_row_group("setosa",     vec![4.7, 3.2, 1.3, 0.2])
    .with_row_group("versicolor", vec![7.0, 3.2, 4.7, 1.4])
    .with_row_group("versicolor", vec![6.4, 3.2, 4.5, 1.5])
    .with_row_group("versicolor", vec![6.9, 3.1, 4.9, 1.5])
    .with_row_group("virginica",  vec![6.3, 3.3, 6.0, 2.5])
    .with_row_group("virginica",  vec![5.8, 2.7, 5.1, 1.9])
    .with_row_group("virginica",  vec![7.1, 3.0, 5.9, 2.1])
    .with_legend("Species");

let plots = vec![Plot::Parallel(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Iris Dataset");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write("parallel.svg", svg).unwrap();
}
Parallel coordinates plot of the Iris dataset

Each axis is normalised to [0, 1] by default so that differently-scaled dimensions are comparable. Disable with .with_normalize(false) when all axes share a common unit.


Smooth curves

.with_curved(true) draws S-shaped cubic Bézier curves instead of straight polylines, which reduces visual clutter in dense plots.

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

let plot = ParallelPlot::new()
    .with_axis_names(["Sepal L", "Sepal W", "Petal L", "Petal W"])
    .with_row_group("setosa",     vec![5.1, 3.5, 1.4, 0.2])
    .with_row_group("setosa",     vec![4.9, 3.0, 1.4, 0.2])
    .with_row_group("versicolor", vec![7.0, 3.2, 4.7, 1.4])
    .with_row_group("versicolor", vec![6.4, 3.2, 4.5, 1.5])
    .with_row_group("virginica",  vec![6.3, 3.3, 6.0, 2.5])
    .with_row_group("virginica",  vec![7.1, 3.0, 5.9, 2.1])
    .with_curved(true)
    .with_opacity(0.7)
    .with_legend("Species");

let plots = vec![Plot::Parallel(plot)];
}
Parallel coordinates with smooth Bézier curves

Group mean overlay

.with_mean(true) draws a bold polyline at the per-group mean for each axis. This makes the group-level pattern visible even when individual lines are dense.

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

let plot = ParallelPlot::new()
    .with_axis_names(["Recall", "Precision", "F1", "AUC", "Inference ms"])
    // Multiple runs per model
    .with_group_rows("BERT",  [
        vec![0.82, 0.85, 0.83, 0.91, 120.0],
        vec![0.80, 0.87, 0.83, 0.90, 115.0],
        vec![0.83, 0.84, 0.83, 0.92, 125.0],
    ])
    .with_group_rows("DistilBERT", [
        vec![0.78, 0.80, 0.79, 0.87,  55.0],
        vec![0.77, 0.82, 0.79, 0.86,  52.0],
        vec![0.79, 0.81, 0.80, 0.88,  58.0],
    ])
    .with_group_rows("LSTM", [
        vec![0.72, 0.75, 0.73, 0.82,  30.0],
        vec![0.71, 0.76, 0.73, 0.81,  28.0],
        vec![0.73, 0.74, 0.73, 0.83,  32.0],
    ])
    .with_mean(true)
    .with_opacity(0.35)
    .with_curved(true)
    .with_legend("Model");

let plots = vec![Plot::Parallel(plot)];
let layout = Layout::auto_from_plots(&plots).with_title("NLP Model Comparison");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Parallel coordinates with group mean overlay

Axis inversion

Some axes are naturally "better when low" (e.g., error rate, latency). .with_inverted_axis(i) inverts axis i so that high values plot near the bottom — a visual triangle at the bottom of the axis label indicates inversion.

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

// Axes: Accuracy (higher = better), Error rate (lower = better), Speed ms (lower = better), F1 (higher = better)
let plot = ParallelPlot::new()
    .with_axis_names(["Accuracy", "Error rate", "Speed (ms)", "F1"])
    .with_row_group("Model A", vec![0.92, 0.08, 120.0, 0.90])
    .with_row_group("Model A", vec![0.91, 0.09, 115.0, 0.89])
    .with_row_group("Model B", vec![0.87, 0.13,  45.0, 0.86])
    .with_row_group("Model B", vec![0.88, 0.12,  48.0, 0.87])
    .with_inverted_axes([1, 2])  // invert Error rate and Speed: down = better
    .with_mean(true)
    .with_legend("Model");

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

ParallelPlot API reference

ParallelPlot builders

MethodDefaultDescription
ParallelPlot::new()Create a parallel coordinates plot
.with_axis_names(iter)Set axis (column) names
.with_row(values)Add an ungrouped row
.with_row_group(group, values)Add a row assigned to a named group
.with_rows(iter)Add multiple ungrouped rows
.with_group_rows(group, iter)Add multiple rows to the same group
.with_normalize(bool)trueNormalise each axis independently to [0, 1]
.with_curved(bool)falseDraw smooth S-shaped Bézier curves
.with_stroke_width(px)1.2Polyline stroke width
.with_opacity(f)0.6Polyline opacity
.with_color(css)"steelblue"Fallback color for ungrouped rows
.with_group_colors(iter)paletteExplicit per-group CSS colors
.with_mean(bool)falseDraw a bold mean line for each group
.with_inverted_axis(i)Invert axis i (high values at bottom)
.with_inverted_axes(iter)Invert multiple axes
.with_legend(label)Legend title (one entry per group)