summary refs log tree commit diff
diff options
context:
space:
mode:
authorEduard-Mihai Burtescu <edy.burt@gmail.com>2017-08-27 16:03:32 +0300
committerGitHub <noreply@github.com>2017-08-27 16:03:32 +0300
commitd0e5ce5d891eeda5c23b537ec8a93ba38fd8f268 (patch)
treedb1a81af9930875604e4feebf761bb3fd9c88b1e
parent(no commit message) (diff)
downloadheatmap-d0e5ce5d891eeda5c23b537ec8a93ba38fd8f268.tar.gz
heatmap-d0e5ce5d891eeda5c23b537ec8a93ba38fd8f268.zip
-rw-r--r--ascii-town-heatmap.rs88
1 files changed, 70 insertions, 18 deletions
diff --git a/ascii-town-heatmap.rs b/ascii-town-heatmap.rs
index 38ff338..4edc4d6 100644
--- a/ascii-town-heatmap.rs
+++ b/ascii-town-heatmap.rs
@@ -26,8 +26,8 @@ struct Tile {
 const TORUS_SZ: u32 = 512;
 
 // Tile width/height (in pixels).
-const TILE_W: u32 = 4;
-const TILE_H: u32 = 1;
+const TILE_W: u32 = 8;
+const TILE_H: u32 = 5;
 
 fn main() {
     // Read the `torus.csv` into a 2D array of `(access, modify)`.
@@ -44,40 +44,92 @@ fn main() {
             ..
         } = result.unwrap();
 
-        // HACK(eddyb) ignore 0,0 for analysis - too busy.
+        // Ignore 0,0, the values are too large.
         if (x, y) != (0, 0) {
             ord_a.insert(a);
             ord_m.insert(m);
         }
 
-        tiles[y][x] = (a, m);
+        // Handle old migrated values.
+        if a == 0 && m > 0 {
+            tiles[y][x] = (1, 0);
+        } else {
+            tiles[y][x] = (a, m);
+        }
     }
 
-    // Extract the maximum values.
-    // TODO(eddyb) chunk values for better representation.
-    let max_a = ord_a.iter().next_back().cloned().unwrap_or(0) as f64;
-    let max_m = ord_m.iter().next_back().cloned().unwrap_or(0) as f64;
+    // Chunk the values by splitting them at the largest gaps.
+    #[derive(PartialEq, Eq, PartialOrd, Ord)]
+    struct Chunk {
+        start: u64,
+    }
+    fn chunks(ord: BTreeSet<u64>, chunks: usize) -> Vec<Chunk> {
+        let mut gaps = BTreeSet::new();
+        let mut prev = 0;
+        let mut run = 0;
+        for &x in &ord {
+            let gap = x - prev;
+            if gap > 100 {
+                // Favor gaps with longer runs before them.
+                gaps.insert((gap + run * 2, x));
+                run = 0;
+            } else {
+                run += 1;
+            }
+            prev = x;
+        }
+        let mut chunks: Vec<_> = gaps.iter()
+            .rev()
+            .take(chunks)
+            .map(|&(_, start)| Chunk { start })
+            .collect();
+        chunks.sort();
+        chunks
+    }
+    let chunks_a = chunks(ord_a, 15);
+    let chunks_m = chunks(ord_m, 15);
 
     // Compose the heatmap image in-memory from the 2D array.
     let mut heatmap = image::ImageBuffer::new(TORUS_SZ * TILE_W, TORUS_SZ * TILE_H);
     let red = hsl::HSL::from_rgb(&[255, 0, 0]).h;
     // let green = hsl::HSL::from_rgb(&[0, 255, 0]).h;
     // let blue = hsl::HSL::from_rgb(&[0, 0, 255]).h;
+    let yellow = hsl::HSL::from_rgb(&[255, 255, 0]).h;
     for y in 0..TORUS_SZ {
         for x in 0..TORUS_SZ {
-            let (a, m) = tiles[y as usize][x as usize];
-            let a = (a as f64 / max_a).min(1.0).powf(0.1);
-            let m = (m as f64 / max_m).min(1.0).powf(0.1);
+            // Normalize a value to the [0, 1] interval based on chunks.
+            fn chunk_normalize(chunks: &[Chunk], x: u64) -> f64 {
+                if x == 0 {
+                    0.0
+                } else {
+                    // Find the chunk x is in.
+                    let i = chunks.iter().position(|c| c.start > x);
+                    let mut v = i.unwrap_or(chunks.len()) as f64;
+
+                    // Add the intra-chunk linear offset.
+                    if let Some(i) = i {
+                        let start = if i > 1 {
+                            chunks[i - 1].start
+                        } else {
+                            0
+                        };
+                        v += (x - start) as f64 / (chunks[i].start - start) as f64;
+                    }
 
-            // access => luminosity, modify => hue (green -> red)
-            // let h = green * (1.0 - m) + red * m;
-            // let s = 1.0;
-            // let l = a.max(m) * 0.5;
+                    // Normalize so all the chunks fit in [0, 1].
+                    (v / chunks.len() as f64).min(1.0)
+                }
+            }
+            // Get and normalize the values.
+            let (a, m) = tiles[y as usize][x as usize];
+            let a = chunk_normalize(&chunks_a, a).powf(0.2);
+            let m = chunk_normalize(&chunks_m, m).powf(0.2);
 
-            // access => luminosity, modify => saturation (grey -> red)
-            let h = red;
+            // access => luminosity
+            let l = (a + m).min(1.0) * 0.7;
+            // modify => saturation + hue (grey -> red -> yellow)
+            let h = red * (1.0 - m) + yellow * m;
             let s = m;
-            let l = a * 0.5;
 
             let (r, g, b) = hsl::HSL { h, s, l }.to_rgb();
             let rgb = image::Rgb([r, g, b]);