Using D3.js with THREE.js, part II

So, our little D3 + 3D experiment continues. This time we will be trying to replicate bar chart tutorial 2 in 3D.

Transitions

Two things need to be done before D3 transitions could work. First, D3 must be able to interpolate “attribute” values, so we need to implement both setAttribute and getAttribute methods in THREE.js:

// this one is to use D3's .attr() on THREE's objects
THREE.Object3D.prototype.setAttribute =
  function (name, value) {
    var chain = name.split('.');
    var object = this;
    for (var i = 0; i < chain.length - 1; i++) {
        object = object[chain[i]];
    }
    object[chain[chain.length - 1]] = value;
}

// and this one is to make'em work with D3's .transition()-s
THREE.Object3D.prototype.getAttribute =
  function (name) {
    var chain = name.split('.');
    var object = this;
    for (var i = 0; i < chain.length - 1; i++) {
        object = object[chain[i]];
    }
    return object[chain[chain.length - 1]];
}

Second, D3′s selectAll() now needs to actually select something to build updating selection. We can make it select various descendants of Object3D type, for example:

// now selectAll must actually do something
THREE.Object3D.prototype.querySelectorAll =
  function (selector) {
    var matches = [];
    var type = eval(selector);
    for (var i = 0; i < this.children.length; i++) {
        var child = this.children[i];
        if (child instanceof type) {
            matches.push(child);
        }
    }
    return matches;
}

These changes are already enough to replicate their simple transition example in THREE.js:

var t = 1297110663,
    v = 30,
    data = d3.range(9).map(next);

function next() {
  return {
    time: ++t,
    value: v = ~~Math.max(10,
      Math.min(90, v + 10 * (Math.random() - .5))
    )
  };
}

setInterval(function() {
  data.shift();
  data.push(next());
  redraw();
}, 1500);

function redraw() {
  d3.select( chart3d )
    .selectAll("THREE.Mesh")
    .data(data)
  .transition()
    .duration(1000)
    .attr("position.y", function(d, i) { return d.value; })
    .attr("scale.y", function(d, i) { return d.value / 10; })
}

But to replicate their final keyed data join example, we need to take care of one last thing…

Exit selections

We want D3′s remove() to actually remove 3D objects. Now, you might be thinking, this is easy one, we’ll just add removeChild() to Object3D prototype

THREE.Object3D.prototype.removeChild =
  function (c) {
    this.remove(c);
  }

and it should work, right? Wrong, it does not! To solve this one, I had to check D3 source. Turns out D3 does not call removeChild on selection parentNode but on node’s parentNode itself, so we need to modify our appendChild as follows:

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

Final result

With these changes, it is now possible to do this:

function redraw() {

	var bars = d3.select( chart3d )
		.selectAll("THREE.Mesh")
		.data(data, function(d) { return d.time; });

	// move existing bars to their new place
	bars.transition()
		.duration(1000)
		.attr("position.x",
			function(d, i) { return 30 * i; })

	// add new bar and make it grow
	bars.enter().append( newBar )
		.attr("position.x",
			function(d, i) { return 30 * (i + 1); })
		.attr("position.y", 0)
		.attr("scale.y", 1e-3)
	  .transition()
		.duration(1000)
		.attr("position.x",
			function(d, i) { return 30 * i; })
		.attr("position.y",
			function(d, i) { return d.value; })
		.attr("scale.y",
			function(d, i) { return d.value / 10; })

	// remove the bar that is no longer in data set
	bars.exit().transition()
		.duration(1000)
		.attr("position.x",
			function(d, i) { return 30 * (i - 1); })
		.attr("position.y", 0)
		.attr("scale.y", 1e-3)
		.remove()
}

Final demo

This is still not complete THREE.js “plugin” but, as you can see, it’s a nice start.

4 Responses to “Using D3.js with THREE.js, part II”


  1. 1 dsummersl January 8, 2014 at 03:04

    Wow, nice work! I was just wondering if d3 could be extended into 3 dimenions, and here, lo and behold, you’ve figured a lot of it out.

  2. 2 David June 25, 2014 at 21:23

    This is excellent. Thanks so much! I noticed that your implementation of querySelectorAll doesn’t look recursively at children of children. Could you suggest a way to change this?


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s




Old stuff

November 2013
M T W T F S S
« Oct   Dec »
 123
45678910
11121314151617
18192021222324
252627282930  

Oh, btw…


Follow

Get every new post delivered to your Inbox.

%d bloggers like this: