Roll over the text.
S/o to http://stencilgram.com/.
Owes a huge debt to George Michael Brower’s FizzyText.
(function() {
function jetpack(d3) {
d3.selection.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
});
};
d3.transition.prototype.translate = function(xy) {
return this.attr('transform', function(d,i) {
return 'translate('+[typeof xy == 'function' ? xy(d,i) : xy]+')';
});
};
d3.selection.prototype.tspans = function(lines, lh) {
return this.selectAll('tspan')
.data(lines)
.enter()
.append('tspan')
.text(function(d) { return d; })
.attr('x', 0)
.attr('dy', function(d,i) { return i ? lh || 15 : 0; });
};
d3.selection.prototype.append =
d3.selection.enter.prototype.append = function(name) {
var n = d3_parse_attributes(name), s;
//console.log(name, n);
name = n.attr ? n.tag : name;
name = d3_selection_creator(name);
s = this.select(function() {
return this.appendChild(name.apply(this, arguments));
});
return n.attr ? s.attr(n.attr) : s;
};
d3.selection.prototype.insert =
d3.selection.enter.prototype.insert = function(name, before) {
var n = d3_parse_attributes(name), s;
name = n.attr ? n.tag : name;
name = d3_selection_creator(name);
before = d3_selection_selector(before);
s = this.select(function() {
return this.insertBefore(name.apply(this, arguments), before.apply(this, arguments) || null);
});
return n.attr ? s.attr(n.attr) : s;
};
var d3_parse_attributes_regex = /([\.#])/g;
function d3_parse_attributes(name) {
if (typeof name === "string") {
var attr = {},
parts = name.split(d3_parse_attributes_regex), p;
name = parts.shift();
while ((p = parts.shift())) {
if (p == '.') attr['class'] = attr['class'] ? attr['class'] + ' ' + parts.shift() : parts.shift();
else if (p == '#') attr.id = parts.shift();
}
return attr.id || attr['class'] ? { tag: name, attr: attr } : name;
}
return name;
}
function d3_selection_creator(name) {
return typeof name === "function" ? name : (name = d3.ns.qualify(name)).local ? function() {
return this.ownerDocument.createElementNS(name.space, name.local);
} : function() {
return this.ownerDocument.createElementNS(this.namespaceURI, name);
};
}
function d3_selection_selector(selector) {
return typeof selector === "function" ? selector : function() {
return this.querySelector(selector);
};
}
d3.wordwrap = function(line, maxCharactersPerLine) {
var w = line.split(' '),
lines = [],
words = [],
maxChars = maxCharactersPerLine || 40,
l = 0;
w.forEach(function(d) {
if (l+d.length > maxChars) {
lines.push(words.join(' '));
words.length = 0;
l = 0;
}
l += d.length;
words.push(d);
});
if (words.length) {
lines.push(words.join(' '));
}
return lines;
};
d3.ascendingKey = function(key) {
return typeof key == 'function' ? function (a, b) {
return key(a) < key(b) ? -1 : key(a) > key(b) ? 1 : key(a) >= key(b) ? 0 : NaN;
} : function (a, b) {
return a[key] < b[key] ? -1 : a[key] > b[key] ? 1 : a[key] >= b[key] ? 0 : NaN;
};
};
d3.descendingKey = function(key) {
return typeof key == 'function' ? function (a, b) {
return key(b) < key(a) ? -1 : key(b) > key(a) ? 1 : key(b) >= key(a) ? 0 : NaN;
} : function (a, b) {
return b[key] < a[key] ? -1 : b[key] > a[key] ? 1 : b[key] >= a[key] ? 0 : NaN;
};
};
d3.f = function(){
var functions = arguments;
//convert all string arguments into field accessors
var i = 0, l = functions.length;
while (i < l) {
if (typeof(functions[i]) === 'string' || typeof(functions[i]) === 'number'){
functions[i] = (function(str){ return function(d){ return d[str] }; })(functions[i])
}
i++;
}
//return composition of functions
return function(d) {
var i=0, l = functions.length;
while (i++ < l) d = functions[i-1].call(this, d);
return d;
};
};
// store d3.f as convenient unicode character function (alt-f on macs)
if (!window.hasOwnProperty('ƒ')) window.ƒ = d3.f;
// this tweak allows setting a listener for multiple events, jquery style
var d3_selection_on = d3.selection.prototype.on;
d3.selection.prototype.on = function(type, listener, capture) {
if (typeof type == 'string' && type.indexOf(' ') > -1) {
type = type.split(' ');
for (var i = 0; i<type.length; i++) {
d3_selection_on.apply(this, [type[i], listener, capture]);
}
} else {
d3_selection_on.apply(this, [type, listener, capture]);
}
return this;
};
// for heaven's sake, let's add prop as alias for property
d3.selection.prototype.prop = d3.selection.prototype.property;
}
if (typeof d3 === 'object' && d3.version) jetpack(d3);
else if (typeof define === 'function' && define.amd) {
define(['d3'], jetpack);
}
})();
function CanvasText(container, message) {
var width = container.offsetWidth;
var height = container.offsetHeight;
// This is the context we use to get a bitmap of text using
// the getImageData function.
var r = document.createElement('canvas');
var s = r.getContext('2d');
r.setAttribute('width', width);
r.setAttribute('height', height);
container.appendChild(r);
// Stores bitmap image
var pixels = [];
s.font = "800 180px helvetica, arial, serif";
s.textBaseline = 'middle';
s.textAlign = "center";
// This function creates a bitmap of pixels based on your message
// It's called every time we change the message property.
var createBitmap = function (msg) {
s.fillStyle = "#fff";
s.fillRect(0, 0, width, height);
s.fillStyle = "#222";
s.fillText(msg, width/2, height/2);
// Pull reference
var imageData = s.getImageData(0, 0, width, height);
pixels = imageData.data;
};
// Returns x, y coordinates for a given index in the pixel array.
var getPosition = function (i) {
return {
x: (i - (width * 4) * Math.floor(i / (width * 4))) / 4,
y: Math.floor(i / (width * 4))
};
};
// Returns a color for a given pixel in the pixel array.
this.getColor = function (x, y) {
var base = (Math.floor(y) * width + Math.floor(x)) * 4;
var c = {
r: pixels[base + 0],
g: pixels[base + 1],
b: pixels[base + 2],
a: pixels[base + 3]
};
return "rgb(" + c.r + "," + c.g + "," + c.b + ")";
};
// createBitmap(message);
var img = new Image(); // Create new img element
img.addEventListener("load", function() {
// execute drawImage statements here
s.drawImage(img,0,0);
var imageData = s.getImageData(0, 0, width, height);
pixels = imageData.data;
}, false);
img.src = 'bloomberg.png'; // Set source path
}
// http://mrl.nyu.edu/~perlin/noise/
var ImprovedNoise = function () {
var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,
23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,
174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,
133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,
89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,
202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,
248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,
14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,
93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];
for ( var i = 0; i < 256 ; i++ ) {
p[ 256 + i ] = p[ i ];
}
function fade( t ) {
return t * t * t * ( t * ( t * 6 - 15 ) + 10 );
}
function lerp( t, a, b ) {
return a + t * ( b - a );
}
function grad( hash, x, y, z ) {
var h = hash & 15;
var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
return ( ( h & 1 ) == 0 ? u : -u ) + ( ( h & 2 ) == 0 ? v : -v );
}
return {
noise: function ( x, y, z ) {
var floorX = Math.floor( x ), floorY = Math.floor( y ), floorZ = Math.floor( z );
var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255;
x -= floorX;
y -= floorY;
z -= floorZ;
var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1;
var u = fade( x ), v = fade( y ), w = fade( z );
var A = p[ X ] + Y, AA = p[ A ] + Z, AB = p[ A + 1 ] + Z, B = p[ X + 1 ] + Y, BA = p[ B ] + Z, BB = p[ B + 1 ] + Z;
return lerp( w, lerp( v, lerp( u, grad( p[ AA ], x, y, z ),
grad( p[ BA ], xMinus1, y, z ) ),
lerp( u, grad( p[ AB ], x, yMinus1, z ),
grad( p[ BB ], xMinus1, yMinus1, z ) ) ),
lerp( v, lerp( u, grad( p[ AA + 1 ], x, y, zMinus1 ),
grad( p[ BA + 1 ], xMinus1, y, z - 1 ) ),
lerp( u, grad( p[ AB + 1 ], x, yMinus1, zMinus1 ),
grad( p[ BB + 1 ], xMinus1, yMinus1, zMinus1 ) ) ) );
}
}
}
var currentRandom = Math.random;
// Pseudo-random generator
function Marsaglia(i1, i2) {
// from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
var z=i1 || 362436069, w= i2 || 521288629;
var nextInt = function() {
z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF;
w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF;
return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF;
};
this.nextDouble = function() {
var i = nextInt() / 4294967296;
return i < 0 ? 1 + i : i;
};
this.nextInt = nextInt;
}
Marsaglia.createRandomized = function() {
var now = new Date();
return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
};
// Noise functions and helpers
function PerlinNoise(seed) {
var rnd = seed !== undefined ? new Marsaglia(seed) : Marsaglia.createRandomized();
var i, j;
// http://www.noisemachine.com/talk1/17b.html
// http://mrl.nyu.edu/~perlin/noise/
// generate permutation
var p = new Array(512);
for(i=0;i<256;++i) { p[i] = i; }
for(i=0;i<256;++i) { var t = p[j = rnd.nextInt() & 0xFF]; p[j] = p[i]; p[i] = t; }
// copy to avoid taking mod in p[0];
for(i=0;i<256;++i) { p[i + 256] = p[i]; }
function grad3d(i,x,y,z) {
var h = i & 15; // convert into 12 gradient directions
var u = h<8 ? x : y,
v = h<4 ? y : h===12||h===14 ? x : z;
return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v);
}
function grad2d(i,x,y) {
var v = (i & 1) === 0 ? x : y;
return (i&2) === 0 ? -v : v;
}
function grad1d(i,x) {
return (i&1) === 0 ? -x : x;
}
function lerp(t,a,b) { return a + t * (b - a); }
this.noise3d = function(x, y, z) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255;
x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z;
var p0 = p[X]+Y, p00 = p[p0] + Z, p01 = p[p0 + 1] + Z, p1 = p[X + 1] + Y, p10 = p[p1] + Z, p11 = p[p1 + 1] + Z;
return lerp(fz,
lerp(fy, lerp(fx, grad3d(p[p00], x, y, z), grad3d(p[p10], x-1, y, z)),
lerp(fx, grad3d(p[p01], x, y-1, z), grad3d(p[p11], x-1, y-1,z))),
lerp(fy, lerp(fx, grad3d(p[p00 + 1], x, y, z-1), grad3d(p[p10 + 1], x-1, y, z-1)),
lerp(fx, grad3d(p[p01 + 1], x, y-1, z-1), grad3d(p[p11 + 1], x-1, y-1,z-1))));
};
this.noise2d = function(x, y) {
var X = Math.floor(x)&255, Y = Math.floor(y)&255;
x -= Math.floor(x); y -= Math.floor(y);
var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y;
var p0 = p[X]+Y, p1 = p[X + 1] + Y;
return lerp(fy,
lerp(fx, grad2d(p[p0], x, y), grad2d(p[p1], x-1, y)),
lerp(fx, grad2d(p[p0 + 1], x, y-1), grad2d(p[p1 + 1], x-1, y-1)));
};
this.noise1d = function(x) {
var X = Math.floor(x)&255;
x -= Math.floor(x);
var fx = (3-2*x)*x*x;
return lerp(fx, grad1d(p[X], x), grad1d(p[X+1], x-1));
};
}
function noiser(oct, fall) {
// these are lifted from Processing.js
// processing defaults
var noiseProfile = {
generator: undefined,
octaves: oct || 4,
fallout: fall || .8,
seed: undefined
};
function noise(x, y, z) {
if(noiseProfile.generator === undefined) {
// caching
noiseProfile.generator = new PerlinNoise(noiseProfile.seed);
}
var generator = noiseProfile.generator;
var effect = 1, k = 1, sum = 0;
for(var i=0; i<noiseProfile.octaves; ++i) {
effect *= noiseProfile.fallout;
switch (arguments.length) {
case 1:
sum += effect * (1 + generator.noise1d(k*x))/2; break;
case 2:
sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break;
case 3:
sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break;
}
k *= 2;
}
return sum;
};
return noise;
}
function split(selection) {
selection.each(function(d,i) {
var text = this.innerText.split("");
this.innerHTML = '';
text.forEach(function(letter,j) {
this.innerHTML += "<span>" + letter + "</span>";
}, this);
d3.select(this).selectAll("span")
.each(function(dd,ii) {
var data = {"bb": this.getBoundingClientRect()};
data.x = data.bb.left - this.offsetParent.getBoundingClientRect().left;
data.y = data.bb.top - this.offsetParent.getBoundingClientRect().top;
d3.select(this).datum(data);
});
});
}
function positionAbsolutely(selection) {
selection.each(function(d,i) {
d3.select(this)
.style('left', function(d) { return d.x + "px"; })
.style('top', function(d) { return d.y + "px"; })
.style('position', 'absolute');
})
}
var container = d3.select(".container");
var text = container.select(".text");
var graf = text.selectAll("p")
.call(split)
.style('height', function(d,i) { return this.getBoundingClientRect().height + 'px'; });
var letters = text.selectAll("span").call(positionAbsolutely);
var canvasText = new CanvasText(container.node(), 'KUNDERA');
text.on("mousemove", function() {
console.log(canvasText.getColor(d3.mouse(this)[0], d3.mouse(this)[1]));
})
var maxX = container.node().offsetWidth;
var maxY = container.node().offsetHeight;
text.on("mouseenter", function() {
var targets = d3.range(letters.size()).map(function(d) {
r = {};
do {
r.x = Math.floor(Math.random() * maxX);
r.y = Math.floor(Math.random() * maxY);
r.c = canvasText.getColor(r.x, r.y);
} while (r.c == "rgb(255,255,255)");
return r;
});
// semi-random sort by distance from top left corner
targets = targets.sort(function(a,b) {
return Math.random()*(Math.pow(a.x,2) + Math.pow(a.y,2)) - Math.random()*(Math.pow(b.x,2) + Math.pow(b.y,2));
})
letters
.transition()
.duration(2000)
.delay(function(d,i) { return 2*i; })
.style('left', function(d,i) { return targets[i].x + "px"; })
.style('top', function(d,i) { return targets[i].y + "px"; });
});
text.on("mouseleave", function() {
letters
.transition()
.duration(1000)
.delay(function(d,i) { return i; })
.style('left', function(d) { return d.x + "px"; })
.style('top', function(d) { return d.y + "px"; });
});