Scatter Plot

A scatter plot renders individual (x, y) data points as markers. It supports trend lines, error bars, variable point sizes, per-point colors, and six marker shapes.

Import path: kuva::plot::scatter::ScatterPlot


Basic usage

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

let data = vec![
    (0.5_f64, 1.2_f64),
    (1.4, 3.1),
    (2.1, 2.4),
    (3.3, 5.0),
    (4.0, 4.3),
    (5.2, 6.8),
    (6.1, 6.0),
    (7.0, 8.5),
    (8.4, 7.9),
    (9.1, 9.8),
];

let plot = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue")
    .with_size(5.0);

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Scatter Plot")
    .with_x_label("X")
    .with_y_label("Y");

let scene = render_multiple(plots, layout);
let svg = SvgBackend.render_scene(&scene);
std::fs::write("scatter.svg", svg).unwrap();
}
Basic scatter plot

Layout options

Layout::auto_from_plots() automatically computes axis ranges from the data. You can also set ranges manually with Layout::new((x_min, x_max), (y_min, y_max)).


Trend line

Add a linear trend line with .with_trend(TrendLine::Linear). Optionally overlay the regression equation and the Pearson R² value.

#![allow(unused)]
fn main() {
use kuva::plot::scatter::{ScatterPlot, TrendLine};
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let data = vec![
    (1.0_f64, 2.1_f64), (2.0, 3.9), (3.0, 6.2),
    (4.0, 7.8), (5.0, 10.1), (6.0, 12.3),
    (7.0, 13.9), (8.0, 16.2), (9.0, 17.8), (10.0, 19.7),
];

let plot = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue")
    .with_size(5.0)
    .with_trend(TrendLine::Linear)
    .with_trend_color("crimson")   // defaults to "black"
    .with_equation()               // show y = mx + b
    .with_correlation();           // show R²

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Linear Trend Line")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Scatter with linear trend line

Confidence band

Attach a shaded uncertainty region with .with_band(y_lower, y_upper). Both slices must align with the x positions of the scatter data. The band color matches the point color.

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

let xs: Vec<f64> = (1..=10).map(|i| i as f64).collect();
let ys: Vec<f64> = xs.iter().map(|&x| x * 1.8 + 0.5).collect();
let lower: Vec<f64> = ys.iter().map(|&y| y - 1.2).collect();
let upper: Vec<f64> = ys.iter().map(|&y| y + 1.2).collect();

let data: Vec<(f64, f64)> = xs.into_iter().zip(ys).collect();

let plot = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue")
    .with_size(5.0)
    .with_band(lower, upper);

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Confidence Band")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Scatter with confidence band

Error bars

Use .with_x_err() and .with_y_err() for symmetric error bars. Asymmetric variants accept (negative, positive) tuples.

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

let data = vec![
    (1.0_f64, 2.0_f64), (2.0, 4.5), (3.0, 5.8),
    (4.0, 8.2), (5.0, 10.1),
];
let x_err = vec![0.2_f64, 0.15, 0.3, 0.1, 0.25];  // symmetric
let y_err = vec![0.6_f64, 0.8, 0.4, 0.9, 0.5];     // symmetric

let plot = ScatterPlot::new()
    .with_data(data)
    .with_x_err(x_err)
    .with_y_err(y_err)
    .with_color("steelblue")
    .with_size(5.0);

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Error Bars")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Scatter with error bars

Asymmetric errors

Pass (neg, pos) tuples instead of scalar values:

#![allow(unused)]
fn main() {
use kuva::plot::scatter::ScatterPlot;
let data = vec![(1.0_f64, 5.0_f64), (2.0, 6.0)];
let y_err = vec![(0.3_f64, 0.8_f64), (0.5, 1.2)];  // (neg, pos)

let plot = ScatterPlot::new()
    .with_data(data)
    .with_y_err_asymmetric(y_err);
}

Marker shapes

Six marker shapes are available via MarkerShape. They are particularly useful when overlaying multiple series on the same axes.

#![allow(unused)]
fn main() {
use kuva::plot::scatter::{ScatterPlot, MarkerShape};
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;

let plots = vec![
    Plot::Scatter(ScatterPlot::new()
        .with_data(vec![(1.0_f64, 1.0_f64), (2.0, 1.0), (3.0, 1.0)])
        .with_color("steelblue").with_size(7.0)
        .with_marker(MarkerShape::Circle).with_legend("Circle")),

    Plot::Scatter(ScatterPlot::new()
        .with_data(vec![(1.0_f64, 2.0_f64), (2.0, 2.0), (3.0, 2.0)])
        .with_color("crimson").with_size(7.0)
        .with_marker(MarkerShape::Square).with_legend("Square")),

    Plot::Scatter(ScatterPlot::new()
        .with_data(vec![(1.0_f64, 3.0_f64), (2.0, 3.0), (3.0, 3.0)])
        .with_color("seagreen").with_size(7.0)
        .with_marker(MarkerShape::Triangle).with_legend("Triangle")),
];

let layout = Layout::auto_from_plots(&plots)
    .with_title("Marker Shapes")
    .with_x_label("X")
    .with_y_label("");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}

Available variants: Circle (default), Square, Triangle, Diamond, Cross, Plus.

Scatter marker shapes

Bubble plot

Encode a third dimension through point area using .with_sizes(). Values are point radii in pixels.

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

let data = vec![
    (1.0_f64, 3.0_f64), (2.5, 6.5), (4.0, 4.0),
    (5.5, 8.0), (7.0, 5.5), (8.5, 9.0),
];
let sizes = vec![5.0_f64, 14.0, 9.0, 18.0, 11.0, 7.0];

let plot = ScatterPlot::new()
    .with_data(data)
    .with_sizes(sizes)
    .with_color("steelblue");

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Bubble Plot")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Bubble plot

Per-point colors

Encode a categorical grouping through color using .with_colors(). Colors are matched to points by index and fall back to the uniform .with_color() value for any point without an entry.

This is useful when your data already carries a group label and you want to avoid splitting into multiple ScatterPlot instances. The legend is not updated automatically — add .with_legend() on separate ScatterPlot instances when you need a labeled legend.

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

// Three clusters, colors assigned per point
let data = vec![
    (1.0_f64, 1.5_f64), (1.5, 2.0), (2.0, 1.8),  // cluster A
    (4.0, 4.5),         (4.5, 5.0), (5.0, 4.8),  // cluster B
    (7.0, 2.0),         (7.5, 2.5), (8.0, 2.2),  // cluster C
];
let colors = vec![
    "steelblue", "steelblue", "steelblue",
    "crimson",   "crimson",   "crimson",
    "seagreen",  "seagreen",  "seagreen",
];

let plot = ScatterPlot::new()
    .with_data(data)
    .with_colors(colors)
    .with_size(6.0);

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Per-Point Colors")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Scatter with per-point colors

Marker opacity and stroke

Two builders control the visual style of markers, enabling three distinct modes useful for dense datasets:

ModeSettingUse case
Solid (default)no calls neededSmall N or well-separated clusters
Semi-transparentopacity < 1 + strokeDense regions pool colour; individual points stay visible
Hollowopacity = 0.0 + strokeVery large N; overlapping rings reveal density without blobs

Semi-transparent markers — overlapping clusters

Three Gaussian clusters of 200 points each share a region in the centre. Solid markers at this density merge into a single opaque mass. Reducing opacity to 0.25 lets the darker overlap region show where clusters share space, while the 0.7 px stroke keeps each marker individually legible.

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

// Three clusters, 200 points each — defined as (center_x, center_y, color, label)
let series = [
    (3.0_f64, 4.0_f64, "steelblue", "Cluster A"),
    (5.0,     5.5,     "tomato",    "Cluster B"),
    (4.0,     3.0,     "seagreen",  "Cluster C"),
];

// (populate `data` from your source — each entry is 200 (x, y) points)

let data: Vec<(f64,f64)> = vec![];
let plot = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue")
    .with_size(5.0)
    .with_marker_opacity(0.25)
    .with_marker_stroke_width(0.7)
    .with_legend("Cluster A");

let plots = vec![Plot::Scatter(plot) /* , ... */];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Overlapping Clusters — semi-transparent markers")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Three overlapping Gaussian clusters with semi-transparent markers

Hollow open circles — dense annular data

800 points sampled uniformly along a noisy ring. With solid fill the ring becomes a uniform band; hollow circles (opacity = 0.0) make the denser arc sections visible through the accumulation of overlapping outlines.

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

// 800 points sampled along a noisy annulus of radius ≈ 3
let data: Vec<(f64,f64)> = vec![];
let plot = ScatterPlot::new()
    .with_data(data)
    .with_color("steelblue")
    .with_size(4.0)
    .with_marker_opacity(0.0)       // fully hollow
    .with_marker_stroke_width(1.0); // only the outline is drawn

let plots = vec![Plot::Scatter(plot)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Hollow open circles — 800 pts in a noisy annulus")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
800 hollow open circles forming a noisy ring

Multiple series

Wrap multiple ScatterPlot structs in a Vec<Plot> and pass them to render_multiple(). Legends are shown when any series has a label attached via .with_legend().

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

let series_a = ScatterPlot::new()
    .with_data(vec![(1.0_f64, 2.0_f64), (3.0, 4.0), (5.0, 3.5)])
    .with_color("steelblue")
    .with_legend("Series A");

let series_b = ScatterPlot::new()
    .with_data(vec![(1.0_f64, 5.0_f64), (3.0, 6.5), (5.0, 7.0)])
    .with_color("crimson")
    .with_legend("Series B");

let plots = vec![Plot::Scatter(series_a), Plot::Scatter(series_b)];
let layout = Layout::auto_from_plots(&plots)
    .with_title("Two Series")
    .with_x_label("X")
    .with_y_label("Y");

let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
}
Multiple scatter series with legend

API reference

MethodDescription
ScatterPlot::new()Create a new scatter plot with defaults
.with_data(iter)Set (x, y) data; accepts any Into<f64> numeric type
.with_color(s)Set uniform point color (CSS color string, default "black")
.with_colors(iter)Set per-point colors; falls back to .with_color for out-of-range indices
.with_size(r)Set uniform point radius in pixels (default 3.0)
.with_sizes(iter)Set per-point radii (bubble plot); falls back to .with_size for out-of-range indices
.with_marker(MarkerShape)Set marker shape (default Circle)
.with_legend(s)Attach a legend label to this series
.with_trend(TrendLine)Overlay a trend line
.with_trend_color(s)Set trend line color
.with_trend_width(w)Set trend line stroke width
.with_equation()Annotate the plot with the regression equation
.with_correlation()Annotate the plot with R²
.with_x_err(iter)Symmetric X error bars
.with_x_err_asymmetric(iter)Asymmetric X error bars: (neg, pos) tuples
.with_y_err(iter)Symmetric Y error bars
.with_y_err_asymmetric(iter)Asymmetric Y error bars: (neg, pos) tuples
.with_band(lower, upper)Confidence band aligned to scatter x positions
.with_marker_opacity(f)Fill alpha: 0.0 = hollow, 1.0 = solid (default: solid)
.with_marker_stroke_width(w)Outline stroke at the fill color; None = no stroke (default)

MarkerShape variants

Circle · Square · Triangle · Diamond · Cross · Plus

TrendLine variants

Linear — fits y = mx + b by ordinary least squares.