BookmarkSubscribeRSS Feed

Combining the Power of D3 with Three.js to Create a 3D Choropleth

Started ‎06-27-2019 by
Modified ‎06-27-2019 by
Views 9,813

#D3Thursday is back again with a new series of articles over the next few weeks. This time we will be looking at how we can use the popular 3D graphics library Three.js to create a 3D choropleth! This week we will start by creating a 2D canvas choropleth using D3, and then using that choropleth to texture a 3D sphere rendered using Three.js.

 

 

Basic 3D ChoroplethBasic 3D Choropleth

As with previous posts, I've included sample data (Sample_Data_16.sas7bdat) for this post to help you get started. 

 

Creating a 2D Canvas Choropleth

Up until now all of the visualizations we have created using D3.js have been made with SVG graphics. Today we will look at how we can instead use the HTML canvas object to create graphics. Unlike SVG graphics where one DOM object is created for each shape, only the canvas object is created with canvas graphics. Images are then created by drawing shapes (or paths) on to the canvas.

 


const PROJECTION = d3
.geoEquirectangular()
.translate([CANVAS_WIDTH / 2, CANVAS_HEIGHT / 2])
.scale(
Math.min(CANVAS_WIDTH / PROJECTION_AR / Math.PI, CANVAS_HEIGHT / Math.PI)
); // D3 geo projection for canvas

...

// Return texture created from canvas choropleth
function getTexture() {

// Append canvas and save reference
const canvas = d3
.select("body")
.append("canvas")
.attr("width", CANVAS_WIDTH)
.attr("height", CANVAS_HEIGHT);

// Get 2d context of canvas
const context = canvas.node().getContext("2d");

// Create geo path generator
const path = d3
.geoPath()
.projection(PROJECTION)
.context(context);

// Draw background
context.fillStyle = "#555";
context.fillRect(0, 0, CANVAS_WIDTH, CANVAS_HEIGHT);

// Draw features from geojson
context.strokeStyle = "#555";
context.lineWidth = 0.25;

WORLD_JSON.features.forEach(function(d) {
context.fillStyle = DATA[d.properties.iso_a3]
? COLOR_SCALE(DATA[d.properties.iso_a3].measure)
: "#CCC";
context.beginPath();
path(d);
context.fill();
context.stroke();
});

// Generate texture from canvas
const texture = new THREE.Texture(canvas.node());
texture.needsUpdate = true;

// Remove canvas
canvas.remove();

// Return texture
return texture;
}

 

As you can see from the code above, D3 makes it easy to render the paths for any given geography using the numerous projections and the geoPath function. When used together, these functions convert latitude/longitude information from a geo.json file into x/y pixel coordinates and then paint the appropriate paths on to your canvas object. The canvas object created by this function would look like the following if you were to render if by itself.

 

 

2D Canvas Choropleth2D Canvas Choropleth

Rendering a 3D Scene

Now that we have created a texture from our 2D canvas choropleth using D3, we can move on to creating our 3D scene. 

 

// Init scene
SCENE = new THREE.Scene();

// Init camera and set position
CAMERA = new THREE.PerspectiveCamera(
45, // field of view (fov) in degrees
WIDTH / HEIGHT, // aspect ratio
0.01, // near field distance
1000 // far field distance
);
CAMERA.position.set(0, 0, 4);

// Init renderer and set size
RENDERER = new THREE.WebGLRenderer({
antialias: true, // turn on antialiasing to smooth jaggies (turn off for better performance)
canvas: d3.select("canvas").node() // canvas to hold rendered scene
});
RENDERER.setSize(WIDTH, HEIGHT);

// Init sphere geometry
SPHERE_GEOMETRY = new THREE.SphereGeometry(
1, // Radius of sphere
SEGMENTS, // Number of horizontal segments used to construct sphere
SEGMENTS // Number of vertical segments used to construct sphere
);

// Init choropleth sphere and add to scene
CHOROPLETH_SPHERE = new THREE.Mesh(
SPHERE_GEOMETRY, // geometry for mesh
new THREE.MeshBasicMaterial({ map: getTexture() }) // material for mesh
);

SCENE.add(CHOROPLETH_SPHERE);

// Init camera controls
CONTROLS = new THREE.TrackballControls(CAMERA, RENDERER.domElement);

// Set legend values
updateLegend();

// Start render loop
render();

// Render loop function
function render() {
// Update controls
CONTROLS.update();

// Render frame
requestAnimationFrame(render);
RENDERER.render(SCENE, CAMERA);
}

 

It only takes  a few lines of code to get up and running with your first 3D scene using Three.js! In our simple example, we only have one object. Each object in Three.js consists of two parts: a geometry and a material. For our object, we are using a spherical geometry and a basic mesh material made with our choropleth texture in order to create a 3D globe choropleth.

 

We chose to keep this scene simple to maximize performance, but we will be adding complexity in the form of mouse events and animations in the coming posts. Three.js is capable of much more complicated scenes than this one and I encourage you to experiment with how you could add to this scene. Fork this example on GitHub and see what you can make!

 

Additional Resources

Next Post

Next post we will take a look at how you can add mouse events to a 3D scene using a raycaster and discuss the performance considerations you should think about when doing so.

 

Remember to follow these articles on the SAS Communities Library and the #D3Thursday Twitter hashtag. Comment below or Tweet your questions and comments!

 

D3Thursday Logo.png

 

Version history
Last update:
‎06-27-2019 02:30 PM
Updated by:
Contributors

sas-innovate-2024.png

Available on demand!

Missed SAS Innovate Las Vegas? Watch all the action for free! View the keynotes, general sessions and 22 breakouts on demand.

 

Register now!

Free course: Data Literacy Essentials

Data Literacy is for all, even absolute beginners. Jump on board with this free e-learning  and boost your career prospects.

Get Started

Article Labels
Article Tags