From 6bf855fc99e4a1a2841dd013c767ca12b4069d93 Mon Sep 17 00:00:00 2001 From: Eduard-Mihai Burtescu Date: Sun, 27 Aug 2017 16:55:59 +0300 Subject: --- ascii-town-heatmap.rs | 96 ++++++++++++++------------------------------------- 1 file changed, 26 insertions(+), 70 deletions(-) diff --git a/ascii-town-heatmap.rs b/ascii-town-heatmap.rs index 4edc4d6..cae11ef 100644 --- a/ascii-town-heatmap.rs +++ b/ascii-town-heatmap.rs @@ -7,7 +7,7 @@ extern crate serde; #[macro_use] extern crate serde_derive; -use std::collections::BTreeSet; +use std::collections::{BTreeSet, HashMap}; use std::io; #[derive(Debug, Deserialize)] @@ -37,57 +37,35 @@ fn main() { let mut ord_m = BTreeSet::new(); for result in csv::Reader::from_reader(io::stdin()).deserialize() { let Tile { - access_count: a, - modify_count: m, + create_time, + modify_time, + access_count: mut a, + modify_count: mut m, tile_x: x, tile_y: y, .. } = result.unwrap(); - // Ignore 0,0, the values are too large. - if (x, y) != (0, 0) { - ord_a.insert(a); - ord_m.insert(m); - } - // Handle old migrated values. - if a == 0 && m > 0 { - tiles[y][x] = (1, 0); - } else { - tiles[y][x] = (a, m); + if modify_time == create_time && m > 0 { + a += 1; + m = 0; } - } - // 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 + ord_a.insert(a); + ord_m.insert(m); + + tiles[y][x] = (a, m); } - let chunks_a = chunks(ord_a, 15); - let chunks_m = chunks(ord_m, 15); + + // Normalize all values by mapping them to equally spaced values in [0, 1]. + let normal_map = |ord: BTreeSet| -> HashMap { + ord.iter().enumerate().map(|(i, &x)| { + (x, i as f64 / (ord.len() - 1) as f64) + }).collect() + }; + let normal_a = normal_map(ord_a); + let normal_m = normal_map(ord_m); // Compose the heatmap image in-memory from the 2D array. let mut heatmap = image::ImageBuffer::new(TORUS_SZ * TILE_W, TORUS_SZ * TILE_H); @@ -97,38 +75,16 @@ fn main() { let yellow = hsl::HSL::from_rgb(&[255, 255, 0]).h; for y in 0..TORUS_SZ { for x in 0..TORUS_SZ { - // 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; - } - - // 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); + let a = normal_a[&a].powf(1.0 / 2.0); + let m = normal_m[&m].powf(1.0 / 3.0); // access => luminosity - let l = (a + m).min(1.0) * 0.7; + let l = ((a + m) * 0.5).min(0.7); // modify => saturation + hue (grey -> red -> yellow) - let h = red * (1.0 - m) + yellow * m; + let interp = |a, b, x| a * (1.0 - x) + b * x; + let h = interp(red, yellow, m.powf(4.0)); let s = m; let (r, g, b) = hsl::HSL { h, s, l }.to_rgb(); -- cgit 1.4.1