Last active
September 12, 2020 02:06
-
-
Save eddyb/56285d9403026f3c4cd35ad83f827a46 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// cargo-deps: hsl, image, csv = "1.0.0-beta.4", serde, serde_derive | |
// ~~~ PUBLIC DOMAIN ~~~ | |
// I, the copyright holder of this work, hereby release it | |
// into the public domain. This applies worldwide. | |
// In case this is not legally possible, I grant any entity | |
// the right to use this work for any purpose, without any | |
// conditions, unless such conditions are required by law. | |
extern crate csv; | |
extern crate hsl; | |
extern crate image; | |
extern crate serde; | |
#[macro_use] | |
extern crate serde_derive; | |
use std::collections::{BTreeSet, HashMap}; | |
use std::io; | |
#[derive(Debug, Deserialize)] | |
#[serde(rename_all = "camelCase")] | |
struct Tile { | |
tile_x: usize, | |
tile_y: usize, | |
create_time: u64, | |
modify_count: u64, | |
modify_time: u64, | |
access_count: u64, | |
access_time: u64, | |
} | |
// Torus size (in tiles). | |
const TORUS_SZ: u32 = 512; | |
// Tile width/height (in pixels). | |
const TILE_W: u32 = 8; | |
const TILE_H: u32 = 5; | |
fn main() { | |
// Read the `torus.csv` into a 2D array of `(access, modify)`. | |
// Also track the values we see in `BTreeSet` (for ordering). | |
let mut tiles = [[(0, 0); TORUS_SZ as usize]; TORUS_SZ as usize]; | |
let mut ord_a = BTreeSet::new(); | |
let mut ord_m = BTreeSet::new(); | |
for result in csv::Reader::from_reader(io::stdin()).deserialize() { | |
let Tile { | |
create_time, | |
modify_time, | |
access_count: mut a, | |
modify_count: mut m, | |
tile_x: x, | |
tile_y: y, | |
.. | |
} = result.unwrap(); | |
// Handle old migrated values. | |
if modify_time == create_time && m > 0 { | |
a += 1; | |
m = 0; | |
} | |
ord_a.insert(a); | |
ord_m.insert(m); | |
tiles[y][x] = (a, m); | |
} | |
// Normalize all values by mapping them to equally spaced values in [0, 1]. | |
let normal_map = |ord: BTreeSet<u64>| -> HashMap<u64, f64> { | |
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); | |
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 { | |
// Get and normalize the values. | |
let (a, m) = tiles[y as usize][x as usize]; | |
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) * 0.5).min(0.7); | |
// modify => saturation + hue (grey -> red -> yellow) | |
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(); | |
let rgb = image::Rgb([r, g, b]); | |
let coord = |x, dx, px| ((x * 2 + TORUS_SZ + 1) * px / 2 + dx) % (TORUS_SZ * px); | |
for dy in 0..TILE_H { | |
let y = coord(y, dy, TILE_H); | |
for dx in 0..TILE_W { | |
let x = coord(x, dx, TILE_W); | |
heatmap.put_pixel(x, y, rgb); | |
} | |
} | |
} | |
} | |
// Save the heatmap image. | |
heatmap.save("heatmap.png").unwrap(); | |
} |
😍
this is cool as heck
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
First:
Then: