• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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: &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.iter().flat_map(|x| x.2.values().to_vec()).collect();
64     let values_range = fitting_range(values.iter());
65 
66     let mut chart = ChartBuilder::on(&upper)
67         .x_label_area_size(40)
68         .y_label_area_size(80)
69         .caption("Ping Boxplot", ("sans-serif", 20))
70         .build_cartesian_2d(
71             values_range.start - 1.0..values_range.end + 1.0,
72             host_list[..].into_segmented(),
73         )?;
74 
75     chart
76         .configure_mesh()
77         .x_desc("Ping, ms")
78         .y_desc("Host")
79         .y_labels(host_list.len())
80         .light_line_style(WHITE)
81         .draw()?;
82 
83     for (label, (values, style, offset)) in &series {
84         chart
85             .draw_series(values.iter().map(|x| {
86                 Boxplot::new_horizontal(SegmentValue::CenterOf(&x.0), x.1)
87                     .width(20)
88                     .whisker_width(0.5)
89                     .style(style)
90                     .offset(*offset)
91             }))?
92             .label(label)
93             .legend(move |(x, y)| Rectangle::new([(x, y - 6), (x + 12, y + 6)], style.filled()));
94     }
95     chart
96         .configure_series_labels()
97         .position(SeriesLabelPosition::UpperRight)
98         .background_style(WHITE.filled())
99         .border_style(BLACK.mix(0.5))
100         .legend_area_size(22)
101         .draw()?;
102 
103     let drawing_areas = lower.split_evenly((1, 2));
104     let (left, right) = (&drawing_areas[0], &drawing_areas[1]);
105 
106     let quartiles_a = Quartiles::new(&[
107         6.0, 7.0, 15.9, 36.9, 39.0, 40.0, 41.0, 42.0, 43.0, 47.0, 49.0,
108     ]);
109     let quartiles_b = Quartiles::new(&[16.0, 17.0, 50.0, 60.0, 40.2, 41.3, 42.7, 43.3, 47.0]);
110 
111     let ab_axis = ["a", "b"];
112 
113     let values_range = fitting_range(
114         quartiles_a
115             .values()
116             .iter()
117             .chain(quartiles_b.values().iter()),
118     );
119     let mut chart = ChartBuilder::on(left)
120         .x_label_area_size(40)
121         .y_label_area_size(40)
122         .caption("Vertical Boxplot", ("sans-serif", 20))
123         .build_cartesian_2d(
124             ab_axis[..].into_segmented(),
125             values_range.start - 10.0..values_range.end + 10.0,
126         )?;
127 
128     chart.configure_mesh().light_line_style(WHITE).draw()?;
129     chart.draw_series(vec![
130         Boxplot::new_vertical(SegmentValue::CenterOf(&"a"), &quartiles_a),
131         Boxplot::new_vertical(SegmentValue::CenterOf(&"b"), &quartiles_b),
132     ])?;
133 
134     let mut chart = ChartBuilder::on(right)
135         .x_label_area_size(40)
136         .y_label_area_size(40)
137         .caption("Horizontal Boxplot", ("sans-serif", 20))
138         .build_cartesian_2d(-30f32..90f32, 0..3)?;
139 
140     chart.configure_mesh().light_line_style(WHITE).draw()?;
141     chart.draw_series(vec![
142         Boxplot::new_horizontal(1, &quartiles_a),
143         Boxplot::new_horizontal(2, &Quartiles::new(&[30])),
144     ])?;
145 
146     // To avoid the IO failure being ignored silently, we manually call the present function
147     root.present().expect("Unable to write result to file, please make sure 'plotters-doc-data' dir exists under current dir");
148     println!("Result has been saved to {}", OUT_FILE_NAME);
149     Ok(())
150 }
151 
get_data() -> String152 fn get_data() -> String {
153     String::from(
154         "
155  1.1.1.1	wireless	41.6
156  1.1.1.1	wireless	32.5
157  1.1.1.1	wireless	33.1
158  1.1.1.1	wireless	32.3
159  1.1.1.1	wireless	36.7
160  1.1.1.1	wireless	32.0
161  1.1.1.1	wireless	33.1
162  1.1.1.1	wireless	32.0
163  1.1.1.1	wireless	32.9
164  1.1.1.1	wireless	32.7
165  1.1.1.1	wireless	34.5
166  1.1.1.1	wireless	36.5
167  1.1.1.1	wireless	31.9
168  1.1.1.1	wireless	33.7
169  1.1.1.1	wireless	32.6
170  1.1.1.1	wireless	35.1
171  8.8.8.8	wireless	42.3
172  8.8.8.8	wireless	32.9
173  8.8.8.8	wireless	32.9
174  8.8.8.8	wireless	34.3
175  8.8.8.8	wireless	32.0
176  8.8.8.8	wireless	33.3
177  8.8.8.8	wireless	31.5
178  8.8.8.8	wireless	33.1
179  8.8.8.8	wireless	33.2
180  8.8.8.8	wireless	35.9
181  8.8.8.8	wireless	42.3
182  8.8.8.8	wireless	34.1
183  8.8.8.8	wireless	34.2
184  8.8.8.8	wireless	34.2
185  8.8.8.8	wireless	32.4
186  8.8.8.8	wireless	33.0
187  1.1.1.1	wired	31.8
188  1.1.1.1	wired	28.6
189  1.1.1.1	wired	29.4
190  1.1.1.1	wired	28.8
191  1.1.1.1	wired	28.2
192  1.1.1.1	wired	28.8
193  1.1.1.1	wired	28.4
194  1.1.1.1	wired	28.6
195  1.1.1.1	wired	28.3
196  1.1.1.1	wired	28.5
197  1.1.1.1	wired	28.5
198  1.1.1.1	wired	28.5
199  1.1.1.1	wired	28.4
200  1.1.1.1	wired	28.6
201  1.1.1.1	wired	28.4
202  1.1.1.1	wired	28.9
203  8.8.8.8	wired	33.3
204  8.8.8.8	wired	28.4
205  8.8.8.8	wired	28.7
206  8.8.8.8	wired	29.1
207  8.8.8.8	wired	29.6
208  8.8.8.8	wired	28.9
209  8.8.8.8	wired	28.6
210  8.8.8.8	wired	29.3
211  8.8.8.8	wired	28.6
212  8.8.8.8	wired	29.1
213  8.8.8.8	wired	28.7
214  8.8.8.8	wired	28.3
215  8.8.8.8	wired	28.3
216  8.8.8.8	wired	28.6
217  8.8.8.8	wired	29.4
218  8.8.8.8	wired	33.1
219 ",
220     )
221 }
222 #[test]
entry_point()223 fn entry_point() {
224     main().unwrap()
225 }
226