From 8f82c4e224f198759c87750f3e50f576941fa1d7 Mon Sep 17 00:00:00 2001 From: Emilia Date: Sun, 18 Feb 2024 22:01:08 -0500 Subject: [PATCH] Linear volume heatmap visualization --- .../callbacks/main_window_callbacks.rs | 11 ++++ plate-tool-web/src/components/main_window.rs | 6 ++ plate-tool-web/src/components/plates/plate.rs | 57 +++++++++++++++---- plate-tool-web/src/components/plates/util.rs | 7 ++- plate-tool-web/src/components/states.rs | 5 +- 5 files changed, 73 insertions(+), 13 deletions(-) diff --git a/plate-tool-web/src/components/callbacks/main_window_callbacks.rs b/plate-tool-web/src/components/callbacks/main_window_callbacks.rs index 73b262c..a4a539c 100644 --- a/plate-tool-web/src/components/callbacks/main_window_callbacks.rs +++ b/plate-tool-web/src/components/callbacks/main_window_callbacks.rs @@ -43,6 +43,17 @@ pub fn toggle_in_transfer_hashes_callback( }) } +pub fn toggle_volume_heatmap_callback( + main_dispatch: Dispatch, +) -> Callback { + let main_dispatch = main_dispatch.clone(); + Callback::from(move |_| { + main_dispatch.reduce_mut(|state| { + state.preferences.volume_heatmap ^= true; + }) + }) +} + pub fn new_plate_dialog_callback( new_plate_dialog_is_open: UseStateHandle, ) -> NoParamsCallback { diff --git a/plate-tool-web/src/components/main_window.rs b/plate-tool-web/src/components/main_window.rs index 1b6175f..fbbba4f 100644 --- a/plate-tool-web/src/components/main_window.rs +++ b/plate-tool-web/src/components/main_window.rs @@ -42,6 +42,11 @@ pub fn MainWindow() -> Html { let main_dispatch = main_dispatch.clone(); main_window_callbacks::toggle_in_transfer_hashes_callback(main_dispatch) }; + + let toggle_volume_heatmap_callback = { + let main_dispatch = main_dispatch.clone(); + main_window_callbacks::toggle_volume_heatmap_callback(main_dispatch) + }; let new_plate_dialog_is_open = use_state_eq(|| false); let new_plate_dialog_callback = @@ -98,6 +103,7 @@ pub fn MainWindow() -> Html {
+
diff --git a/plate-tool-web/src/components/plates/plate.rs b/plate-tool-web/src/components/plates/plate.rs index e761007..4803972 100644 --- a/plate-tool-web/src/components/plates/plate.rs +++ b/plate-tool-web/src/components/plates/plate.rs @@ -40,26 +40,40 @@ pub fn Plate(props: &PlateProps) -> Html { m_end_handle.set(Some(pt2)); } - let tooltip_map = { + let tooltip_map: HashMap<(u8, u8), Vec<&Transfer>>; + let volume_map: HashMap<(u8, u8), f32>; + 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: HashMap<(u8, u8), Vec<&Transfer>> = HashMap::new(); + let mut tooltip_map_temp: HashMap<(u8, u8), Vec<&Transfer>> = HashMap::new(); + let mut volume_map_temp: HashMap<(u8,u8), f32> = 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.get_mut(&well) { + if let Some(val) = tooltip_map_temp.get_mut(&well) { val.push(transfer); } else { - tooltip_map.insert(well, vec![transfer]); + tooltip_map_temp.insert(well, vec![transfer]); } + if let Some(val) = volume_map_temp.get_mut(&well) { + *val += transfer.volume; + } else { + log::info!("well: {:?}, vol: {:?}", well, transfer.volume); + volume_map_temp.insert(well, transfer.volume); + } + volume_max_temp = f32::max(volume_max_temp, transfer.volume); } } - tooltip_map + tooltip_map = tooltip_map_temp; + volume_map = volume_map_temp; + volume_max = volume_max_temp; }; let wells = match props.ptype { @@ -120,18 +134,39 @@ pub fn Plate(props: &PlateProps) -> Html { 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(&(i,j)) + .and_then(|t| t.last()) + .map(|t| PALETTE.get_ordered(t.get_uuid(), &ordered_ids)) + } else { + volume_map.get(&(i,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(&(i,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(&(i,j)) + .map(|t| format!("Volume: {}", t)); + if let Some(val) = volume_sum { + if !out.is_empty() { out += "\n" } + out += &val; + } + out + }; html! { >().join(", ")))} + title={title} /> } }) diff --git a/plate-tool-web/src/components/plates/util.rs b/plate-tool-web/src/components/plates/util.rs index 5baf8ad..4238d90 100644 --- a/plate-tool-web/src/components/plates/util.rs +++ b/plate-tool-web/src/components/plates/util.rs @@ -26,7 +26,7 @@ impl ColorPalette { ] } - pub fn _get_u8(&self, t: u8) -> [f64; 3] { + fn get_u8(&self, t: u8) -> [f64; 3] { assert!(t > 0, "t must be greater than zero!"); self.get((2f64.powi(-(t.ilog2() as i32))) * (t as f64 + 0.5f64) - 1.0f64) } @@ -43,6 +43,11 @@ impl ColorPalette { self.get(Self::space_evenly(index)) } + pub fn get_linear(&self, t: f64, max: f64) -> [f64; 3] { + let scaled = t / max; + self.get(scaled) + } + fn space_evenly(x: usize) -> f64 { let e: usize = (x.ilog2() + 1) as usize; let d: usize = 2usize.pow(e as u32); diff --git a/plate-tool-web/src/components/states.rs b/plate-tool-web/src/components/states.rs index 7e9b3a3..f98d870 100644 --- a/plate-tool-web/src/components/states.rs +++ b/plate-tool-web/src/components/states.rs @@ -15,12 +15,15 @@ pub struct CurrentTransfer { #[derive(PartialEq, Clone, Copy, Serialize, Deserialize)] pub struct Preferences { + #[serde(default)] pub in_transfer_hashes: bool, + #[serde(default)] + pub volume_heatmap: bool, } impl Default for Preferences { fn default() -> Self { - Self { in_transfer_hashes: true } + Self { in_transfer_hashes: true, volume_heatmap: false } } }