use std::path::Path;
use std::io::{BufRead, BufReader};
use image::{ImageBuffer, Rgba};
use crate::{Data, Error, Result};
pub struct Heatmap<T> {
pixel: usize,
data: Data<T>,
}
impl<T> Heatmap<T> {
pub fn new(data: Data<T>, pixel: usize) -> Self {
Heatmap { data, pixel }
}
pub fn image_height(&self) -> usize {
self.data.pixel_mapping_row(self.pixel).len()
}
pub fn image_width(&self) -> usize {
self.data.pixel_mapping_col(self.pixel).len()
}
}
impl Heatmap<f64> {
pub fn to_image(self) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
let (pixel, data) = (self.pixel, self.data);
let data: Data<Rgba<u8>> = data.into();
let context = Heatmap { pixel, data, };
context.to_image()
}
}
impl Heatmap<Rgba<u8>> {
pub fn to_image(self) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
let (pixel, data) = (self.pixel, self.data);
let context = Heatmap { pixel, data, };
context.into()
}
}
impl From<Heatmap<f64>> for ImageBuffer<Rgba<u8>, Vec<u8>> {
fn from(map: Heatmap<f64>) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
let data: Data<Rgba<u8>> = map.data.convert_with(crate::convert);
let context = Heatmap {
pixel: map.pixel,
data,
};
context.into()
}
}
impl From<Heatmap<Rgba<u8>>> for ImageBuffer<Rgba<u8>, Vec<u8>> {
fn from(map: Heatmap<Rgba<u8>>) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
let img_width = map.image_width() as u32;
let img_height = map.image_height() as u32;
log::debug!("Converting heatmap to image with pixel size: {} (image size: ({img_width}, {img_height}))", map.pixel);
let gap_color = Rgba([255, 255, 255, 0]);
let mut result_image = ImageBuffer::new(img_width, img_height);
let row_mapping = map.data.pixel_mapping_row(map.pixel);
let col_mapping = map.data.pixel_mapping_col(map.pixel);
for (py, row_index) in row_mapping.into_iter().enumerate() {
for (px, col_index) in col_mapping.iter().enumerate() {
let color = match (row_index, *col_index) {
(Some(ri), Some(ci)) =>
map.data.cell(ri, ci)
.copied().unwrap_or(gap_color),
_ => gap_color,
};
result_image.put_pixel(px as u32, py as u32, color);
}
}
result_image
}
}
pub enum Order {
Symmetric(Vec<String>),
Asymmetric(Vec<String>, Vec<String>),
}
impl Order {
pub fn load_symmetric<P: AsRef<Path>>(path: P) -> Result<Self> {
load_order_vec(path)
.map(Order::Symmetric)
}
pub fn load_asymmetric<P: AsRef<Path>>(row_path: P, column_path: P) -> Result<Self> {
let rows = load_order_vec(row_path)?;
let cols = load_order_vec(column_path)?;
Ok(Order::Asymmetric(rows, cols))
}
pub fn is_row_empty(&self) -> bool {
match self {
Order::Symmetric(items) => items.is_empty(),
Order::Asymmetric(rows, _) => rows.is_empty(),
}
}
pub fn is_column_empty(&self) -> bool {
match self {
Order::Symmetric(items) => items.is_empty(),
Order::Asymmetric(_, cols) => cols.is_empty(),
}
}
pub fn rows(&self) -> impl Iterator<Item = &String> {
match self {
Order::Symmetric(items) => items.iter(),
Order::Asymmetric(rows, _) => rows.iter(),
}
}
pub fn columns(&self) -> impl Iterator<Item = &String> {
match self {
Order::Symmetric(items) => items.iter(),
Order::Asymmetric(_, cols) => cols.iter(),
}
}
pub fn apply_assistant_line(self, gap: usize) -> Order {
if gap == 0 {
self
} else {
match self {
Order::Symmetric(items) => {
let new_items = insert_assistant_lines(items, gap);
Order::Symmetric(new_items)
},
Order::Asymmetric(rows, cols) => {
let new_rows = insert_assistant_lines(rows, gap);
let new_cols = insert_assistant_lines(cols, gap);
Order::Asymmetric(new_rows, new_cols)
}
}
}
}
}
fn insert_assistant_lines(items: Vec<String>, gap: usize) -> Vec<String> {
let mut result = Vec::new();
for (i, item) in items.into_iter().enumerate() {
if i > 0 && i % gap == 0 {
result.push("---".to_string());
}
result.push(item);
}
result
}
fn load_order_vec<P: AsRef<Path>>(path: P) -> Result<Vec<String>> {
let file = std::fs::File::open(path)
.map_err(Error::Io)?;
let reader = BufReader::new(file);
let mut items = Vec::new();
for line in reader.lines() {
let line = line.map_err(Error::Io)
.map(strip_comment)?;
items.push(line.to_string());
}
Ok(items)
}
fn strip_comment(line: String) -> String {
let mut result = String::with_capacity(line.len());
let mut escaped = false;
for c in line.chars() {
if escaped {
result.push(c);
escaped = false;
} else if c == '\\' {
escaped = true;
} else if c == '#' {
break;
} else {
result.push(c);
}
}
result.trim().to_string()
}