tawasuyu/pineal
Backend-agnostic GPU visualization — a catalog of painters over one Canvas trait, in Rust.
⎇ 514f17ba57e75d7e5db3a54d703d47377ca10fe6
00_unanchay/pineal/pineal-heatmap/src/matrix.rs
History
Who touched what
raw
feat: pineal standalone — catálogo de painters GPU sobre Llimphi (front-door liviano)
1//! `HeatmapMatrix` — matriz densa `width × height` de `f32`.
2
3/// Matriz de valores para un heatmap. `revision` se incrementa en cada
4/// mutación — los backends lo usan para invalidar la textura cacheada.
5#[derive(Debug, Clone)]
6pub struct HeatmapMatrix {
7 data: Vec<f32>,
8 width: usize,
9 height: usize,
10 revision: u64,
11}
12
13impl HeatmapMatrix {
14 /// Matriz de ceros de `width × height`.
15 pub fn new(width: usize, height: usize) -> Self {
16 Self { data: vec![0.0; width * height], width, height, revision: 0 }
17 }
18
19 /// Construye desde datos crudos. `None` si `data.len() != width*height`.
20 pub fn from_data(data: Vec<f32>, width: usize, height: usize) -> Option<Self> {
21 if data.len() != width * height {
22 return None;
23 }
24 Some(Self { data, width, height, revision: 0 })
25 }
26
27 pub fn width(&self) -> usize { self.width }
28 pub fn height(&self) -> usize { self.height }
29 pub fn revision(&self) -> u64 { self.revision }
30 pub fn data(&self) -> &[f32] { &self.data }
31
32 /// Valor en `(x, y)`. `0.0` si está fuera de rango.
33 pub fn get(&self, x: usize, y: usize) -> f32 {
34 if x >= self.width || y >= self.height {
35 return 0.0;
36 }
37 self.data[y * self.width + x]
38 }
39
40 /// Fija el valor en `(x, y)` e incrementa `revision`. No-op si está
41 /// fuera de rango.
42 pub fn set(&mut self, x: usize, y: usize, v: f32) {
43 if x >= self.width || y >= self.height {
44 return;
45 }
46 self.data[y * self.width + x] = v;
47 self.revision += 1;
48 }
49
50 /// Reemplaza todos los datos (mismas dimensiones) e incrementa
51 /// `revision`. No-op si la longitud no coincide.
52 pub fn replace_data(&mut self, data: Vec<f32>) {
53 if data.len() == self.width * self.height {
54 self.data = data;
55 self.revision += 1;
56 }
57 }
58
59 /// `(min, max)` de los valores. `(0.0, 0.0)` si la matriz está vacía.
60 pub fn min_max(&self) -> (f32, f32) {
61 let mut it = self.data.iter().copied();
62 let Some(first) = it.next() else { return (0.0, 0.0) };
63 it.fold((first, first), |(lo, hi), v| (lo.min(v), hi.max(v)))
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn from_data_checks_length() {
73 assert!(HeatmapMatrix::from_data(vec![1.0; 6], 2, 3).is_some());
74 assert!(HeatmapMatrix::from_data(vec![1.0; 5], 2, 3).is_none());
75 }
76
77 #[test]
78 fn get_set_and_revision() {
79 let mut m = HeatmapMatrix::new(3, 2);
80 assert_eq!(m.revision(), 0);
81 m.set(1, 1, 4.5);
82 assert_eq!(m.get(1, 1), 4.5);
83 assert_eq!(m.revision(), 1);
84 m.set(99, 99, 1.0); // fuera de rango → no-op
85 assert_eq!(m.revision(), 1);
86 }
87
88 #[test]
89 fn min_max_over_values() {
90 let m = HeatmapMatrix::from_data(vec![3.0, -1.0, 7.0, 2.0], 2, 2).unwrap();
91 assert_eq!(m.min_max(), (-1.0, 7.0));
92 }
93}