summaryrefslogtreecommitdiff
path: root/static/src/_posts/2020-07-07-viz-3.md
blob: 7f5280d8b7f1822f2453fc5f33eb2d0ccc42bbe8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
---
title: >-
    Visualization 3
description: >-
    All the pixels.
series: viz
tags: tech art
---

<canvas id="canvas" style="padding-bottom: 2rem;"></canvas>

This visualization is built from the ground up. On every frame a random set of
pixels is chosen. Each chosen pixel calculates the average of its color and the
color of a random neighbor. Some random color drift is added in as well. It
replaces its own color with that calculated color.

Choosing a neighbor is done using the "asteroid rule", ie a pixel at the very
top row is considered to be the neighbor of the pixel on the bottom row of the
same column.

Without the asteroid rule the pixels would all eventually converge into a single
uniform color, generally a light blue, due to the colors at the edge, the reds,
being quickly averaged away. With the asteroid rule in place the canvas has no
edges, thus no position on the canvas is favored and balance can be maintained.

<script type="text/javascript">
let rectSize = 12;

function randn(n) {
    return Math.floor(Math.random() * n);
}

let canvas = document.getElementById("canvas");
canvas.width = window.innerWidth - (window.innerWidth % rectSize);
canvas.height = window.innerHeight- (window.innerHeight % rectSize);
let ctx = canvas.getContext("2d");

let w = canvas.width / rectSize;
let h = canvas.height / rectSize;

let matrices = new Array(2);
matrices[0] = new Array(w);
matrices[1] = new Array(w);
for (let x = 0; x < w; x++) {
    matrices[0][x] = new Array(h);
    matrices[1][x] = new Array(h);
    for (let y = 0; y < h; y++) {
        let el = {
            h: 360 * (x / w),
            s: "100%",
            l: "50%",
        };
        matrices[0][x][y] = el;
        matrices[1][x][y] = el;
    }
}

// draw initial canvas, from here on out only individual rectangles will be
// filled as they get updated.
for (let x = 0; x < w; x++) {
    for (let y = 0; y < h; y++) {
        let el = matrices[0][x][y];
        ctx.fillStyle = `hsl(${el.h}, ${el.s}, ${el.l})`;
        ctx.fillRect(x * rectSize, y * rectSize, rectSize, rectSize);
    }
}


let requestAnimationFrame = 
  window.requestAnimationFrame || 
  window.mozRequestAnimationFrame || 
  window.webkitRequestAnimationFrame || 
  window.msRequestAnimationFrame;

let neighbors = [
    [-1, -1], [0, -1], [1, -1],
    [-1, 0], [1, 0],
    [-1, 1], [0, 1], [1, 1],
];

function randNeighborAsteroid(matrix, x, y) {
    let neighborCoord = neighbors[randn(neighbors.length)];
    let neighborX = x+neighborCoord[0];
    let neighborY = y+neighborCoord[1];
    neighborX = (neighborX + w) % w;
    neighborY = (neighborY + h) % h;
    return matrix[neighborX][neighborY];
}

function randNeighbor(matrix, x, y) {
    while (true) {
        let neighborCoord = neighbors[randn(neighbors.length)];
        let neighborX = x+neighborCoord[0];
        let neighborY = y+neighborCoord[1];
        if (neighborX < 0 || neighborX >= w || neighborY < 0 || neighborY >= h) {
            continue;
        }
        return matrix[neighborX][neighborY];
    }
}

let drift = 10;
function genChildH(elA, elB) {
    // set the two h values, h1 <= h2
    let h1 = elA.h;
    let h2 = elB.h;
    if (h1 > h2) {
        h1 = elB.h;
        h2 = elA.h;
    }

    // diff must be between 0 (inclusive) and 360 (exclusive). If it's greater
    // than 180 then it's not the shortest path around, that must be the other
    // way around the circle.
    let hChild;
    let diff = h2 - h1;
    if (diff > 180) {
        diff = 360 - diff;
        hChild = h2 + (diff / 2);
    } else {
        hChild = h1 + (diff / 2);
    }

    hChild += (Math.random() * drift * 2) - drift;
    hChild = (hChild + 360) % 360;
    return hChild;
}

let tick = 0;
function doTick() {
    tick++;
    let currI = tick % 2;
    let curr = matrices[currI];
    let lastI = (tick - 1) % 2;
    let last = matrices[lastI];

    for (let i = 0; i < (w * h / 2); i++) {
        let x = randn(w);
        let y = randn(h);
        if (curr[x][y].lastTick == tick) continue;

        let neighbor = randNeighborAsteroid(last, x, y);
        curr[x][y].h = genChildH(curr[x][y], neighbor);
        curr[x][y].lastTick = tick;
        ctx.fillStyle = `hsl(${curr[x][y].h}, ${curr[x][y].s}, ${curr[x][y].l})`;
        ctx.fillRect(x * rectSize, y * rectSize, rectSize, rectSize);
    }

    matrices[currI] = curr;
    requestAnimationFrame(doTick);
}

requestAnimationFrame(doTick);

</script>