microBioRust_heatmap/
lib.rs

1//! # A Heatmap in Rust web assembly calling d3.js
2//!
3//! You will need to use wasm-pack to build instead of cargo 
4//!  wasm-pack build --target web
5//! And some way of serving locally 
6//!  http-server .
7//! It requires the index.html in the static directory
8//! Currently working with fixed data
9//! and a rusty colour theme
10#![allow(non_snake_case)]
11pub mod heatmap_data;
12pub mod canvas;
13
14// internal imports
15use canvas::drawing::draw_responsive_heatmap;
16use heatmap_data::HeatmapData;
17
18// external imports
19use wasm_bindgen::prelude::*;
20use wasm_bindgen::JsValue;
21use web_sys::{window, HtmlCanvasElement, CanvasRenderingContext2d};
22use web_sys::console;
23use std::rc::Rc;
24
25//returns a JsValue to javascript
26#[wasm_bindgen(start)]
27pub fn start() -> Result<(), JsValue> {
28    // Get the window and document
29    console::log_1(&JsValue::from_str(&format!("literal start")));
30    let window = window().ok_or(JsValue::from_str("should have a window in this context"))?;
31    let window = Rc::new(window);
32    let window_clone = Rc::clone(&window);
33    let document = window.document().ok_or(JsValue::from_str("no document"))?;
34    console::log_1(&JsValue::from_str(&format!("up in the start of the function")));
35    // Get the canvas element
36    let canvas = document
37        .get_element_by_id("heatmap")
38        .ok_or(JsValue::from_str("Canvas element not found"))?
39        .dyn_into::<HtmlCanvasElement>()?;
40    console::log_1(&JsValue::from_str(&format!("called the canvas")));
41    let heatmap_values = vec![
42        vec![2, 1, 0, 1, 0],  // row 1
43        vec![1, 2, 0, 0, 1],  // row 2
44        vec![2, 0, 1, 2, 1],  // row 3
45        vec![0, 0, 0, 2, 0],  // row 4
46        vec![1, 2, 0, 1, 1], // row 5
47    ];
48    console::log_1(&JsValue::from_str(&format!("called the heatmap vals")));
49    let x_labels: Vec<String> = vec!["A", "B", "C", "D", "E"].iter().map(|s| s.to_string()).collect();
50    let y_labels: Vec<String> = vec!["R1", "R2", "R3", "R4", "R5"].iter().map(|s| s.to_string()).collect();
51    
52    let num_rows = heatmap_values.len();      // Should be 5
53    let num_cols = heatmap_values[0].len();   // Should be 5
54    let mut heatmap_data = HeatmapData::new();
55    heatmap_data.values = heatmap_values.clone();
56    heatmap_data.x_labels = x_labels.clone();
57    heatmap_data.y_labels = y_labels.clone();
58    let box_size = 100.0;
59    let device_pixel_ratio = window.device_pixel_ratio();
60    console::log_1(&JsValue::from_str(&format!("num rows are {:?} num cols are {:?}", &num_rows, &num_cols)));
61    
62    // Dynamically set canvas size based on number of rows and columns
63    let canvas_width = num_cols as f64 * box_size;  // 6 columns * 50px
64    let canvas_height = num_rows as f64 * box_size; // 6 rows * 50px
65    canvas.set_width(canvas_width as u32);
66    canvas.set_height(canvas_height as u32);
67    console::log_1(&JsValue::from_str(&format!(
68        "Canvas width: {}, height: {}",
69        canvas.width(),
70        canvas.height()
71         )));
72
73    let context = canvas
74        .get_context("2d")?
75        .ok_or(JsValue::from_str("Context not found"))?
76        .dyn_into::<CanvasRenderingContext2d>()?;
77
78    // Define the heatmap matrix (3x3) with values representing different colors
79    context.scale(device_pixel_ratio, device_pixel_ratio)?;
80    
81    draw_responsive_heatmap(
82            &context,
83            heatmap_values.clone(),
84            x_labels.clone(),
85            y_labels.clone(),
86            canvas_width,
87            canvas_height,
88            device_pixel_ratio,
89        )?;
90
91    let handle_heatmap_resize = move || -> Result<(), JsValue> {
92        let new_width = window_clone.inner_width()
93            .map_err(|_| JsValue::from_str("error getting inner width"))?
94            .as_f64()
95            .ok_or(JsValue::from_str("error converting width to f64"))?;
96
97        let new_height = window_clone.inner_height()
98            .map_err(|_| JsValue::from_str("error getting inner height"))?
99            .as_f64()
100            .ok_or(JsValue::from_str("error converting height to f64"))?;
101
102        let canvas_new_width = (num_cols as f64 * box_size).min(new_width);
103        let canvas_new_height = (num_rows as f64 * box_size).min(new_height);
104
105        canvas.set_width(canvas_new_width as u32);
106        canvas.set_height(canvas_new_height as u32);
107
108        context.set_transform(1.0, 0.0, 0.0, 1.0, 0.0, 0.0)
109            .map_err(|_| JsValue::from_str("error setting transform"))?;
110        context.scale(device_pixel_ratio, device_pixel_ratio)
111            .map_err(|_| JsValue::from_str("error scaling context"))?;
112
113        draw_responsive_heatmap(
114            &context,
115            heatmap_values.clone(),
116            x_labels.clone(),
117            y_labels.clone(),
118            canvas_new_width,
119            canvas_new_height,
120            device_pixel_ratio,
121        )?;
122        Ok(())
123    };
124
125    // Wrap the closure_func to handle errors
126    let error_handled_heatmap_resize = move || {
127        if let Err(e) = handle_heatmap_resize() {
128            console::error_1(&e);
129        }
130    };
131   
132    let closure = Closure::wrap(Box::new(error_handled_heatmap_resize) as Box<dyn FnMut()>);
133    
134    window.add_event_listener_with_callback("resize", closure.as_ref().unchecked_ref())?;
135    closure.forget();
136
137    Ok(())
138}