Using D3.js with THREE.js

This blog name has “3D” in it, but there was no 3D here for a long time now. So I am fixing this tonight.

Once upon a time we did simple AS3 port of D3.js lib here at CodeOrchestra, codenamed “D6” by Potapenko. The name D6 came from D3 + 3D, since our port was able to work with 3D engines to enable 3D data visualization. That port never went into production, but the idea behind it is still valid today.

I mean, can you do 3D with D3? There is no 3D examples in their gallery, so the obvious answer seems to be “no”. The reason is that D3 was built with browser DOM in mind, and it is not straightforward to extend it to support arbitrary object models such as THREE.js, de facto standard for 3D in javascript world. Nevertheless, as you can guess, it could be done.

Let’s take a look at D3 simplest example (bar chart):

d3.select(".chart")
  .selectAll()
    .data(data)
  .enter().append("div")
    .style("width", function(d) { return d * 10 + "px"; })
    .text(function(d) { return d; });

Here, a number of methods accept browser-specific magic strings such as “.chart” selector or “div” tag name. While convenient in 2D browser environment, this is totally useless for our purposes. But have no fear, for D3 comes with alternative signatures for these methods! These signatures were designed for boring things like reusing existing selections, but we can actually bend them to swallow non-DOM objects. For example, we can rewrite the bar chart above like this:

function createDiv() {
	return document.createElement("div");
}

var chart = {
	appendChild: function (child) {
		// this is called by append() after createDiv()
		return document.getElementById("chartId")
			.appendChild(child);
	},
	querySelectorAll: function () {
		// this is called by selectAll()
		return [];
	}
}

d3.select( chart )
  .selectAll()
    .data(data)
  .enter().append( createDiv )
    .style("width", function(d) { return d * 10 + "px"; })
    .text(function(d) { return d; });

Here, we 1) explicitly instruct D3 how to create “div” tags, and 2) trick D3 into thinking that “chart” is normal DOM object, while the end result of the script is exactly the same as before. Now when we learned how to work around D3 helpful APIs, we can easily do the same with THREE.js:

THREE.Object3D.prototype.appendChild = function (c) {
	this.add(c); return c;
}

THREE.Object3D.prototype.querySelectorAll = function () {
	return [];
}

chart3d = new THREE.Object3D();

d3.select( chart3d )
  .selectAll()
  .data(data)
.enter().append(
  function (d, i) {
	var bar = new THREE.Mesh( geometry, material );
	bar.position.x = 30 * i;
	bar.position.y = d;
	bar.scale.y = d / 10;
	return bar;
  }
);

In this demo, I actually went ahead and made D3’s attr() calls work for why not.

Well, this is it for today :) I hope to continue this post some day with part 2, where I’d get D3’s transitions to work, but right now I need to sleep. Zzzz..

Ask a Question




Old stuff

October 2013
M T W T F S S
 123456
78910111213
14151617181920
21222324252627
28293031