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(); }
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)); }
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)); }
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)); }
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.
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)); }
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)); }
Marker opacity and stroke
Two builders control the visual style of markers, enabling three distinct modes useful for dense datasets:
| Mode | Setting | Use case |
|---|---|---|
| Solid (default) | no calls needed | Small N or well-separated clusters |
| Semi-transparent | opacity < 1 + stroke | Dense regions pool colour; individual points stay visible |
| Hollow | opacity = 0.0 + stroke | Very 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)); }
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)); }
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)); }
API reference
| Method | Description |
|---|---|
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.