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