fastqc_rust/report/charts/
quality_boxplot.rs1use super::{
8 render_centered_title, svg_footer, svg_header, svg_line, svg_rect_filled, svg_rect_stroked,
9 ChartColor, ChartLayout,
10};
11
12pub struct QualityBoxPlotData {
14 pub means: Vec<f64>,
15 pub medians: Vec<f64>,
16 pub lower_quartile: Vec<f64>,
17 pub upper_quartile: Vec<f64>,
18 pub lowest: Vec<f64>,
20 pub highest: Vec<f64>,
22 pub min_y: f64,
23 pub max_y: f64,
24 pub y_interval: f64,
25 pub x_labels: Vec<String>,
26 pub title: String,
27}
28
29const GOOD: ChartColor = ChartColor::new(195, 230, 195);
32const BAD: ChartColor = ChartColor::new(230, 220, 195);
33const UGLY: ChartColor = ChartColor::new(230, 195, 195);
34const GOOD_DARK: ChartColor = ChartColor::new(175, 230, 175);
35const BAD_DARK: ChartColor = ChartColor::new(230, 215, 175);
36const UGLY_DARK: ChartColor = ChartColor::new(230, 175, 175);
37
38const BOX_FILL: ChartColor = ChartColor::new(240, 240, 0);
40const MEDIAN_COLOR: ChartColor = ChartColor::new(200, 0, 0);
42const MEAN_COLOR: ChartColor = ChartColor::new(0, 0, 200);
44
45pub fn render_quality_boxplot(params: &QualityBoxPlotData) -> String {
54 let layout = ChartLayout::new(params.min_y, params.max_y, params.y_interval);
55
56 let num_positions = params.means.len();
57 let base_width = layout.base_width(num_positions);
58
59 let mut svg = svg_header(layout.width, layout.height);
60
61 layout.render_background(&mut svg);
64 layout.render_y_labels(&mut svg);
65 render_centered_title(&mut svg, ¶ms.title, layout.x_offset, layout.width);
66
67 let black = ChartColor::new(0, 0, 0);
68
69 let mut last_x_label_end: f64 = 0.0;
71 for i in 0..num_positions {
72 let x = layout.x_offset + base_width * i as f64;
73
74 let (ugly, bad, good) = if i % 2 != 0 {
76 (&UGLY, &BAD, &GOOD)
77 } else {
78 (&UGLY_DARK, &BAD_DARK, &GOOD_DARK)
79 };
80
81 let ugly_top = layout.get_y(20.0);
83 let ugly_bottom = layout.get_y(layout.y_start);
84 if ugly_bottom > ugly_top {
85 svg.push_str(&svg_rect_filled(
86 x,
87 ugly_top,
88 base_width,
89 ugly_bottom - ugly_top,
90 ugly,
91 ));
92 }
93
94 let bad_top = layout.get_y(28.0);
96 let bad_bottom = layout.get_y(20.0);
97 if bad_bottom > bad_top {
98 svg.push_str(&svg_rect_filled(
99 x,
100 bad_top,
101 base_width,
102 bad_bottom - bad_top,
103 bad,
104 ));
105 }
106
107 let good_top = layout.get_y(params.max_y);
109 let good_bottom = layout.get_y(28.0);
110 if good_bottom > good_top {
111 svg.push_str(&svg_rect_filled(
112 x,
113 good_top,
114 base_width,
115 good_bottom - good_top,
116 good,
117 ));
118 }
119
120 if i < params.x_labels.len() {
122 last_x_label_end = layout.render_x_category_label_at(
123 &mut svg,
124 ¶ms.x_labels[i],
125 i,
126 base_width,
127 last_x_label_end,
128 );
129 }
130 }
131
132 layout.render_axes(&mut svg);
134 layout.render_x_axis_label(&mut svg, "Position in read (bp)");
135
136 for i in 0..num_positions {
138 let box_x = layout.x_offset + base_width * i as f64;
139 let box_top_y = layout.get_y(params.upper_quartile[i]);
140 let box_bottom_y = layout.get_y(params.lower_quartile[i]);
141 let upper_whisker_y = layout.get_y(params.highest[i]);
142 let lower_whisker_y = layout.get_y(params.lowest[i]);
143 let median_y = layout.get_y(params.medians[i]);
144 let center_x = box_x + base_width / 2.0;
145
146 let box_inset = 2.0;
148 let box_w = base_width - 4.0;
149 let box_h = box_bottom_y - box_top_y;
150 svg.push_str(&svg_rect_filled(
151 box_x + box_inset,
152 box_top_y,
153 box_w,
154 box_h,
155 &BOX_FILL,
156 ));
157 svg.push_str(&svg_rect_stroked(
158 box_x + box_inset,
159 box_top_y,
160 box_w,
161 box_h,
162 &black,
163 ));
164
165 svg.push_str(&svg_line(
167 center_x,
168 upper_whisker_y,
169 center_x,
170 box_top_y,
171 &black,
172 1.0,
173 ));
174 svg.push_str(&svg_line(
175 box_x + box_inset,
176 upper_whisker_y,
177 box_x + base_width - box_inset,
178 upper_whisker_y,
179 &black,
180 1.0,
181 ));
182
183 svg.push_str(&svg_line(
185 center_x,
186 lower_whisker_y,
187 center_x,
188 box_bottom_y,
189 &black,
190 1.0,
191 ));
192 svg.push_str(&svg_line(
193 box_x + box_inset,
194 lower_whisker_y,
195 box_x + base_width - box_inset,
196 lower_whisker_y,
197 &black,
198 1.0,
199 ));
200
201 svg.push_str(&svg_line(
203 box_x + box_inset,
204 median_y,
205 box_x + base_width - box_inset,
206 median_y,
207 &MEDIAN_COLOR,
208 1.0,
209 ));
210 }
211
212 let half_bw = layout.half_base_width(num_positions);
214 if num_positions >= 2 {
215 let mut prev_x = 0i32;
216 let mut prev_y = 0i32;
217 for i in 0..num_positions {
218 let x = (half_bw + layout.x_offset + (base_width * i as f64)) as i32;
219 let y = layout.get_y(params.means[i]) as i32;
220 if i > 0 {
221 svg.push_str(&format!(
222 "<line x1=\"{}\" y1=\"{}\" x2=\"{}\" y2=\"{}\" stroke=\"{}\" stroke-width=\"1\"/>\n",
223 prev_x, prev_y, x, y, MEAN_COLOR.to_rgb_string()
224 ));
225 }
226 prev_x = x;
227 prev_y = y;
228 }
229 }
230
231 svg.push_str(svg_footer());
232 svg
233}