Mar 092010
 

For today’s March Madness program I made a visualization of Tumblr quotes from a few friends I follow. I turn the text into a tumbleweed and let it ramble on by across the screen. It could definitely use some optimization as canvas doesn’t seem to handle lots of text rendering too well. Source is after the break.

JPLT.Class.create("JPLT.TextTumbleweed", JPLT.Object,
	function(str) {
		this.str = str;
		this.reset();
		this.createPhrases();
	},
	{
		reset: function() {
			this.active = false;
			this.x = -50 + Math.random() * -200;
			this.y = 267 + 133 * Math.random();
			this.baseY = this.y;
			this.speed = Math.random() * 5 + 1;
			this.r = Math.random() * Math.PI/8 - Math.PI/16;
			this.size = 10 + Math.random()*10;
			this.color = "rgb(" + Math.floor(Math.random()*50) + "," + 
				Math.floor(Math.random()*50) + "," + 
				Math.floor(Math.random()*50) + ")";
		},
 
		run: function() {
			if (this.active) {
				this.r += this.speed/5 * Math.PI/16;
				if (this.r > Math.PI*2) {
					this.r -= Math.PI*2;
				}
 
				this.x += this.speed;
				this.y = this.baseY + Math.abs(Math.cos(this.r)) * this.speed * 5;
 
				if (this.x > window.innerWidth + 200) {
					this.reset();
					this.fireEvent("weedInactive");
				}
			}
		},
 
		createPhrases: function() {
			var phrase;
			var words = this.str.split(/\s+/);
 
			this.phrases = [];
 
			while(words.length) {
				phrase = "";
				while(words.length && phrase.length < 25) {
					phrase = phrase + words.shift() + " ";
				}
 
				this.phrases.push({ 'phrase': phrase, 'random': Math.random() });
			}
		},
 
		paint: function(ctx) {
			try {
				if (this.active) {
					var random;
					var phrase;
					var r = this.r;
 
					ctx.save();
 
					ctx.fillStyle = this.color;
					ctx.translate(this.x,this.y);
 
					for (var i=0; i<this.phrases.length; i++) {
						phrase = this.phrases[i].phrase;
						random = this.phrases[i].random;
 
						ctx.rotate(r);
						ctx.font = this.size + "px sans-serif";
						var size = ctx.measureText(phrase);
						ctx.fillText(phrase,-size.width/2,0);
 
						r = Math.PI + Math.PI/(5 + -random*3);
					}
 
					ctx.restore();
				}
			}
			catch (e) {
				throw(e);
			}
		}
	}
);
 
JPLT.Class.create("JPLT.TumblrTumbleweeds", JPLT.Object,
	function() {
		// We need a global reference to call back.
		JPLT.TumblrTumbleweeds.instance = this;
 
		this.width = 800;
		this.height = 534;
		this.delay = 10;
		this.quoteLength = 200;
		this.activeWeeds = 2;
		this.weeds = [];
 
		this.addEventListener("weedInactive", this.delegate(this.activateWeed));
		this.createElements();
		this.load('soupsoup');
		this.load('justinday');
		this.load('ericmortensen');
		this.load('jacobjoaquin');
		this.load('mikehudack');
	},
 
	{
		getUrl:function(user) {
 
			var url = "http://" + escape(user) + 
				".tumblr.com/api/read/json?type=quote&" +
				"callback=JPLT.TumblrTumbleweeds.instance.loaded";
 
			return url;
		},
 
		load:function(user) {
			var script = document.createElement('script');
			script.type = "text/javascript";
			script.src = this.getUrl(user);
 
			var body = document.documentElement || document.body;
			body.appendChild(script);
		},
 
		stripHtml: function(str) {
			return str.replace(/<.+?>/,"").replace(/&#\d+;/,"");
		},
 
		excerptize: function(str) {
			if (str.length > this.quoteLength) {
				str = str.substr(0,this.quoteLength-3) + "...";
			}
 
			return str;
		},
 
		createTumbleweed: function(str) {
			var weed = new JPLT.TextTumbleweed(str);
			this.weeds.push(weed);
		},
 
		activateWeed: function() {
			var weed;
 
			for (var i=0; i<10; i++) {
				weed = this.weeds[Math.floor(Math.random() * this.weeds.length)];
				if (!weed.active) {
					this.log("Activating " + weed.str);
					weed.active = true;
					return;
				}
			}
 
			window.setTimeout(this.delegate(this.activateWeed), 5000);
		},
 
		loaded:function(data) {
			var posts = data.posts;
			for (var i=0; i<posts.length; i++) {
				this.createTumbleweed(this.excerptize(this.stripHtml(posts[i]['quote-text'])));
			}
 
			this.run();
		},
 
		createElements: function() {
			this.background = document.createElement("img");
			this.background.src = "image.jpg";
			this.background.style.position = "absolute";
			this.background.style.top = window.innerHeight/2 - this.height/2 + "px";
			this.background.style.left = window.innerWidth/2 - this.width/2 + "px";
 
			this.element = document.createElement("canvas");
 
			this.element.width = this.width;
			this.element.height = this.height;
			this.element.style.position = "absolute";
			this.element.style.top = window.innerHeight/2 - this.height/2 + "px";
			this.element.style.left = window.innerWidth/2 - this.width/2 + "px";
			this.element.style.zIndex = 1;
 
			var body = document.documentElement || document.body;
			body.appendChild(this.background);
			body.appendChild(this.element);
		},
 
		context: function() {
			return this.element.getContext("2d");
		},
 
		run: function() {
			if (!this.timer) {
				for (var i=0; i<this.activeWeeds; i++) {
					this.activateWeed();
				}
 
				this.timer = window.setInterval(this.delegate(this.paint), this.delay);	
			}
		},
 
		stop: function() {
			window.clearInterval(this.timer);
 
			for (var i=0; i<this.weeds.length; i++) {
				this.weeds[i].reset();
			}
 
			this.timer = null;
		},
 
		clear: function() {
			var ctx = this.context();
			ctx.clearRect(0,0,this.width,this.height);			
		},
 
		paint: function() {
			var weed;
			var ctx = this.context();
 
			try {
				this.clear();
 
				for (var i=0; i<this.weeds.length; i++) {
					weed = this.weeds[i];
					weed.run();
					weed.paint(ctx);
				}
			}
			catch (e) {
				stop();
				throw(e);
			}
		}
	}
);