summaryrefslogtreecommitdiff
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]);