fastqc_rust/modules/
sequence_length_distribution.rs1use std::io;
5
6use crate::config::{Limits, LimitsExt};
7use crate::modules::QCModule;
8use crate::report::charts::line_graph::{render_line_graph, LineGraphData};
9use crate::report::charts::scaled_chart_width;
10use crate::sequence::Sequence;
11use crate::utils::format::java_format_double;
12
13pub struct SequenceLengthDistribution {
14 length_counts: Vec<u64>,
16 limits: Limits,
17 nogroup: bool,
18 computed: Option<ComputedDistribution>,
20}
21
22struct ComputedDistribution {
23 graph_counts: Vec<f64>,
24 x_categories: Vec<String>,
25}
26
27impl SequenceLengthDistribution {
28 pub fn new(limits: &Limits, nogroup: bool) -> Self {
29 SequenceLengthDistribution {
30 length_counts: Vec::new(),
31 limits: limits.clone(),
32 nogroup,
33 computed: None,
34 }
35 }
36
37 fn get_size_distribution(min: usize, max: usize, nogroup: bool) -> (usize, usize) {
40 if nogroup {
42 return (min, 1);
43 }
44
45 let mut base: usize = 1;
47
48 let divisions = [1, 2, 5];
53 let interval;
54
55 'outer: loop {
56 for &d in &divisions {
57 let tester = base * d;
58 if (max - min) / tester <= 50 {
59 interval = tester;
60 break 'outer;
61 }
62 }
63 base *= 10;
64 }
65
66 let basic_division = min / interval;
68 let starting = basic_division * interval;
69
70 (starting, interval)
71 }
72
73 fn calculate_distribution(&mut self) {
74 if self.computed.is_some() {
75 return;
76 }
77
78 let mut max_len: usize = 0;
79 let mut min_len: Option<usize> = None;
80
81 for i in 0..self.length_counts.len() {
83 if self.length_counts[i] > 0 {
84 if min_len.is_none() {
85 min_len = Some(i);
86 }
87 max_len = i;
88 }
89 }
90
91 let mut min_len = min_len.unwrap_or(0);
93
94 min_len = min_len.saturating_sub(1);
96 max_len += 1;
97
98 let (starting, interval) = Self::get_size_distribution(min_len, max_len, self.nogroup);
99
100 let mut categories = 0;
102 let mut current_value = starting;
103 while current_value <= max_len {
104 categories += 1;
105 current_value += interval;
106 }
107
108 let mut graph_counts = vec![0.0f64; categories];
109 let mut x_categories = Vec::with_capacity(categories);
110
111 for (i, gc) in graph_counts.iter_mut().enumerate() {
113 let min_value = starting + (interval * i);
114 let mut max_value = (starting + (interval * (i + 1))) - 1;
115
116 if max_value > max_len {
118 max_value = max_len;
119 }
120
121 for bp in min_value..=max_value {
123 if bp < self.length_counts.len() {
124 *gc += self.length_counts[bp] as f64;
125 }
126 }
127
128 if interval == 1 {
130 x_categories.push(format!("{}", min_value));
131 } else {
132 x_categories.push(format!("{}-{}", min_value, max_value));
133 }
134 }
135
136 self.computed = Some(ComputedDistribution {
137 graph_counts,
138 x_categories,
139 });
140 }
141
142 fn ensure_calculated(&self) -> &ComputedDistribution {
143 static DEFAULT: ComputedDistribution = ComputedDistribution {
146 graph_counts: Vec::new(),
147 x_categories: Vec::new(),
148 };
149 self.computed.as_ref().unwrap_or(&DEFAULT)
150 }
151}
152
153impl SequenceLengthDistribution {
154 fn build_chart_svg(&self) -> String {
155 let computed = self.ensure_calculated();
156 let max_val = computed
157 .graph_counts
158 .iter()
159 .cloned()
160 .fold(0.0_f64, f64::max);
161 let max_y = max_val;
163
164 render_line_graph(&LineGraphData {
166 width: scaled_chart_width(computed.graph_counts.len()),
167 data: vec![computed.graph_counts.clone()],
168 min_y: 0.0,
169 max_y,
170 x_label: "Sequence Length (bp)".to_string(),
171 series_names: vec!["Sequence Length".to_string()],
172 x_categories: computed.x_categories.clone(),
173 title: "Distribution of sequence lengths over all sequences".to_string(),
174 })
175 }
176}
177
178impl QCModule for SequenceLengthDistribution {
179 fn process_sequence(&mut self, sequence: &Sequence) {
180 self.computed = None;
181 let seq_len = sequence.sequence.len();
182
183 if seq_len + 2 > self.length_counts.len() {
185 self.length_counts.resize(seq_len + 2, 0);
186 }
187
188 self.length_counts[seq_len] += 1;
189 }
190
191 fn name(&self) -> &str {
192 "Sequence Length Distribution"
193 }
194
195 fn description(&self) -> &str {
196 "Shows the distribution of sequence length over all sequences"
197 }
198
199 fn reset(&mut self) {
200 self.length_counts.clear();
201 self.computed = None;
202 }
203
204 fn finalize(&mut self) {
205 self.calculate_distribution();
206 }
207
208 fn raises_error(&self) -> bool {
209 let threshold = self.limits.threshold("sequence_length\terror", 1.0);
211 if threshold == 0.0 {
212 return false;
213 }
214
215 if !self.length_counts.is_empty() && self.length_counts[0] > 0 {
217 return true;
218 }
219 false
220 }
221
222 fn raises_warning(&self) -> bool {
223 let threshold = self.limits.threshold("sequence_length\twarn", 1.0);
225 if threshold == 0.0 {
226 return false;
227 }
228
229 let mut seen_length = false;
231 for &count in &self.length_counts {
232 if count > 0 {
233 if seen_length {
234 return true;
235 }
236 seen_length = true;
237 }
238 }
239 false
240 }
241
242 fn ignore_filtered_sequences(&self) -> bool {
243 true
244 }
245
246 fn ignore_in_report(&self) -> bool {
247 self.limits.is_ignored("sequence_length")
248 }
249
250 fn write_text_report(&self, writer: &mut dyn io::Write) -> io::Result<()> {
251 let computed = self.ensure_calculated();
252
253 writeln!(writer, "#Length\tCount")?;
254 for i in 0..computed.x_categories.len() {
255 if (i == 0 || i == computed.x_categories.len() - 1) && computed.graph_counts[i] == 0.0 {
257 continue;
258 }
259 writeln!(
260 writer,
261 "{}\t{}",
262 computed.x_categories[i],
263 java_format_double(computed.graph_counts[i])
264 )?;
265 }
266 Ok(())
267 }
268
269 fn chart_image_name(&self) -> Option<&str> {
271 Some("sequence_length_distribution")
272 }
273 fn chart_alt_text(&self) -> Option<&str> {
274 Some("Sequence length distribution")
275 }
276 fn generate_chart_svg(&self) -> Option<String> {
277 Some(self.build_chart_svg())
278 }
279}