use kuva::backend::svg::SvgBackend;
use kuva::plot::streamgraph::{StreamBaseline, StreamOrder, StreamgraphPlot};
use kuva::render::layout::Layout;
use kuva::render::palette::Palette;
use kuva::render::plots::Plot;
use kuva::render::render::render_multiple;
const OUT: &str = "docs/src/assets/streamgraph";
fn main() {
std::fs::create_dir_all(OUT).expect("could not create docs/src/assets/streamgraph");
microbiome_wiggle();
microbiome_symmetric();
microbiome_zero();
microbiome_normalized();
microbiome_by_total();
microbiome_stroke();
simple_linear();
println!("Streamgraph SVGs written to {OUT}/");
}
fn microbiome_data() -> (Vec<f64>, Vec<(&'static str, Vec<f64>)>) {
use std::f64::consts::PI;
let weeks: Vec<f64> = (1..=52).map(|w| w as f64).collect();
let n = weeks.len();
let mut rng_state: u64 = 42;
let mut rng = move || -> f64 {
rng_state = rng_state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
let bits = (rng_state >> 33) as f64;
(bits / (u32::MAX as f64)) * 2.0 - 1.0 };
let series: Vec<(&str, Vec<f64>)> = vec![
(
"Firmicutes",
(0..n)
.map(|i| {
let w = weeks[i];
(350.0 + 80.0 * (w * PI / 26.0).sin() + rng() * 20.0).max(10.0)
})
.collect(),
),
(
"Bacteroidetes",
(0..n)
.map(|i| {
let w = weeks[i];
(260.0 - 60.0 * (w * PI / 26.0).sin() + rng() * 20.0).max(10.0)
})
.collect(),
),
(
"Proteobacteria",
(0..n)
.map(|i| {
let w = weeks[i];
(180.0 + 30.0 * (w * PI / 13.0).cos() + rng() * 15.0).max(10.0)
})
.collect(),
),
(
"Actinobacteria",
(0..n)
.map(|i| {
let w = weeks[i];
(120.0 + 20.0 * (w * PI / 8.67).sin() + rng() * 10.0).max(5.0)
})
.collect(),
),
(
"Fusobacteria",
(0..n).map(|_| (80.0 + rng() * 12.0).max(5.0)).collect(),
),
(
"Verrucomicrobia",
(0..n)
.map(|i| {
let w = weeks[i];
(40.0 + 35.0 * (w * PI / 26.0 * 2.0 + 1.0).sin() + rng() * 8.0).max(5.0)
})
.collect(),
),
];
(weeks, series)
}
fn build_plot(weeks: Vec<f64>, series: Vec<(&str, Vec<f64>)>) -> StreamgraphPlot {
let pal = Palette::category10();
let mut sg = StreamgraphPlot::new().with_x(weeks);
for (i, (name, vals)) in series.into_iter().enumerate() {
sg = sg
.with_series(vals)
.with_color(pal[i].to_string())
.with_label(name);
}
sg
}
#[allow(dead_code)]
fn save(plot: StreamgraphPlot, layout: Layout, name: &str) {
let plots = vec![Plot::Streamgraph(plot)];
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/{name}"), svg).unwrap();
}
fn microbiome_wiggle() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series);
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — wiggle baseline")
.with_x_label("Week")
.with_y_label("Relative abundance");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/wiggle.svg"), svg).unwrap();
}
fn microbiome_symmetric() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series).with_baseline(StreamBaseline::Symmetric);
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — symmetric baseline")
.with_x_label("Week");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/symmetric.svg"), svg).unwrap();
}
fn microbiome_zero() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series).with_baseline(StreamBaseline::Zero);
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — zero baseline")
.with_x_label("Week")
.with_y_label("Abundance");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/zero.svg"), svg).unwrap();
}
fn microbiome_normalized() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series).with_normalized().with_legend("");
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — 100 % normalised")
.with_x_label("Week")
.with_y_label("Proportion (%)");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/normalized.svg"), svg).unwrap();
}
fn microbiome_by_total() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series)
.with_order(StreamOrder::ByTotal)
.with_legend("Phylum");
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — by-total ordering")
.with_x_label("Week");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/by_total.svg"), svg).unwrap();
}
fn microbiome_stroke() {
let (weeks, series) = microbiome_data();
let sg = build_plot(weeks, series)
.with_stroke()
.with_stream_labels(false)
.with_legend("");
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Gut microbiome — with strokes")
.with_x_label("Week");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/stroke.svg"), svg).unwrap();
}
fn simple_linear() {
let weeks: Vec<f64> = (1..=12).map(|w| w as f64).collect();
let pal = Palette::category10();
let sg = StreamgraphPlot::new()
.with_x(weeks)
.with_series([
10.0, 14.0, 18.0, 22.0, 20.0, 16.0, 12.0, 18.0, 24.0, 28.0, 22.0, 16.0,
])
.with_color(pal[0].to_string())
.with_label("Alpha")
.with_series([
5.0, 8.0, 12.0, 15.0, 14.0, 10.0, 8.0, 11.0, 16.0, 18.0, 14.0, 9.0,
])
.with_color(pal[1].to_string())
.with_label("Beta")
.with_series([3.0, 4.0, 6.0, 8.0, 9.0, 7.0, 5.0, 7.0, 9.0, 10.0, 8.0, 5.0])
.with_color(pal[2].to_string())
.with_label("Gamma")
.with_linear();
let plots = vec![Plot::Streamgraph(sg)];
let layout = Layout::auto_from_plots(&plots)
.with_title("Linear interpolation")
.with_x_label("Week");
let svg = SvgBackend.render_scene(&render_multiple(plots, layout));
std::fs::write(format!("{OUT}/linear.svg"), svg).unwrap();
}