use kuva::plot::scatter::{MarkerShape, ScatterPlot, TrendLine};
use kuva::backend::svg::SvgBackend;
use kuva::render::render::render_multiple;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
const OUT: &str = "docs/src/assets/scatter";
fn main() {
std::fs::create_dir_all(OUT).expect("could not create docs/src/assets/scatter");
basic();
trend();
confidence_band();
error_bars();
markers();
bubble();
per_point_colors();
multiple_series();
marker_semi_transparent();
marker_hollow();
println!("Scatter SVGs written to {OUT}/");
}
fn basic() {
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 svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/basic.svg"), svg).unwrap();
}
fn trend() {
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")
.with_equation()
.with_correlation();
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));
std::fs::write(format!("{OUT}/trend.svg"), svg).unwrap();
}
fn confidence_band() {
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));
std::fs::write(format!("{OUT}/confidence_band.svg"), svg).unwrap();
}
fn error_bars() {
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];
let y_err = vec![0.6_f64, 0.8, 0.4, 0.9, 0.5];
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));
std::fs::write(format!("{OUT}/error_bars.svg"), svg).unwrap();
}
fn markers() {
let y_offsets = [1.0_f64, 2.0, 3.0, 4.0, 5.0, 6.0];
let shapes = [
(MarkerShape::Circle, "Circle", "steelblue"),
(MarkerShape::Square, "Square", "crimson"),
(MarkerShape::Triangle, "Triangle", "seagreen"),
(MarkerShape::Diamond, "Diamond", "darkorange"),
(MarkerShape::Cross, "Cross", "purple"),
(MarkerShape::Plus, "Plus", "saddlebrown"),
];
let plots: Vec<Plot> = shapes
.iter()
.zip(y_offsets.iter())
.map(|((shape, label, color), y)| {
let data = vec![(1.0_f64, *y), (2.0, *y), (3.0, *y)];
Plot::Scatter(
ScatterPlot::new()
.with_data(data)
.with_color(*color)
.with_size(7.0)
.with_marker(*shape)
.with_legend(*label),
)
})
.collect();
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));
std::fs::write(format!("{OUT}/markers.svg"), svg).unwrap();
}
fn per_point_colors() {
let data = vec![
(1.0_f64, 1.5_f64), (1.5, 2.0), (2.0, 1.8),
(4.0, 4.5), (4.5, 5.0), (5.0, 4.8),
(7.0, 2.0), (7.5, 2.5), (8.0, 2.2),
];
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));
std::fs::write(format!("{OUT}/per_point_colors.svg"), svg).unwrap();
}
fn multiple_series() {
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_size(5.0)
.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_size(5.0)
.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));
std::fs::write(format!("{OUT}/multiple_series.svg"), svg).unwrap();
}
fn marker_semi_transparent() {
let mut seed: u64 = 9_123_456_789;
let mut lcg = || -> f64 {
seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
(seed >> 33) as f64 / ((1u64 << 31) as f64)
};
let gauss = |lcg: &mut dyn FnMut() -> f64| -> (f64, f64) {
let u1 = lcg().max(1e-10);
let u2 = lcg();
let z = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos();
let w = (-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).sin();
(z, w)
};
let centers = [(3.0_f64, 4.0_f64), (5.0, 5.5), (4.0, 3.0)];
let colors = ["steelblue", "tomato", "seagreen"];
let labels = ["Cluster A", "Cluster B", "Cluster C"];
let plots: Vec<Plot> = centers
.iter()
.zip(colors.iter())
.zip(labels.iter())
.map(|((&(cx, cy), &color), &label)| {
let mut data = Vec::new();
for _ in 0..200 {
let (z0, z1) = gauss(&mut lcg);
data.push((cx + z0 * 0.7, cy + z1 * 0.7));
}
Plot::Scatter(
ScatterPlot::new()
.with_data(data)
.with_color(color)
.with_size(5.0)
.with_marker_opacity(0.25)
.with_marker_stroke_width(0.7)
.with_legend(label),
)
})
.collect();
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));
std::fs::write(format!("{OUT}/marker_semi_transparent.svg"), svg).unwrap();
}
fn marker_hollow() {
let mut seed: u64 = 5_555_555_555;
let mut lcg = || -> f64 {
seed = seed.wrapping_mul(6_364_136_223_846_793_005).wrapping_add(1_442_695_040_888_963_407);
(seed >> 33) as f64 / ((1u64 << 31) as f64)
};
let data: Vec<(f64, f64)> = (0..800)
.map(|_| {
let angle = lcg() * 2.0 * std::f64::consts::PI;
let r = 3.0 + (lcg() - 0.5) * 1.2;
(r * angle.cos(), r * angle.sin())
})
.collect();
let plot = ScatterPlot::new()
.with_data(data)
.with_color("steelblue")
.with_size(4.0)
.with_marker_opacity(0.0)
.with_marker_stroke_width(1.0);
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));
std::fs::write(format!("{OUT}/marker_hollow.svg"), svg).unwrap();
}
fn bubble() {
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));
std::fs::write(format!("{OUT}/bubble.svg"), svg).unwrap();
}