#![allow(non_snake_case)] use std::collections::HashMap; use std::ops::Deref; use plate_tool_lib::transfer_region::TransferRegion; use plate_tool_lib::transfer_volume::TransferVolume; use yew::prelude::*; use yewdux::prelude::*; use crate::components::states::{CurrentTransfer, MainState}; use plate_tool_lib::plate::PlateType; use plate_tool_lib::transfer::Transfer; use plate_tool_lib::transfer_region::Region; use plate_tool_lib::Well; // Color Palette for the Source Plates, can be changed here use crate::components::plates::util::Palettes; const PALETTE: super::util::ColorPalette = Palettes::RAINBOW; use plate_tool_lib::util::num_to_letters; use super::plate_data::*; use super::plate_callbacks; #[function_component] pub fn Plate(props: &PlateProps) -> Html { let (main_state, _) = use_store::(); let (ct_state, ct_dispatch) = use_store::(); let m_start_handle: MStartHandle = use_state_eq(|| None); let m_end_handle: MEndHandle = use_state_eq(|| None); let m_stat_handle: MStatHandle = use_state_eq(|| false); if !(*m_stat_handle) { let region = match props.ptype { PlateType::Source => ct_state.transfer.transfer_region.source_region.clone(), PlateType::Destination => ct_state.transfer.transfer_region.dest_region.clone(), }; let (pt1, pt2) = match region { Region::Point(Well {row: x, col: y }) => ((x, y), (x, y)), Region::Rect(c1, c2) => ((c1.row, c1.col ), (c2.row, c2.col)), Region::Custom(_) => ((0, 0), (0, 0)), }; m_start_handle.set(Some(pt1)); m_end_handle.set(Some(pt2)); } let tooltip_map: HashMap>; let volume_map: HashMap; let volume_max: f32; { let transfers = main_state.transfers.iter().filter(|t| match props.ptype { PlateType::Source => t.source_id == props.source_plate.get_uuid(), PlateType::Destination => t.dest_id == props.destination_plate.get_uuid(), }); let mut tooltip_map_temp: HashMap> = HashMap::new(); let mut volume_map_temp: HashMap = HashMap::new(); let mut volume_max_temp: f32 = f32::NEG_INFINITY; for transfer in transfers { let wells = match props.ptype { PlateType::Source => transfer.transfer_region.get_source_wells(), PlateType::Destination => transfer.transfer_region.get_destination_wells(), }; for well in wells { if let Some(val) = tooltip_map_temp.get_mut(&well) { val.push(transfer); } else { tooltip_map_temp.insert(well, vec![transfer]); } let temp_volume: f32 = match &transfer.volume { TransferVolume::Single(x) => *x, TransferVolume::WellMap(wm) => { *match props.ptype { PlateType::Source => wm.source_only.get(&well), PlateType::Destination => wm.destination_only.get(&well) }.unwrap_or(&2.5f32) }, _ => unreachable!(), }; // Usage to be used as a volume multiplier; should be in [1, U32_MAX] // First convert to f64 (u32 can not fit in f32 directly) and then cast // to round into the closest f32. let usage: f32 = Into::::into(count_plate_usage(transfer, &well).unwrap_or(1u32).max(1u32)) as f32; if let Some(val) = volume_map_temp.get_mut(&well) { *val += temp_volume * usage; } else { volume_map_temp.insert(well, temp_volume * usage); } volume_max_temp = f32::max(volume_max_temp, *volume_map_temp.get(&well).expect("Just added")); } } tooltip_map = tooltip_map_temp; volume_map = volume_map_temp; volume_max = volume_max_temp; }; let wells = match props.ptype { PlateType::Source => ct_state.transfer.transfer_region.get_source_wells(), PlateType::Destination => ct_state.transfer.transfer_region.get_destination_wells(), }; let ordered_ids: Vec = { let mut ids: Vec = main_state.transfers.clone().iter().map(|x| x.id).collect(); ids.sort_unstable(); ids }; let mouse_callback = { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); let m_stat_handle = m_stat_handle.clone(); plate_callbacks::mouse_callback(m_start_handle, m_end_handle, m_stat_handle) }; let mouseup_callback = { let m_start_handle = m_start_handle.clone(); let m_end_handle = m_end_handle.clone(); plate_callbacks::mouseup_callback(m_start_handle, m_end_handle, m_stat_handle, ct_dispatch, props.ptype) }; let mouseleave_callback = Callback::clone(&mouseup_callback); let screenshot_callback = Callback::from(|_| { let _ = js_sys::eval("copy_screenshot_src()"); }); let width = match props.ptype { PlateType::Source => props.source_plate.plate.size().1, PlateType::Destination => props.destination_plate.plate.size().1, }; let height = match props.ptype { PlateType::Source => props.source_plate.plate.size().0, PlateType::Destination => props.destination_plate.plate.size().0, }; let pformat = match props.ptype { PlateType::Source => props.source_plate.plate.plate_format, PlateType::Destination => props.destination_plate.plate.plate_format, }; let column_header = { let headers = (1..=width) .map(|j| { html! { {format!("{:0>2}", j)} } }) .collect::(); html! {{ headers }} }; let rows = (1..=height) .map(|i| { let row_header = html! {{num_to_letters(i)}}; let row = (1..=width) .map(|j| { let color = { if !main_state.preferences.volume_heatmap { tooltip_map.get(&Well { row: i, col: j }) .and_then(|t| t.last()) .map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids)) } else { volume_map.get(&Well { row: i, col: j }) .map(|t| PALETTE.get_linear(*t as f64, volume_max as f64)) } }; let title = { let mut out = String::new(); let used_by = tooltip_map.get(&Well { row: i, col: j }).map(|transfers| format!("Used by: {}", transfers.iter().map(|t| t.name.clone()) .collect::>().join(", "))); if let Some(val) = used_by { out += &val; } let volume_sum = volume_map.get(&Well { row: i, col: j }) .map(|t| format!("Volume: {}", t)); if let Some(val) = volume_sum { if !out.is_empty() { out += "\n" } out += &val; } out }; html! { } }) .collect::(); html! { { row_header }{ row } } }) .collect::(); html! {
"source_plate", PlateType::Destination => "dest_plate", }, "W".to_owned()+&pformat.to_string()}}> { column_header } { rows }
} } #[derive(PartialEq, Properties)] pub struct PlateCellProps { i: u8, j: u8, selected: bool, mouse: Callback<(u8, u8, MouseEventType)>, in_transfer: Option, color: Option<[f64; 3]>, cell_height: f64, title: Option, } #[function_component] fn PlateCell(props: &PlateCellProps) -> Html { let selected_class = match props.selected { true => Some("current_select"), false => None, }; let in_transfer_class = match props.in_transfer { Some(true) => Some("in_transfer"), _ => None, }; let color = props.color.unwrap_or([255.0, 255.0, 255.0]); let mouse = Callback::clone(&props.mouse); let mouse2 = Callback::clone(&props.mouse); let (i, j) = (props.i, props.j); html! {
} } pub fn in_rect(corner1: Option<(u8, u8)>, corner2: Option<(u8, u8)>, pt: (u8, u8)) -> bool { if let (Some(c1), Some(c2)) = (corner1, corner2) { pt.0 <= u8::max(c1.0, c2.0) && pt.0 >= u8::min(c1.0, c2.0) && pt.1 <= u8::max(c1.1, c2.1) && pt.1 >= u8::min(c1.1, c2.1) } else { false } } fn count_plate_usage(transfer: &Transfer, well: &Well) -> Option { if let Region::Custom(_) = transfer.transfer_region.source_region { return None; } let map = transfer.transfer_region.calculate_map(); map(*well).and_then(|list| Some(list.len() as u32)) } #[cfg(test)] mod tests { use wasm_bindgen_test::*; use super::in_rect; // in_rect tests #[test] #[wasm_bindgen_test] fn test_in_rect1() { // Test in center of rect let c1 = (1, 1); let c2 = (10, 10); let pt = (5, 5); assert!(in_rect(Some(c1), Some(c2), pt)); // Order of the corners should not matter: assert!(in_rect(Some(c2), Some(c1), pt)); } #[test] #[wasm_bindgen_test] fn test_in_rect2() { // Test on top/bottom edges of rect let c1 = (1, 1); let c2 = (10, 10); let pt1 = (1, 5); let pt2 = (10, 5); assert!(in_rect(Some(c1), Some(c2), pt1)); assert!(in_rect(Some(c1), Some(c2), pt2)); // Order of the corners should not matter: assert!(in_rect(Some(c2), Some(c1), pt1)); assert!(in_rect(Some(c2), Some(c1), pt2)); } #[test] #[wasm_bindgen_test] fn test_in_rect3() { // Test on left/right edges of rect let c1 = (1, 1); let c2 = (10, 10); let pt1 = (5, 1); let pt2 = (5, 10); assert!(in_rect(Some(c1), Some(c2), pt1)); assert!(in_rect(Some(c1), Some(c2), pt2)); // Order of the corners should not matter: assert!(in_rect(Some(c2), Some(c1), pt1)); assert!(in_rect(Some(c2), Some(c1), pt2)); } #[test] #[wasm_bindgen_test] fn test_in_rect4() { // Test cases that should fail let c1 = (1, 1); let c2 = (10, 10); let pt1 = (0, 0); let pt2 = (15, 15); assert!(!in_rect(Some(c1), Some(c2), pt1)); assert!(!in_rect(Some(c1), Some(c2), pt2)); } }