use super::{
approx_text_width, render_centered_title, svg_footer, svg_header, svg_line, svg_rect_filled,
svg_text, ChartColor, CHART_HEIGHT, CHART_WIDTH,
};
pub struct TileGraphData {
pub x_labels: Vec<String>,
pub tiles: Vec<i32>,
pub tile_base_means: Vec<Vec<f64>>,
pub color_scale_max: f64,
}
struct HotColdGradient {
colors: [(u8, u8, u8); 100],
}
impl HotColdGradient {
fn new() -> Self {
let mut colors = [(0u8, 0u8, 0u8); 100];
let min = -(50.0_f64.sqrt());
let max = (99.0 - 50.0_f64).sqrt();
for (c, color) in colors.iter_mut().enumerate() {
let actual_c = (c as f64 - 50.0).abs();
let mut corrected = actual_c.sqrt();
if c < 50 && corrected > 0.0 {
corrected = -corrected;
}
let (r, g, b) = Self::get_rgb(corrected, min, max);
*color = (r, g, b);
}
HotColdGradient { colors }
}
fn get_rgb(value: f64, min: f64, max: f64) -> (u8, u8, u8) {
let diff = max - min;
let (red, green, blue);
if value < min + diff * 0.25 {
red = 0;
blue = 200;
green = (200.0 * ((value - min) / (diff * 0.25))) as i32;
} else if value < min + diff * 0.5 {
red = 0;
green = 200;
blue = (200.0 - 200.0 * ((value - (min + diff * 0.25)) / (diff * 0.25))) as i32;
} else if value < min + diff * 0.75 {
blue = 0;
green = 200;
red = (200.0 * ((value - (min + diff * 0.5)) / (diff * 0.25))) as i32;
} else {
red = 200;
blue = 0;
green = (200.0 - 200.0 * ((value - (min + diff * 0.75)) / (diff * 0.25))) as i32;
}
(
red.clamp(0, 255) as u8,
green.clamp(0, 255) as u8,
blue.clamp(0, 255) as u8,
)
}
fn get_color(&self, value: f64, min: f64, max: f64) -> ChartColor {
let percentage = (((100.0 * (value - min)) / (max - min)) as i32).clamp(1, 100);
let (r, g, b) = self.colors[(percentage - 1) as usize];
ChartColor::new(r, g, b)
}
}
pub fn render_tile_graph(params: &TileGraphData) -> String {
let width = CHART_WIDTH;
let height = CHART_HEIGHT;
let num_tiles = params.tiles.len();
let num_bases = params.x_labels.len();
if num_tiles == 0 || num_bases == 0 {
let mut svg = svg_header(width, height);
svg.push_str(&svg_rect_filled(
0.0,
0.0,
width,
height,
&ChartColor::new(255, 255, 255),
));
svg.push_str(svg_footer());
return svg;
}
let gradient = HotColdGradient::new();
let plot_height = height - 80.0;
let get_y =
|y: f64| -> f64 { (height - 40.0) - ((plot_height / num_tiles as f64) * y).floor() };
let black = ChartColor::new(0, 0, 0);
let mut svg = svg_header(width, height);
svg.push_str(&svg_rect_filled(
0.0,
0.0,
width,
height,
&ChartColor::new(255, 255, 255),
));
let mut x_offset: f64 = 0.0;
for &tile in ¶ms.tiles {
let label = format!("{}", tile);
let w = approx_text_width(&label);
if w > x_offset {
x_offset = w;
}
}
x_offset += 5.0;
{
let font_size = 12.0_f64;
let mut last_y = 0.0_f64;
let ascent = 10.0; for (i, &tile) in params.tiles.iter().enumerate() {
let label = format!("{}", tile);
let this_y = get_y(i as f64);
if i > 0 && this_y + ascent > last_y {
continue;
}
let label_x = 2.0;
svg.push_str(&svg_text(
label_x,
this_y + font_size / 2.0,
&label,
&black,
false,
));
last_y = this_y;
}
}
render_centered_title(&mut svg, "Quality per tile", x_offset, width);
svg.push_str(&svg_line(
x_offset,
height - 40.0,
width - 10.0,
height - 40.0,
&black,
1.0,
));
svg.push_str(&svg_line(
x_offset,
height - 40.0,
x_offset,
40.0,
&black,
1.0,
));
{
let x_label = "Position in read (bp)";
let x_label_w = approx_text_width(x_label);
svg.push_str(&svg_text(
width / 2.0 - x_label_w / 2.0,
height - 5.0,
x_label,
&black,
false,
));
}
let base_width = ((width - x_offset - 10.0) / num_bases as f64)
.floor()
.max(1.0);
{
let mut last_x_label_end: f64 = 0.0;
for (base, label) in params.x_labels.iter().enumerate() {
let label_w = approx_text_width(label);
let label_x = (base_width / 2.0).trunc() + x_offset + (base_width * base as f64) - (label_w / 2.0);
if label_x > last_x_label_end {
svg.push_str(&svg_text(label_x, height - 25.0, label, &black, false));
last_x_label_end = label_x + label_w + 5.0;
}
}
}
let color_max = params.color_scale_max;
for tile in 0..num_tiles {
for base in 0..num_bases {
let deviation = params.tile_base_means[tile][base];
let color_value = -deviation; let color = gradient.get_color(color_value, 0.0, color_max);
let x = x_offset + base_width * base as f64;
let y = get_y((tile + 1) as f64);
let cell_height = get_y(tile as f64) - y;
svg.push_str(&svg_rect_filled(x, y, base_width, cell_height, &color));
}
}
svg.push_str(svg_footer());
svg
}