fastqc_rust/report/charts/
tile_graph.rs1use super::{
9 approx_text_width, render_centered_title, svg_footer, svg_header, svg_line, svg_rect_filled,
10 svg_text, ChartColor, CHART_HEIGHT, CHART_WIDTH,
11};
12
13pub struct TileGraphData {
15 pub x_labels: Vec<String>,
16 pub tiles: Vec<i32>,
18 pub tile_base_means: Vec<Vec<f64>>,
21 pub color_scale_max: f64,
25}
26
27struct HotColdGradient {
36 colors: [(u8, u8, u8); 100],
37}
38
39impl HotColdGradient {
40 fn new() -> Self {
41 let mut colors = [(0u8, 0u8, 0u8); 100];
42
43 let min = -(50.0_f64.sqrt());
45 let max = (99.0 - 50.0_f64).sqrt();
46
47 for (c, color) in colors.iter_mut().enumerate() {
48 let actual_c = (c as f64 - 50.0).abs();
49 let mut corrected = actual_c.sqrt();
50 if c < 50 && corrected > 0.0 {
51 corrected = -corrected;
52 }
53 let (r, g, b) = Self::get_rgb(corrected, min, max);
54 *color = (r, g, b);
55 }
56
57 HotColdGradient { colors }
58 }
59
60 fn get_rgb(value: f64, min: f64, max: f64) -> (u8, u8, u8) {
63 let diff = max - min;
64
65 let (red, green, blue);
66
67 if value < min + diff * 0.25 {
68 red = 0;
70 blue = 200;
71 green = (200.0 * ((value - min) / (diff * 0.25))) as i32;
72 } else if value < min + diff * 0.5 {
73 red = 0;
75 green = 200;
76 blue = (200.0 - 200.0 * ((value - (min + diff * 0.25)) / (diff * 0.25))) as i32;
77 } else if value < min + diff * 0.75 {
78 blue = 0;
80 green = 200;
81 red = (200.0 * ((value - (min + diff * 0.5)) / (diff * 0.25))) as i32;
82 } else {
83 red = 200;
85 blue = 0;
86 green = (200.0 - 200.0 * ((value - (min + diff * 0.75)) / (diff * 0.25))) as i32;
87 }
88
89 (
90 red.clamp(0, 255) as u8,
91 green.clamp(0, 255) as u8,
92 blue.clamp(0, 255) as u8,
93 )
94 }
95
96 fn get_color(&self, value: f64, min: f64, max: f64) -> ChartColor {
98 let percentage = (((100.0 * (value - min)) / (max - min)) as i32).clamp(1, 100);
99 let (r, g, b) = self.colors[(percentage - 1) as usize];
100 ChartColor::new(r, g, b)
101 }
102}
103
104pub fn render_tile_graph(params: &TileGraphData) -> String {
112 let width = CHART_WIDTH;
113 let height = CHART_HEIGHT;
114 let num_tiles = params.tiles.len();
115 let num_bases = params.x_labels.len();
116
117 if num_tiles == 0 || num_bases == 0 {
118 let mut svg = svg_header(width, height);
120 svg.push_str(&svg_rect_filled(
121 0.0,
122 0.0,
123 width,
124 height,
125 &ChartColor::new(255, 255, 255),
126 ));
127 svg.push_str(svg_footer());
128 return svg;
129 }
130
131 let gradient = HotColdGradient::new();
132
133 let plot_height = height - 80.0;
136 let get_y =
137 |y: f64| -> f64 { (height - 40.0) - ((plot_height / num_tiles as f64) * y).floor() };
138
139 let black = ChartColor::new(0, 0, 0);
140
141 let mut svg = svg_header(width, height);
142 svg.push_str(&svg_rect_filled(
143 0.0,
144 0.0,
145 width,
146 height,
147 &ChartColor::new(255, 255, 255),
148 ));
149
150 let mut x_offset: f64 = 0.0;
152 for &tile in ¶ms.tiles {
153 let label = format!("{}", tile);
154 let w = approx_text_width(&label);
155 if w > x_offset {
156 x_offset = w;
157 }
158 }
159 x_offset += 5.0;
160
161 {
164 let font_size = 12.0_f64;
165 let mut last_y = 0.0_f64;
166 let ascent = 10.0; for (i, &tile) in params.tiles.iter().enumerate() {
168 let label = format!("{}", tile);
169 let this_y = get_y(i as f64);
170 if i > 0 && this_y + ascent > last_y {
172 continue;
173 }
174 let label_x = 2.0;
176 svg.push_str(&svg_text(
177 label_x,
178 this_y + font_size / 2.0,
179 &label,
180 &black,
181 false,
182 ));
183 last_y = this_y;
184 }
185 }
186
187 render_centered_title(&mut svg, "Quality per tile", x_offset, width);
189
190 svg.push_str(&svg_line(
192 x_offset,
193 height - 40.0,
194 width - 10.0,
195 height - 40.0,
196 &black,
197 1.0,
198 ));
199 svg.push_str(&svg_line(
200 x_offset,
201 height - 40.0,
202 x_offset,
203 40.0,
204 &black,
205 1.0,
206 ));
207
208 {
210 let x_label = "Position in read (bp)";
211 let x_label_w = approx_text_width(x_label);
212 svg.push_str(&svg_text(
213 width / 2.0 - x_label_w / 2.0,
214 height - 5.0,
215 x_label,
216 &black,
217 false,
218 ));
219 }
220
221 let base_width = ((width - x_offset - 10.0) / num_bases as f64)
225 .floor()
226 .max(1.0);
227
228 {
230 let mut last_x_label_end: f64 = 0.0;
231 for (base, label) in params.x_labels.iter().enumerate() {
232 let label_w = approx_text_width(label);
233 let label_x = (base_width / 2.0).trunc() + x_offset + (base_width * base as f64) - (label_w / 2.0);
235 if label_x > last_x_label_end {
236 svg.push_str(&svg_text(label_x, height - 25.0, label, &black, false));
237 last_x_label_end = label_x + label_w + 5.0;
238 }
239 }
240 }
241
242 let color_max = params.color_scale_max;
248 for tile in 0..num_tiles {
249 for base in 0..num_bases {
250 let deviation = params.tile_base_means[tile][base];
252 let color_value = -deviation; let color = gradient.get_color(color_value, 0.0, color_max);
254
255 let x = x_offset + base_width * base as f64;
256 let y = get_y((tile + 1) as f64);
258 let cell_height = get_y(tile as f64) - y;
259 svg.push_str(&svg_rect_filled(x, y, base_width, cell_height, &color));
260 }
261 }
262
263 svg.push_str(svg_footer());
264 svg
265}