use itertools::Itertools; use plotters::data::fitting_range; use plotters::prelude::*; use std::collections::BTreeMap; use std::collections::HashMap; use std::env; use std::fs; use std::io::{self, prelude::*, BufReader}; fn read_data(reader: BR) -> HashMap<(String, String), Vec> { let mut ds = HashMap::new(); for l in reader.lines() { let line = l.unwrap(); let tuple: Vec<&str> = line.split('\t').collect(); if tuple.len() == 3 { let key = (String::from(tuple[0]), String::from(tuple[1])); let entry = ds.entry(key).or_insert_with(Vec::new); entry.push(tuple[2].parse::().unwrap()); } } ds } fn main() -> Result<(), Box> { let root = SVGBackend::new("plotters-doc-data/boxplot.svg", (1024, 768)).into_drawing_area(); root.fill(&WHITE)?; let root = root.margin(5, 5, 5, 5); let (upper, lower) = root.split_vertically(512); let args: Vec = env::args().collect(); let ds = if args.len() < 2 { read_data(io::Cursor::new(get_data())) } else { let file = fs::File::open(&args[1])?; read_data(BufReader::new(file)) }; let dataset: Vec<(String, String, Quartiles)> = ds .iter() .map(|(k, v)| (k.0.clone(), k.1.clone(), Quartiles::new(&v))) .collect(); let host_list: Vec<_> = dataset .iter() .unique_by(|x| x.0.clone()) .sorted_by(|a, b| b.2.median().partial_cmp(&a.2.median()).unwrap()) .map(|x| x.0.clone()) .collect(); let mut colors = (0..).map(Palette99::pick); let mut offsets = (-12..).step_by(24); let mut series = BTreeMap::new(); for x in dataset.iter() { let entry = series .entry(x.1.clone()) .or_insert_with(|| (Vec::new(), colors.next().unwrap(), offsets.next().unwrap())); entry.0.push((x.0.clone(), &x.2)); } let values: Vec = dataset .iter() .map(|x| x.2.values().to_vec()) .flatten() .collect(); let values_range = fitting_range(values.iter()); let mut chart = ChartBuilder::on(&upper) .x_label_area_size(40) .y_label_area_size(80) .caption("Ping Boxplot", ("sans-serif", 20).into_font()) .build_cartesian_2d( values_range.start - 1.0..values_range.end + 1.0, host_list[..].into_segmented(), )?; chart .configure_mesh() .x_desc("Ping, ms") .y_desc("Host") .y_labels(host_list.len()) .light_line_style(&WHITE) .draw()?; for (label, (values, style, offset)) in &series { chart .draw_series(values.iter().map(|x| { Boxplot::new_horizontal(SegmentValue::CenterOf(&x.0), &x.1) .width(20) .whisker_width(0.5) .style(style) .offset(*offset) }))? .label(label) .legend(move |(x, y)| Rectangle::new([(x, y - 6), (x + 12, y + 6)], style.filled())); } chart .configure_series_labels() .position(SeriesLabelPosition::UpperRight) .background_style(WHITE.filled()) .border_style(&BLACK.mix(0.5)) .legend_area_size(22) .draw()?; let drawing_areas = lower.split_evenly((1, 2)); let (left, right) = (&drawing_areas[0], &drawing_areas[1]); let quartiles_a = Quartiles::new(&[ 6.0, 7.0, 15.9, 36.9, 39.0, 40.0, 41.0, 42.0, 43.0, 47.0, 49.0, ]); let quartiles_b = Quartiles::new(&[16.0, 17.0, 50.0, 60.0, 40.2, 41.3, 42.7, 43.3, 47.0]); let ab_axis = ["a", "b"]; let values_range = fitting_range( quartiles_a .values() .iter() .chain(quartiles_b.values().iter()), ); let mut chart = ChartBuilder::on(&left) .x_label_area_size(40) .y_label_area_size(40) .caption("Vertical Boxplot", ("sans-serif", 20).into_font()) .build_cartesian_2d( ab_axis[..].into_segmented(), values_range.start - 10.0..values_range.end + 10.0, )?; chart.configure_mesh().light_line_style(&WHITE).draw()?; chart.draw_series(vec![ Boxplot::new_vertical(SegmentValue::CenterOf(&"a"), &quartiles_a), Boxplot::new_vertical(SegmentValue::CenterOf(&"b"), &quartiles_b), ])?; let mut chart = ChartBuilder::on(&right) .x_label_area_size(40) .y_label_area_size(40) .caption("Horizontal Boxplot", ("sans-serif", 20).into_font()) .build_cartesian_2d(-30f32..90f32, 0..3)?; chart.configure_mesh().light_line_style(&WHITE).draw()?; chart.draw_series(vec![ Boxplot::new_horizontal(1, &quartiles_a), Boxplot::new_horizontal(2, &Quartiles::new(&[30])), ])?; Ok(()) } fn get_data() -> String { String::from( " 1.1.1.1 wireless 41.6 1.1.1.1 wireless 32.5 1.1.1.1 wireless 33.1 1.1.1.1 wireless 32.3 1.1.1.1 wireless 36.7 1.1.1.1 wireless 32.0 1.1.1.1 wireless 33.1 1.1.1.1 wireless 32.0 1.1.1.1 wireless 32.9 1.1.1.1 wireless 32.7 1.1.1.1 wireless 34.5 1.1.1.1 wireless 36.5 1.1.1.1 wireless 31.9 1.1.1.1 wireless 33.7 1.1.1.1 wireless 32.6 1.1.1.1 wireless 35.1 8.8.8.8 wireless 42.3 8.8.8.8 wireless 32.9 8.8.8.8 wireless 32.9 8.8.8.8 wireless 34.3 8.8.8.8 wireless 32.0 8.8.8.8 wireless 33.3 8.8.8.8 wireless 31.5 8.8.8.8 wireless 33.1 8.8.8.8 wireless 33.2 8.8.8.8 wireless 35.9 8.8.8.8 wireless 42.3 8.8.8.8 wireless 34.1 8.8.8.8 wireless 34.2 8.8.8.8 wireless 34.2 8.8.8.8 wireless 32.4 8.8.8.8 wireless 33.0 1.1.1.1 wired 31.8 1.1.1.1 wired 28.6 1.1.1.1 wired 29.4 1.1.1.1 wired 28.8 1.1.1.1 wired 28.2 1.1.1.1 wired 28.8 1.1.1.1 wired 28.4 1.1.1.1 wired 28.6 1.1.1.1 wired 28.3 1.1.1.1 wired 28.5 1.1.1.1 wired 28.5 1.1.1.1 wired 28.5 1.1.1.1 wired 28.4 1.1.1.1 wired 28.6 1.1.1.1 wired 28.4 1.1.1.1 wired 28.9 8.8.8.8 wired 33.3 8.8.8.8 wired 28.4 8.8.8.8 wired 28.7 8.8.8.8 wired 29.1 8.8.8.8 wired 29.6 8.8.8.8 wired 28.9 8.8.8.8 wired 28.6 8.8.8.8 wired 29.3 8.8.8.8 wired 28.6 8.8.8.8 wired 29.1 8.8.8.8 wired 28.7 8.8.8.8 wired 28.3 8.8.8.8 wired 28.3 8.8.8.8 wired 28.6 8.8.8.8 wired 29.4 8.8.8.8 wired 33.1 ", ) } #[test] fn entry_point() { main().unwrap() }