aboutsummaryrefslogtreecommitdiff
path: root/examples/boxplot.rs
diff options
context:
space:
mode:
Diffstat (limited to 'examples/boxplot.rs')
-rw-r--r--examples/boxplot.rs222
1 files changed, 222 insertions, 0 deletions
diff --git a/examples/boxplot.rs b/examples/boxplot.rs
new file mode 100644
index 0000000..47f3b1a
--- /dev/null
+++ b/examples/boxplot.rs
@@ -0,0 +1,222 @@
+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 category = Category::new(
+ "Host",
+ 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).into_font())
+ .build_ranged(
+ values_range.start - 1.0..values_range.end + 1.0,
+ category.range(),
+ )?;
+
+ chart
+ .configure_mesh()
+ .x_desc("Ping, ms")
+ .y_desc(category.name())
+ .y_labels(category.len())
+ .line_style_2(&WHITE)
+ .draw()?;
+
+ for (label, (values, style, offset)) in &series {
+ chart
+ .draw_series(values.iter().map(|x| {
+ Boxplot::new_horizontal(category.get(&x.0).unwrap(), &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 category_ab = Category::new("", vec!["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_ranged(
+ category_ab.clone(),
+ values_range.start - 10.0..values_range.end + 10.0,
+ )?;
+
+ chart.configure_mesh().line_style_2(&WHITE).draw()?;
+ chart.draw_series(vec![
+ Boxplot::new_vertical(category_ab.get(&"a").unwrap(), &quartiles_a),
+ Boxplot::new_vertical(category_ab.get(&"b").unwrap(), &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_ranged(-30f32..90f32, 0..3)?;
+
+ chart.configure_mesh().line_style_2(&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
+",
+ )
+}