Tuesday, January 15, 2013

D3GL Tutorials 03: Intro to data primitives with Points

Data primitives for D3GL are the visual equivalents to the raw data. So far, we have covered (1) creating a globe, (2) binding the globe to some data to set globe-specific properties, and then (3) binding primitives to data. But all this has been setting grounds for showcasing the data through the primitive it has been bound to by letting the data dictate specific properties of the primitives.

In D3GL globe, there are five data primitives:
Points, Shapes, Bars, Arcs,
and
Painter
. We will take
Points
as an intro to primitives.

Customizing primitives for data


Just as each globe was an instance of a globe template which was tied to each data element, each point, shape, arc, etc. is an instance of a template. Just like with the globe template, you will either pass in fixed values that are shared across all instances or functions that will return customized values depending on the data element it takes as argument.

Let's take
Points
as an example. First you would create a template that binds to data, as the following:
var points = globe.points()
  .data(function(k) {
    return datasets[k];
  });
As we discussed in the previous tutorial, the argument
k
is bound on the globe level (i.e. per globe) and in this case is being used as the key to fetch a dataset, which is then bound to the primitive
Points
.

We want to customize each point instance so that it represents each data element:
var points = globe.points()
  .data(function(k) {
    return datasets[k];
  })
  .radius(function(d) {
    return scaleRadius(d['precipitation']);
  })
  .color(function(d) {
    return scaleColor(d['precipitation']);
  })
  .latitude(function(d) {
    return d['latitude'];
  })
  .longitude(function(d) {
    return d['longitude'];
  });
The parameter
d
used in the setter functions represents a data element in the dataset bound to
Points
. In the above code snippet, the radius and color of each point is determined by the value of
d['precipitation']
. The scaling functions
scaleRadius
and
scaleColor
take the precipitation value and spit out an appropriately scaled radius in units of degrees, and color in "#" format. D3.js provides a convenient
d3.scale
that is pretty useful for all types of scaling, including color gradients.

Demo


This demo marks the landings on Mars with
Points
, using the color and size of each point to represent how recent the landing was: Landings on Mars

Implementation


As discussed in the previous tutorial, a data primitive for D3GL Globe is another closure within the
d3.gl.globe
closure.
Points
has a rendering function, but unlike with the globe, this function is not invoked by the client. Instead, when
Points
are added to the globe, the rendering function for
Points
is pushed to the array of rendering functions that are called per frame by
d3.gl.globe.


In the rendering loop invoked with
.call(globe),
a hidden canvas element that is programmatically created is passed in to the rendering functions for primitives, along with the datum the globe instance is bound to, and the WebGL environment:
// In rendering loop - called once per frame
for(var i = 0; i < primitiveRenderingFunctions.length; i++){
  primitiveRenderingFunctions[i](
    webGLEnvironment, contextForHiddenCanvas, datumForGlobeInstance
  );
}
Of course, it's JavaScript, not Objective-C, so the variable names are not actually this long.

In the rendering loop for
Points,
the datum pass in is used to fetch the dataset. Then for each element in the dataset, a circle is drawn using
drawCircle()
on the context that is passed in:
function points(gl, context, datum) {
  var dataset = fnData(datum);
  for (var i = 0; i < dataset.length; i++) {
    var elem = dataset[i];
    // client-defined functions are used to fetch properties of each point
    var lat = fnLat(elem);
    var lon = fnLon(elem);
    // ... and so on
    drawCircle(context, plat, plon, pradius, color, strokeColor, lineWidth);
  }
}
After all rendering functions in queue are called and the overlay texture on the hidden canvas is complete, the entire scene is rendered via
gl.renderer.render(gl.scene, gl.camera);


The other primitives roughly use the same format, but there are differences. So stay tuned!

← Prev

No comments:

Post a Comment