|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.10.1/lodash.min.js"></script> |
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script> |
|
<style> |
|
</style> |
|
</head> |
|
|
|
<body> |
|
<img src="https://pbs.twimg.com/profile_images/3775316964/fe2ba0b0a4f0fb30aaef37c089553a8d.jpeg" width="400" /> |
|
<canvas id="canvas"></canvas> |
|
<script> |
|
d3.json('twitter_profile3.json', function(image) { |
|
d3.json('tweets.json', function(tweets) { |
|
|
|
// first process the tweets |
|
var minOpacity = _.min(tweets, function(tweet) { |
|
return tweet.stats.favorites; |
|
}); |
|
minOpacity = minOpacity.stats.favorites + 1; |
|
var maxOpacity = _.max(tweets, function(tweet) { |
|
return tweet.stats.favorites; |
|
}); |
|
maxOpacity = maxOpacity.stats.favorites + 1; |
|
var opacityScale = d3.scale.log() |
|
.domain([minOpacity, maxOpacity]) |
|
.range([25, 255]); |
|
var tweetColors = { |
|
'reply': [248,148,6], // orange |
|
'retweet': [81,163,81], // green |
|
'tweet': [0,136,204] // blue |
|
}; |
|
tweets = _.sortBy(tweets, function(tweet) { |
|
tweet.date = new Date(tweet.created_at); |
|
tweet.opacity = opacityScale(tweet.stats.favorites + 1); |
|
if (tweet.retweet || tweet.quote) { |
|
tweet.type = 'retweet'; |
|
} else if (tweet.in_reply_to) { |
|
tweet.type = 'reply'; |
|
} else { |
|
tweet.type = 'tweet'; |
|
} |
|
return tweet.date; |
|
}); |
|
|
|
// some defaults for the image |
|
var imageSize = Math.sqrt(image.length); |
|
var scaleFactor = Math.floor(500 / imageSize); |
|
var canvas = document.getElementById('canvas'); |
|
canvas.width = imageSize * scaleFactor; |
|
canvas.height = imageSize * scaleFactor; |
|
var ctx = canvas.getContext('2d'); |
|
var data = []; |
|
var threshold = 122.5; |
|
|
|
// turn it grayscale first |
|
_.each(image, function(pixel) { |
|
data.push(Math.max(pixel[0], pixel[1], pixel[2])); |
|
}); |
|
// Atkinson dithering |
|
var tweetIndex = 0; |
|
var tweetMap = {}; |
|
_.each(data, function(oldPixel, i) { |
|
var newPixel = oldPixel > threshold ? 255 : 0; |
|
var error = (oldPixel - newPixel) >> 3; |
|
|
|
data[i] = newPixel; |
|
data[i + 1] += error; |
|
data[i + 1] += error; |
|
data[i + imageSize - 1] += error; |
|
data[i + imageSize] += error; |
|
data[i + imageSize + 1] += error; |
|
data[i + imageSize + 2] += error; |
|
|
|
if (!newPixel) { |
|
// if the pixel is black, then keep track of |
|
// its corresponding tweet |
|
tweetMap[i] = tweets[tweetIndex]; |
|
tweetIndex += 1; |
|
} |
|
}); |
|
data = data.slice(0, imageSize * imageSize); |
|
|
|
var imageData = ctx.getImageData(0,0,imageSize * scaleFactor, imageSize * scaleFactor); |
|
_.each(data, function(pixel, i) { |
|
var tweet = tweetMap[i]; |
|
_.times(scaleFactor, function(x) { |
|
_.times(scaleFactor, function(y) { |
|
// first calculate row, then column |
|
var index = Math.floor(i / imageSize) * (imageSize * Math.pow(scaleFactor, 2)) + |
|
(y * imageSize * scaleFactor) + |
|
((i % imageSize) * scaleFactor + x); |
|
index *= 4; |
|
|
|
imageData.data[index] = pixel || tweetColors[tweet.type][0]; |
|
imageData.data[index + 1] = pixel || tweetColors[tweet.type][1]; |
|
imageData.data[index + 2] = pixel || tweetColors[tweet.type][2]; |
|
imageData.data[index + 3] = !tweet ? 255 : tweet.opacity; |
|
}); |
|
}); |
|
}); |
|
ctx.putImageData(imageData, 0, 0); |
|
}); |
|
}); |
|
</script> |
|
</body> |