From d0e5ce5d891eeda5c23b537ec8a93ba38fd8f268 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Sun, 27 Aug 2017 16:03:32 +0300 Subject: --- ascii-town-heatmap.rs | 88 ++++++++++++++++++++++++++++++++++++++++----------- 1 file 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, chunks: usize) -> Vec { + 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]); -- cgit 1.4.1