1
// use color_eyre::Result;
2
use svg::node::element::path::Data;
3
use svg::node::element::Element;
4
use svg::node::element::Path;
5
use svg::node::Text;
6
use svg::Document;
7
use svg::Node;
8

            
9
use crate::args::GraphArgs;
10
use crate::structs::PhasedMatrix;
11
use crate::utils::{select_only_longest_alleles, shared_lengths_by_majority};
12

            
13
6
pub fn matrix_graph_png(
14
6
    vcf: &PhasedMatrix,
15
6
    s: GraphArgs,
16
6
    mark_shorter_alleles: bool,
17
6
    decoy_samples: Option<Vec<String>>,
18
6
    order: &[usize],
19
6
) -> image::RgbaImage {
20
6
    let longest_alleles: Option<Vec<usize>> = match mark_shorter_alleles {
21
        true => {
22
            let shared_lengths = shared_lengths_by_majority(vcf, vcf.variant_idx());
23
            let only_longest_lengths = select_only_longest_alleles(&shared_lengths);
24
            Some(only_longest_lengths.iter().map(|s| s.index).collect())
25
        }
26
6
        false => None,
27
    };
28

            
29
6
    let width = vcf.matrix.ncols() * 5;
30
6
    let height = vcf.matrix.nrows() * 7;
31
6

            
32
6
    let mut imgbuf: image::RgbaImage =
33
6
        image::ImageBuffer::new(width as u32, height as u32);
34

            
35
    // for y in 0..vcf.matrix.nrows() {
36
168
    for (y, row_idx) in order.iter().enumerate() {
37
168
        let row = vcf.matrix.slice(ndarray::s![*row_idx, ..]);
38
10584
        for (x, gt) in row.iter().enumerate() {
39
10584
            let ny = y * 7;
40
10584
            let nx = x * 5;
41
74088
            for yy in ny..ny + 7 {
42
370440
                for xx in nx..nx + 5 {
43
370440
                    let pixel = imgbuf.get_pixel_mut(xx as u32, yy as u32);
44
370440
                    if yy == ny || yy == ny + 7 {
45
52920
                        *pixel = image::Rgba([255, 255, 255, 255]);
46
317520
                    } else if xx == nx || xx == nx + 5 {
47
63504
                        *pixel = image::Rgba([255, 255, 255, 255]);
48
63504
                    } else {
49
254016
                        match gt {
50
243936
                            0 => *pixel = image::Rgba([255, 0, 135, 255]),
51
10080
                            1 => *pixel = determine_line_color(vcf, &decoy_samples, &longest_alleles, y, x),
52
                            _ => panic!(),
53
                        }
54
                    }
55
                }
56
            }
57
        }
58
    }
59
6
    image::imageops::resize(
60
6
        &imgbuf,
61
6
        s.width,
62
6
        s.height,
63
6
        image::imageops::FilterType::CatmullRom,
64
6
    )
65
6
}
66

            
67
10080
pub fn determine_line_color<'a>(
68
10080
    vcf: &PhasedMatrix,
69
10080
    decoy_samples: &Option<Vec<String>>,
70
10080
    longest_alleles: &Option<Vec<usize>>,
71
10080
    row_idx: usize,
72
10080
    variant_idx: usize,
73
10080
) -> image::Rgba<u8> {
74
10080
    // Color based on if only longest or decoy samples coloring is wanted
75
10080
    let sample = vcf.get_sample_name(row_idx);
76
10080
    if variant_idx == vcf.variant_idx() {
77
2016
        return image::Rgba([0, 0, 0, 255])
78
8064
    }
79
8064
    match (decoy_samples, longest_alleles) {
80
        (Some(vec), None) => match vec.contains(&sample) {
81
            true => image::Rgba([0, 255, 0, 255]),
82
            false => image::Rgba([255, 255, 255, 255]),
83
        },
84
        (None, Some(vec)) => match vec.contains(&row_idx) {
85
            true => image::Rgba([255, 255, 255, 255]),
86
            false => image::Rgba([0, 255, 255, 255]),
87
        },
88
        (Some(vec), Some(_)) => match vec.contains(&sample) {
89
            true => image::Rgba([0, 255, 0, 255]),
90
            false => image::Rgba([255, 255, 255, 255]),
91
        },
92
8064
        (None, None) => image::Rgba([255, 255, 255, 255]),
93
    }
94
10080
}
95

            
96
#[derive(Clone, Debug)]
97
pub struct Point {
98
    gt: u8,
99
    y: f32,
100
    x: f32,
101
}
102

            
103
impl std::fmt::Display for Point {
104
1
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
105
1
        let line = format!("Point {{ x: {}, y: {} }}", self.x, self.y);
106
1
        write!(f, "{line}")
107
1
    }
108
}
109

            
110

            
111
#[derive(Debug)]
112
pub struct MatrixGraph<'a> {
113
    pub document: Document,
114
    pub s: GraphArgs,
115
    pub vcf: &'a PhasedMatrix,
116
    marker_width: f32,
117
    row_height: f32,
118
    btm_padding: f32,
119
    decoy_samples: Option<Vec<String>>,
120
    longest_alleles: Option<Vec<usize>>,
121
}
122

            
123
impl<'a> MatrixGraph<'a> {
124
30
    pub fn new(
125
30
        vcf: &'a PhasedMatrix,
126
30
        s: GraphArgs,
127
30
        decoy_samples: Option<Vec<String>>,
128
30
        mark_shorter_alleles: bool,
129
30
    ) -> Self {
130
30
        let document = Document::new()
131
30
            .set("viewBox", (0, 0, s.width, s.height))
132
30
            .set("style", format!("background-color:{}", s.background_color));
133

            
134
30
        let longest_alleles = match mark_shorter_alleles {
135
            true => {
136
6
                let shared_lengths = shared_lengths_by_majority(vcf, vcf.variant_idx());
137
6
                let only_longest_lengths = select_only_longest_alleles(&shared_lengths);
138
84
                Some(only_longest_lengths.iter().map(|s| s.index).collect())
139
            }
140
24
            false => None,
141
        };
142

            
143
30
        Self {
144
30
            document,
145
30
            vcf,
146
30
            marker_width: 0.0,
147
30
            row_height: 0.0,
148
30
            btm_padding: s.width as f32 * 0.015,
149
30
            decoy_samples,
150
30
            longest_alleles,
151
30
            s,
152
30
        }
153
30
    }
154

            
155
30
    pub fn draw_graph(&mut self, order: &[usize]) {
156
30
        let nrows = self.vcf.matrix.nrows();
157
30
        let ncols = self.vcf.matrix.ncols();
158
30
        self.marker_width = self.s.width as f32 / ncols as f32;
159
30
        self.row_height = (self.s.height as f32 - (self.s.height as f32) * 0.015) / nrows as f32;
160

            
161
588
        for (y, row_idx) in order.iter().enumerate() {
162
588
            let row = self.vcf.matrix.slice(ndarray::s![*row_idx, ..]);
163
18228
            for (x, gt) in row.iter().enumerate() {
164
18228
                self.create_box(
165
18228
                    Point {
166
18228
                        gt: *gt,
167
18228
                        x: x as f32,
168
18228
                        y: y as f32,
169
18228
                    },
170
18228
                    *row_idx,
171
18228
                    x,
172
18228
                );
173
18228
            }
174
        }
175

            
176
30
        self.draw_bottom_margin();
177
30
    }
178

            
179
30
    pub fn draw_bottom_margin(&mut self) {
180
30
        let y = self.s.height as f32 - self.btm_padding / 3.;
181
30

            
182
30
        let start = self.vcf.coords().first().unwrap();
183
30
        let stop = self.vcf.coords().last().unwrap();
184
30

            
185
30
        let mut element = Element::new("text");
186
30
        element.assign("x", self.s.width as f32 * 0.02);
187
30
        element.assign("y", y);
188
30
        element.assign("fill", "black");
189
30
        element.assign("font-size", format!("{}px", self.s.font_size));
190
30
        element.append(Text::new(format!("start: {}", start.pos)));
191
30
        self.document.append(element);
192
30

            
193
30
        let mut element = Element::new("text");
194
30
        element.assign("x", self.s.width as f32 * 0.33);
195
30
        element.assign("y", y);
196
30
        element.assign("fill", "black");
197
30
        element.assign("font-size", format!("{}px", self.s.font_size));
198
30
        element.append(Text::new(start.contig.clone()));
199
30
        self.document.append(element);
200
30

            
201
30
        let mut element = Element::new("text");
202
30
        element.assign("x", self.s.width as f32 * 0.56);
203
30
        element.assign("y", y);
204
30
        element.assign("fill", "black");
205
30
        element.assign("font-size", format!("{}px", self.s.font_size));
206
30
        element.append(Text::new(format!("markers: {}", self.vcf.ncoords())));
207
30
        self.document.append(element);
208
30

            
209
30
        let second_place = self.s.width as f32 * 0.86;
210
30
        let mut element = Element::new("text");
211
30
        element.assign("x", second_place);
212
30
        element.assign("y", y);
213
30
        element.assign("fill", "black");
214
30
        element.assign("font-size", format!("{}px", self.s.font_size));
215
30
        element.append(Text::new(format!("stop: {}", stop.pos)));
216
30
        self.document.append(element);
217
30
    }
218

            
219
18228
    pub fn create_box(&mut self, p: Point, row_idx: usize, var_idx: usize) {
220
18228
        let color = self.determine_line_color(&p, row_idx, var_idx);
221
18228

            
222
18228
        let x = (p.x + 1.0) * self.marker_width;
223
18228
        let y = (p.y + 1.0) * self.row_height;
224
18228

            
225
18228
        let vertical_line = Data::new()
226
18228
            .move_to((x, y))
227
18228
            .line_to((x, y + self.row_height / 1.5));
228
18228

            
229
18228
        let vertical_line = Path::new()
230
18228
            .set("fill", "none")
231
18228
            .set("stroke", color)
232
18228
            .set("opacity", 1)
233
18228
            .set("stroke-width", self.s.stroke_width)
234
18228
            .set("d", vertical_line);
235
18228

            
236
18228
        self.document.append(vertical_line);
237
18228
    }
238

            
239
18228
    pub fn determine_line_color(&self, p: &Point, row_idx: usize, var_idx: usize) -> &str {
240
18228
        // Color based on if only longest or decoy samples coloring is wanted
241
18228
        let sample = self.vcf.get_sample_name(row_idx);
242
18228
        if var_idx == self.vcf.variant_idx() && p.gt == 1 {
243
336
            return "#000"
244
17892
        }
245
17892
        match p.gt {
246
756
            0 => "#ff0087",
247
17136
            1 => match (&self.decoy_samples, &self.longest_alleles) {
248
                (Some(vec), None) => match vec.contains(&sample) {
249
                    true => "#00ff00",
250
                    false => "#fff",
251
                },
252
4872
                (None, Some(vec)) => match vec.contains(&row_idx) {
253
2520
                    true => "#fff",
254
2352
                    false => "#00ffff",
255
                },
256
                (Some(vec), Some(_)) => match vec.contains(&sample) {
257
                    true => "#00ff00",
258
                    false => "#fff",
259
                },
260
12264
                (None, None) => "#fff",
261
            },
262
            _ => panic!(),
263
        }
264
18228
    }
265
}
266

            
267
#[cfg(test)]
268
#[rustfmt::skip]
269
mod tests {
270
    use super::*;
271

            
272
1
    #[test]
273
1
    fn point_display() {
274
1
        let point = Point { x: 5.0, y: 6.0, gt: 1 };
275
1
        assert_eq!("Point { x: 5, y: 6 }".to_string(), format!("{point}"));
276
1
    }
277
}