1 use itertools::Itertools;
2 use plotters::data::fitting_range;
3 use plotters::prelude::*;
4 use std::collections::BTreeMap;
5 use std::collections::HashMap;
6 use std::env;
7 use std::fs;
8 use std::io::{self, prelude::*, BufReader};
9 
read_data<BR: BufRead>(reader: BR) -> HashMap<(String, String), Vec<f64>>10 fn read_data<BR: BufRead>(reader: BR) -> HashMap<(String, String), Vec<f64>> {
11     let mut ds = HashMap::new();
12     for l in reader.lines() {
13         let line = l.unwrap();
14         let tuple: Vec<&str> = line.split('\t').collect();
15         if tuple.len() == 3 {
16             let key = (String::from(tuple[0]), String::from(tuple[1]));
17             let entry = ds.entry(key).or_insert_with(Vec::new);
18             entry.push(tuple[2].parse::<f64>().unwrap());
19         }
20     }
21     ds
22 }
23 
24 const OUT_FILE_NAME: &'static str = "plotters-doc-data/boxplot.svg";
main() -> Result<(), Box<dyn std::error::Error>>25 fn main() -> Result<(), Box<dyn std::error::Error>> {
26     let root = SVGBackend::new(OUT_FILE_NAME, (1024, 768)).into_drawing_area();
27     root.fill(&WHITE)?;
28 
29     let root = root.margin(5, 5, 5, 5);
30 
31     let (upper, lower) = root.split_vertically(512);
32 
33     let args: Vec<String> = env::args().collect();
34 
35     let ds = if args.len() < 2 {
36         read_data(io::Cursor::new(get_data()))
37     } else {
38         let file = fs::File::open(&args[1])?;
39         read_data(BufReader::new(file))
40     };
41     let dataset: Vec<(String, String, Quartiles)> = ds
42         .iter()
43         .map(|(k, v)| (k.0.clone(), k.1.clone(), Quartiles::new(&v)))
44         .collect();
45 
46     let host_list: Vec<_> = dataset
47         .iter()
48         .unique_by(|x| x.0.clone())
49         .sorted_by(|a, b| b.2.median().partial_cmp(&a.2.median()).unwrap())
50         .map(|x| x.0.clone())
51         .collect();
52 
53     let mut colors = (0..).map(Palette99::pick);
54     let mut offsets = (-12..).step_by(24);
55     let mut series = BTreeMap::new();
56     for x in dataset.iter() {
57         let entry = series
58             .entry(x.1.clone())
59             .or_insert_with(|| (Vec::new(), colors.next().unwrap(), offsets.next().unwrap()));
60         entry.0.push((x.0.clone(), &x.2));
61     }
62 
63     let values: Vec<f32> = dataset
64         .iter()
65         .map(|x| x.2.values().to_vec())
66         .flatten()
67         .collect();
68     let values_range = fitting_range(values.iter());
69 
70     let mut chart = ChartBuilder::on(&upper)
71         .x_label_area_size(40)
72         .y_label_area_size(80)
73         .caption("Ping Boxplot", ("sans-serif", 20))
74         .build_cartesian_2d(
75             values_range.start - 1.0..values_range.end + 1.0,
76             host_list[..].into_segmented(),
77         )?;
78 
79     chart
80         .configure_mesh()
81         .x_desc("Ping, ms")
82         .y_desc("Host")
83         .y_labels(host_list.len())
84         .light_line_style(&WHITE)
85         .draw()?;
86 
87     for (label, (values, style, offset)) in &series {
88         chart
89             .draw_series(values.iter().map(|x| {
90                 Boxplot::new_horizontal(SegmentValue::CenterOf(&x.0), &x.1)
91                     .width(20)
92                     .whisker_width(0.5)
93                     .style(style)
94                     .offset(*offset)
95             }))?
96             .label(label)
97             .legend(move |(x, y)| Rectangle::new([(x, y - 6), (x + 12, y + 6)], style.filled()));
98     }
99     chart
100         .configure_series_labels()
101         .position(SeriesLabelPosition::UpperRight)
102         .background_style(WHITE.filled())
103         .border_style(&BLACK.mix(0.5))
104         .legend_area_size(22)
105         .draw()?;
106 
107     let drawing_areas = lower.split_evenly((1, 2));
108     let (left, right) = (&drawing_areas[0], &drawing_areas[1]);
109 
110     let quartiles_a = Quartiles::new(&[
111         6.0, 7.0, 15.9, 36.9, 39.0, 40.0, 41.0, 42.0, 43.0, 47.0, 49.0,
112     ]);
113     let quartiles_b = Quartiles::new(&[16.0, 17.0, 50.0, 60.0, 40.2, 41.3, 42.7, 43.3, 47.0]);
114 
115     let ab_axis = ["a", "b"];
116 
117     let values_range = fitting_range(
118         quartiles_a
119             .values()
120             .iter()
121             .chain(quartiles_b.values().iter()),
122     );
123     let mut chart = ChartBuilder::on(&left)
124         .x_label_area_size(40)
125         .y_label_area_size(40)
126         .caption("Vertical Boxplot", ("sans-serif", 20))
127         .build_cartesian_2d(
128             ab_axis[..].into_segmented(),
129             values_range.start - 10.0..values_range.end + 10.0,
130         )?;
131 
132     chart.configure_mesh().light_line_style(&WHITE).draw()?;
133     chart.draw_series(vec![
134         Boxplot::new_vertical(SegmentValue::CenterOf(&"a"), &quartiles_a),
135         Boxplot::new_vertical(SegmentValue::CenterOf(&"b"), &quartiles_b),
136     ])?;
137 
138     let mut chart = ChartBuilder::on(&right)
139         .x_label_area_size(40)
140         .y_label_area_size(40)
141         .caption("Horizontal Boxplot", ("sans-serif", 20))
142         .build_cartesian_2d(-30f32..90f32, 0..3)?;
143 
144     chart.configure_mesh().light_line_style(&WHITE).draw()?;
145     chart.draw_series(vec![
146         Boxplot::new_horizontal(1, &quartiles_a),
147         Boxplot::new_horizontal(2, &Quartiles::new(&[30])),
148     ])?;
149 
150     // To avoid the IO failure being ignored silently, we manually call the present function
151     root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");
152     println!("Result has been saved to {}", OUT_FILE_NAME);
153     Ok(())
154 }
155 
get_data() -> String156 fn get_data() -> String {
157     String::from(
158         "
159  1.1.1.1	wireless	41.6
160  1.1.1.1	wireless	32.5
161  1.1.1.1	wireless	33.1
162  1.1.1.1	wireless	32.3
163  1.1.1.1	wireless	36.7
164  1.1.1.1	wireless	32.0
165  1.1.1.1	wireless	33.1
166  1.1.1.1	wireless	32.0
167  1.1.1.1	wireless	32.9
168  1.1.1.1	wireless	32.7
169  1.1.1.1	wireless	34.5
170  1.1.1.1	wireless	36.5
171  1.1.1.1	wireless	31.9
172  1.1.1.1	wireless	33.7
173  1.1.1.1	wireless	32.6
174  1.1.1.1	wireless	35.1
175  8.8.8.8	wireless	42.3
176  8.8.8.8	wireless	32.9
177  8.8.8.8	wireless	32.9
178  8.8.8.8	wireless	34.3
179  8.8.8.8	wireless	32.0
180  8.8.8.8	wireless	33.3
181  8.8.8.8	wireless	31.5
182  8.8.8.8	wireless	33.1
183  8.8.8.8	wireless	33.2
184  8.8.8.8	wireless	35.9
185  8.8.8.8	wireless	42.3
186  8.8.8.8	wireless	34.1
187  8.8.8.8	wireless	34.2
188  8.8.8.8	wireless	34.2
189  8.8.8.8	wireless	32.4
190  8.8.8.8	wireless	33.0
191  1.1.1.1	wired	31.8
192  1.1.1.1	wired	28.6
193  1.1.1.1	wired	29.4
194  1.1.1.1	wired	28.8
195  1.1.1.1	wired	28.2
196  1.1.1.1	wired	28.8
197  1.1.1.1	wired	28.4
198  1.1.1.1	wired	28.6
199  1.1.1.1	wired	28.3
200  1.1.1.1	wired	28.5
201  1.1.1.1	wired	28.5
202  1.1.1.1	wired	28.5
203  1.1.1.1	wired	28.4
204  1.1.1.1	wired	28.6
205  1.1.1.1	wired	28.4
206  1.1.1.1	wired	28.9
207  8.8.8.8	wired	33.3
208  8.8.8.8	wired	28.4
209  8.8.8.8	wired	28.7
210  8.8.8.8	wired	29.1
211  8.8.8.8	wired	29.6
212  8.8.8.8	wired	28.9
213  8.8.8.8	wired	28.6
214  8.8.8.8	wired	29.3
215  8.8.8.8	wired	28.6
216  8.8.8.8	wired	29.1
217  8.8.8.8	wired	28.7
218  8.8.8.8	wired	28.3
219  8.8.8.8	wired	28.3
220  8.8.8.8	wired	28.6
221  8.8.8.8	wired	29.4
222  8.8.8.8	wired	33.1
223 ",
224     )
225 }
226 #[test]
entry_point()227 fn entry_point() {
228     main().unwrap()
229 }
230