Quantcast
Channel: Blog - delimited
Viewing all articles
Browse latest Browse all 26

D3.js, Three.js and CSS 3D Transforms

$
0
0
d3-threejs-css-3d-tranforms.png

Authored by Steven Hall

This week I have been having some fun thinking about how you could use D3.js and Three.js together to do some data visualization work.  We'll have to put this one in the experimental column since there is a lot more work to be done, but I was pretty pleased with the results and thought I would blog about what I have done up to this point.  While there are plenty of dramatic examples of three.js used to generate 3D globes with lines shooting everywhere, I was interested in a more subtle approach to complement work in D3.  I would be curious to hear about other experiments going on along the same lines.  A Google search didn't turn up much.

The following example is using D3 to generate HTML elements and SVG charts and also to store coordinate information for transitions inside data properties.  The objects created using D3 are then passed into a three.js scene and animated using CSS 3D transforms (no WebGL here, this is pure DOM).

You can take a look at the demo here.    Code on GitHub here.

For mobile phones and other slower devices, you will want to load this scaled down version.

Browser support for this demo is pretty good, but this is what I found in some casual testing...

spehere-d3-threejs-css-3d-tranforms.png

PC Browsers

  • Chrome - Awesome
  • Safari - Awesome
  • Firefox - Pretty Good
  • Internet Explorer - Nope

Tablet Browsers

  • Android/Chrome - Awesome
  • iPad/Safari - Incredible
  • Android/Firefox - Passable
  • Android Browser - Not good

 

Why CSS 3D Transforms?

The short answer is that CSS 3D transforms are widely supported in browsers and that support will continue to grow.  Transforms, like WebGL, capture the benefit of hardware acceleration and this is key to creating more sophisticated animations with more data points.  The current browser stats put support at 77.99% (in March 2014) with Internet Explorer bringing up the rear, yet again, only offering partial support.  If you are using D3 and rendering in SVG there's really not much difference.  SVG is supported in about 85% of browsers (March 2014).

I'm interested in WebGL as well, but the support is just not there at the moment.  There are some amazing demo apps and browser support has been steadily increasing, but it is still a little early to begin building for WebGL (although I am going to start experimenting).   A quick look at the current browser stats for WebGL puts it at about 64% for at least partial support (in March 2014).  Probably one of the biggest stumbling block is that Safari users have to manually turn on support for WebGL, which just about kills it for many developers.  Using three.js to render with CSS 3D transforms side steps that issue and, for me was a good intro into three.js as someone who works with D3.

Why D3.js and Three.js?

Even in a world where 3D in the browser is common, I don't think we are going to get away from 2D charts for communicating basic relationships between entities in the data.  One possibility I see is that three.js can allow you to set up a 3D space where those 2D charts live that the user can quickly navigate.  Anyone, who has tried producing complex visualizations with JavaScript has quickly run into the problem of providing a way for the user to interact with the data or select a different view of the information.  On mobile devices (and PCs really) that interaction, often using sliders or drop-down menus or allowing the user to click on items, is awkward and just really clumsy. 

This example, while put together quickly, shows it's at least possible to use D3 in 2D and allow it to exist in a 3D world that the user can quickly navigate using touch or the mouse.  Here we are just throwing all the charts into a 3D space and giving the users some controls, but I think a more advanced example could show how filtering and sorting could be done in a more fluid way using a similar approach.  I think a more well thought navigation system would be a good next step as well.

How Does This Work?

The code is based on an example created by Mr. Doob in three.js that uses the elements of the periodic table (we are going to skip over the whole famo.us part of the story here).  For my example, I started with his code and re-factored it to use D3 wherever possible.  In addition to simply generating SVG charts, D3 is being used to generate all the HTML content and buttons that are used in the graphic.

Especially when talking about rendering with CSS 3D transforms, I think D3's use of selections and method chaining is really useful when working with three.js.  If you compare the two examples, I think the code written in D3 is more readable and elegant than the three.js example code (perdóname, Sr. Doob!).  Also, if you wanted to set events on elements and have the data for that element already queued up on click, D3 has a really elegant way of handling that.  I didn't set any events on the elements in the example, but I did test it out and it works great even after being transformed.  You could add events to the chart or the surrounding HTML elements to provide more interactivity.

Looking at the Code

Over view of the process in the code

Over view of the process in the code

The main block of code for the example is in the viz.js file.  There is a total of about 250 lines of code to generate the visualization, which I was pretty happy about.  There are just a few additional lines in the HTML file that load the data and kicks things off.  The rest of the magic is happening with CSS.  Many of the elements are being placed using "absolute" positioning, so definitely take a look at the CSS file if you want to understand how everything works.

The image to the left describes the basic process.  If you look at the lines of JavaScript in the index.html files, you see the data loads from a JSON file and is first sent to the VIZ.drawElements function.  In that function, D3 will generate the HTML and SVG for each one of the elements then, for each element, it will call a function for setting the data position for the element in each view (random, helix, sphere, grid), storing the information inside the data property of the element.  I found this to be an elegant way to use D3 and it really helps to keep the data organized for events and debugging.

You can see a screenshot of the data set on an element in the image to the left.   What actually gets stored for the coordinates of the element in the view (helix, sphere, etc) is a THREE.Object3D object which has both position and rotation which can be directly sent to three.js for rendering the scene and doing animations.

With all the HTML and SVG generated and the data property set on the element, it is passed into another function ("objectify") where the entire element is set as a THREE.CSS3Object with its initial coordinates being the random ones (generated in the setData function) and then that object is passed into the three.js scene.  When the page loads the scene will immediately begin a transition from the "random" coordinates to the "helix" ones.

// From drawElements function
elements.each(objectify);

// Objectify function receives the data
// from the element
function objectify(d) {
var object = new THREE.CSS3DObject(this);
object.position = d.random.position;
scene.add(object);
}

Transforming, Rendering and Animating

This example is really geared towards people with D3 experience who have an interest in three.js and 3D graphics in the browser.  If you look over the code, over half of the ~250 lines is devoted to the drawElements function which is straight D3 (admittedly, this is some mind bender D3 code since we are generating 100 charts in one function...excellent workout if you want to hone your D3 selection skills).  If you then take out the setData function which is just adding data to the data property on the elements and the other helper functions, there is not much left to talk about.  The rest of the code handles transitions, rendering and animation, but this is common to any three.js example where you set up a scene and a camera.

Tweening in Three.js

To make transitions from one layout or view (helix, sphere, etc) to another in three.js, the idea is similar to what is done in D3 in a 2D format.  If you have been working with D3 the idea of transitioning through "tweening" is probably not unfamiliar (discussion of D3 transition).  Tweening is a way to create a smooth transition from one state to another without having to write all the complex code to handle each individual frame.  In this example I am using the Tween.js library and you can see how it is used in the VIZ.transform function:

VIZ.transform = function (layout) {
var duration = 1000;

TWEEN.removeAll();

scene.children.forEach(function (object){
var newPos = object.element.__data__[layout].position; //A
var coords = new TWEEN.Tween(object.position)
.to({x: newPos.x, y: newPos.y, z: newPos.z}, duration)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();

var newRot = object.element.__data__[layout].rotation; //B
var rotate = new TWEEN.Tween(object.rotation)
.to({x: newRot.x, y: newRot.y, z: newRot.z}, duration)
.easing(TWEEN.Easing.Sinusoidal.InOut)
.start();
});

var update = new TWEEN.Tween(this)
.to({}, duration)
.onUpdate(VIZ.render)
.start();
}

With data already set on each element, we just need to cycle over each one and grab the new coordinates and start the transition.  The function above takes a layout as input.  That's the new layout that you want to have the elements move towards (usually on a button click). You can see at points A and B in the code above that the coordinate information is extracted from the element's data property.

You can go to the Tween.js GitHub page for some great examples and little more explanation of how this works.

Controls, Render and Animate

One last short JavaScript file called TrackballControls.js is loading in the example that gives it the controls for zooming, panning, and rotating.  This is not part of the core three.js library, but you can find it in the examples section on GitHub here.  You can see where this is setup near the bottom of the viz.js file:

VIZ.render = function () {
renderer.render(scene, camera);
}

VIZ.animate = function () {
requestAnimationFrame(VIZ.animate);
TWEEN.update();
controls.update();
}

renderer = new THREE.CSS3DRenderer();
renderer.setSize(width, height);
renderer.domElement.style.position = 'absolute';

document.getElementById('container').appendChild(renderer.domElement);

controls = new THREE.TrackballControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.minDistance = 100;
controls.maxDistance = 6000;
controls.addEventListener('change', VIZ.render);

With TrackballControls.js file in place, there is a little more surrounding code for the VIZ.render function and the VIZ.animate function.  There are also a few scattered lines for setting the camera  and renderer properties.  This is all pretty standard stuff in three.js.  I think one of the best resources for understanding the basic process for rendering and animating scenes in three.js is the website and book from Jos Dirksen.  Although he doesn't cover the CSS 3D renderer I found the book extremely helpful for learning the basics of three.js.

That about covers all the code in the example.  Hope some people found this example interesting. Looking forward to future experiments with three.js and WebGL. 


Viewing all articles
Browse latest Browse all 26

Trending Articles