use kuva::backend::svg::SvgBackend;
use kuva::plot::legend::{LegendEntry, LegendPosition, LegendShape};
use kuva::plot::scatter::ScatterPlot;
use kuva::plot::stacked_area::StackedAreaPlot;
use kuva::render::figure::Figure;
use kuva::render::layout::Layout;
use kuva::render::plots::Plot;
use kuva::render::render::render_multiple;
const OUT: &str = "docs/src/assets/legends";
fn months() -> Vec<f64> {
(1..=12).map(|m| m as f64).collect()
}
fn variant_sa(pos: LegendPosition) -> (Vec<Plot>, Layout) {
let sa = StackedAreaPlot::new()
.with_x(months())
.with_series([
420.0, 445.0, 398.0, 510.0, 488.0, 501.0, 467.0, 523.0, 495.0, 540.0, 518.0, 555.0,
])
.with_color("steelblue")
.with_legend("SNVs")
.with_series([
95.0, 102.0, 88.0, 115.0, 108.0, 112.0, 98.0, 125.0, 118.0, 130.0, 122.0, 140.0,
])
.with_color("orange")
.with_legend("Indels")
.with_series([
22.0, 25.0, 20.0, 28.0, 26.0, 27.0, 24.0, 31.0, 28.0, 33.0, 30.0, 35.0,
])
.with_color("mediumseagreen")
.with_legend("SVs")
.with_series([
15.0, 17.0, 14.0, 19.0, 18.0, 18.0, 16.0, 21.0, 19.0, 23.0, 21.0, 24.0,
])
.with_color("tomato")
.with_legend("CNVs");
let plots = vec![Plot::StackedArea(sa)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Monthly Variant Counts")
.with_x_label("Month")
.with_y_label("Variant count")
.with_legend_position(pos);
(plots, layout)
}
fn write(name: &str, plots: Vec<Plot>, layout: Layout) {
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/{name}.svg"), svg).unwrap();
println!(" wrote {OUT}/{name}.svg");
}
fn main() {
std::fs::create_dir_all(OUT).expect("could not create docs/src/assets/legends");
basic_auto();
positions();
no_box();
legend_title();
legend_groups();
custom_pos();
data_coords();
manual_entries();
size_override();
figure_shared();
println!("Legend SVGs written to {OUT}/");
}
fn basic_auto() {
let groups = [
(
"Cluster A",
"steelblue",
vec![(1.1, 2.3), (1.9, 3.1), (2.4, 2.7), (3.0, 3.8), (3.6, 3.2)],
),
(
"Cluster B",
"orange",
vec![(4.0, 1.2), (4.8, 1.8), (5.3, 1.4), (6.0, 2.0), (6.5, 1.6)],
),
(
"Cluster C",
"mediumseagreen",
vec![(2.0, 5.5), (2.8, 6.1), (3.5, 5.8), (4.3, 6.5), (5.0, 6.0)],
),
];
let mut plots: Vec<Plot> = Vec::new();
for (label, color, pts) in &groups {
plots.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts.iter().copied())
.with_color(*color)
.with_legend(*label)
.with_size(6.0),
));
}
let layout = Layout::auto_from_plots(&plots)
.with_title("Auto-Collected Legend")
.with_x_label("X")
.with_y_label("Y");
write("basic_auto", plots, layout);
}
fn positions() {
let (plots, layout) = variant_sa(LegendPosition::OutsideRightTop);
write("pos_outside_right_top", plots, layout);
let (plots, layout) = variant_sa(LegendPosition::InsideTopRight);
write("pos_inside_top_right", plots, layout);
let (plots, layout) = variant_sa(LegendPosition::InsideBottomLeft);
write("pos_inside_bottom_left", plots, layout);
let (plots, layout) = variant_sa(LegendPosition::OutsideLeftTop);
write("pos_outside_left_top", plots, layout);
let (plots, layout) = variant_sa(LegendPosition::OutsideBottomCenter);
write("pos_outside_bottom_center", plots, layout);
}
fn no_box() {
let sa = StackedAreaPlot::new()
.with_x(months())
.with_series([
420.0, 445.0, 398.0, 510.0, 488.0, 501.0, 467.0, 523.0, 495.0, 540.0, 518.0, 555.0,
])
.with_color("steelblue")
.with_legend("SNVs")
.with_series([
95.0, 102.0, 88.0, 115.0, 108.0, 112.0, 98.0, 125.0, 118.0, 130.0, 122.0, 140.0,
])
.with_color("orange")
.with_legend("Indels")
.with_series([
22.0, 25.0, 20.0, 28.0, 26.0, 27.0, 24.0, 31.0, 28.0, 33.0, 30.0, 35.0,
])
.with_color("mediumseagreen")
.with_legend("SVs")
.with_series([
15.0, 17.0, 14.0, 19.0, 18.0, 18.0, 16.0, 21.0, 19.0, 23.0, 21.0, 24.0,
])
.with_color("tomato")
.with_legend("CNVs");
let plots = vec![Plot::StackedArea(sa)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Legend Without Box")
.with_x_label("Month")
.with_y_label("Variant count")
.with_legend_position(LegendPosition::InsideTopRight)
.with_legend_box(false);
write("no_box", plots, layout);
}
fn legend_title() {
let (plots, layout) = variant_sa(LegendPosition::OutsideRightTop);
let layout = layout.with_legend_title("Variant type");
write("legend_title", plots, layout);
}
fn legend_groups() {
let groups = [
(
"Control-A",
"steelblue",
vec![(1.0, 2.1), (2.0, 3.4), (3.0, 2.9), (4.0, 4.2)],
),
(
"Control-B",
"#4e9fd4",
vec![(1.0, 1.8), (2.0, 2.9), (3.0, 2.5), (4.0, 3.6)],
),
(
"Treatment-A",
"tomato",
vec![(1.0, 2.5), (2.0, 4.1), (3.0, 5.3), (4.0, 6.8)],
),
(
"Treatment-B",
"#e06060",
vec![(1.0, 2.2), (2.0, 3.8), (3.0, 5.0), (4.0, 6.2)],
),
];
let mut plots: Vec<Plot> = Vec::new();
for (_, color, pts) in &groups {
plots.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts.iter().copied())
.with_color(*color)
.with_size(6.0),
));
}
let ctrl_entries = vec![
LegendEntry {
label: "Control-A".into(),
color: "steelblue".into(),
shape: LegendShape::Circle,
dasharray: None,
},
LegendEntry {
label: "Control-B".into(),
color: "#4e9fd4".into(),
shape: LegendShape::Circle,
dasharray: None,
},
];
let trt_entries = vec![
LegendEntry {
label: "Treatment-A".into(),
color: "tomato".into(),
shape: LegendShape::Circle,
dasharray: None,
},
LegendEntry {
label: "Treatment-B".into(),
color: "#e06060".into(),
shape: LegendShape::Circle,
dasharray: None,
},
];
let layout = Layout::auto_from_plots(&plots)
.with_title("Grouped Legend")
.with_x_label("Time (h)")
.with_y_label("Expression")
.with_legend_group("Controls", ctrl_entries)
.with_legend_group("Treatments", trt_entries);
write("legend_groups", plots, layout);
}
fn custom_pos() {
let (plots, layout) = variant_sa(LegendPosition::OutsideRightTop);
let layout = layout.with_legend_at(30.0, 30.0);
write("custom_pos", plots, layout);
}
fn data_coords() {
let (plots, layout) = variant_sa(LegendPosition::OutsideRightTop);
let layout = layout.with_legend_at_data(7.0, 450.0);
write("data_coords", plots, layout);
}
fn manual_entries() {
let series: &[(&str, &str, LegendShape, &[(f64, f64)])] = &[
(
"Healthy",
"steelblue",
LegendShape::Circle,
&[(1.2, 4.1), (2.0, 3.8), (2.8, 4.5), (3.7, 4.0), (4.5, 4.3)],
),
(
"At risk",
"orange",
LegendShape::Rect,
&[(1.5, 2.5), (2.3, 2.1), (3.1, 2.8), (4.0, 2.4), (4.8, 2.7)],
),
(
"Diseased",
"crimson",
LegendShape::Line,
&[(1.1, 0.9), (1.9, 1.3), (2.7, 0.7), (3.5, 1.1), (4.3, 0.8)],
),
];
let mut plots: Vec<Plot> = Vec::new();
for (_, color, _, pts) in series {
plots.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts.iter().copied())
.with_color(*color)
.with_size(6.0),
));
}
let entries: Vec<LegendEntry> = series
.iter()
.map(|(label, color, shape, _)| LegendEntry {
label: (*label).into(),
color: (*color).into(),
shape: shape.clone(),
dasharray: None,
})
.collect();
let layout = Layout::auto_from_plots(&plots)
.with_title("Manual Legend Entries")
.with_x_label("Biomarker A")
.with_y_label("Biomarker B")
.with_legend_entries(entries);
write("manual_entries", plots, layout);
}
fn size_override() {
let series: &[(&str, &str, &[(f64, f64)])] = &[
(
"Homo sapiens (reference)",
"steelblue",
&[(0.0, 1.0), (1.0, 1.8), (2.0, 2.4), (3.0, 2.9)],
),
(
"Mus musculus (knockout)",
"orange",
&[(0.0, 0.9), (1.0, 1.5), (2.0, 1.8), (3.0, 2.0)],
),
(
"Rattus norvegicus (ctrl)",
"crimson",
&[(0.0, 0.8), (1.0, 1.2), (2.0, 1.4), (3.0, 1.5)],
),
];
let mut plots: Vec<Plot> = Vec::new();
for (label, color, pts) in series {
plots.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts.iter().copied())
.with_color(*color)
.with_legend(*label)
.with_size(6.0),
));
}
let layout = Layout::auto_from_plots(&plots)
.with_title("Long Labels — Width Override")
.with_x_label("Week")
.with_y_label("Fold change")
.with_legend_width(230.0);
write("size_override", plots, layout);
}
fn figure_shared() {
let colors = [
("steelblue", "SNVs"),
("orange", "Indels"),
("mediumseagreen", "SVs"),
("tomato", "CNVs"),
];
let counts: [[f64; 6]; 4] = [
[420.0, 467.0, 510.0, 523.0, 540.0, 555.0],
[95.0, 98.0, 115.0, 125.0, 130.0, 140.0],
[22.0, 24.0, 28.0, 31.0, 33.0, 35.0],
[15.0, 16.0, 19.0, 21.0, 23.0, 24.0],
];
let mut panel_a: Vec<Plot> = Vec::new();
for (i, (color, label)) in colors.iter().enumerate() {
let pts: Vec<(f64, f64)> = counts[i]
.iter()
.enumerate()
.map(|(m, &v)| (m as f64 + 1.0, v))
.collect();
panel_a.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts)
.with_color(*color)
.with_legend(*label)
.with_size(6.0),
));
}
let counts_b: [[f64; 6]; 4] = [
[390.0, 440.0, 480.0, 505.0, 520.0, 535.0],
[88.0, 95.0, 105.0, 118.0, 124.0, 132.0],
[19.0, 22.0, 25.0, 28.0, 30.0, 33.0],
[12.0, 14.0, 17.0, 19.0, 21.0, 23.0],
];
let mut panel_b: Vec<Plot> = Vec::new();
for (i, (color, label)) in colors.iter().enumerate() {
let pts: Vec<(f64, f64)> = counts_b[i]
.iter()
.enumerate()
.map(|(m, &v)| (m as f64 + 1.0, v))
.collect();
panel_b.push(Plot::Scatter(
ScatterPlot::new()
.with_data(pts)
.with_color(*color)
.with_legend(*label)
.with_size(6.0),
));
}
let layout_a = Layout::auto_from_plots(&panel_a)
.with_title("Cohort H1")
.with_x_label("Month")
.with_y_label("Count");
let layout_b = Layout::auto_from_plots(&panel_b)
.with_title("Cohort H2")
.with_x_label("Month")
.with_y_label("Count");
let scene = Figure::new(1, 2)
.with_plots(vec![panel_a, panel_b])
.with_layouts(vec![layout_a, layout_b])
.with_shared_legend()
.render();
let svg = SvgBackend.render_scene(&scene);
std::fs::write(format!("{OUT}/figure_shared.svg"), svg).unwrap();
println!(" wrote {OUT}/figure_shared.svg");
}