226 lines
6.2 KiB
Rust
226 lines
6.2 KiB
Rust
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<BR: BufRead>(reader: BR) -> HashMap<(String, String), Vec<f64>> {
|
|
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::<f64>().unwrap());
|
|
}
|
|
}
|
|
ds
|
|
}
|
|
|
|
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|
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<String> = 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<f32> = 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))
|
|
.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))
|
|
.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))
|
|
.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()
|
|
}
|