Make a Face with D3.js and SVG

This tutorial is a good introduction to using D3.js with SVG. It will cover selecting DOM elements with D3, creating DOM elements with D3, customizing them, and more.

At the end of the tutorial, you will have created a smiley face that looks like this:

Loading D3.js

Load D3.js from CDN by adding the following script tag to the header of your index.html file:

https://d3js.org/d3.v5.min.js 

Now, you can check to make sure that D3 was successfully added to your project by simply typing “d3” in the console while running your html file. It should output the d3 object, which looks like this:

Now, we can use D3 to manipulate the DOM by using the d3 object in our JavaScript code. Here’s an example:

d3.select('div');

Creating a face using SVG and D3

Make an SVG in your index.html file:

<svg width="500" height="250"></svg>

In your app.js file, get access to the SVG element you just created using d3:

const svg = d3.select('svg');

Creating the Head

Now, add a circle to the SVG:

const circle = svg.append('circle');

Now, we will give the circle a radius and (x, y) position for its center. We will use D3 method chaining:

circle 
    .attr('r', 250 / 2)   // radius
    .attr('cx', 500 / 2)  // center x
    .attr('cy', 250 / 2); // center y

I set the radius and center-y values to be half of the height of the SVG and the center-x to be half of the width.

Instead of hard-coding the values in, we can create variables for these width and height values in our JavaScript. To access the width and height values from the SVG in our index.html file, we can use svg.attr(). If you call svg.attr() without a second attribute, it returns the value of the attribute that you specify:

const width = svg.attr('width')

SVG attributes are strings by default, so we can parse the value into a float using parseFloat:

const width = parseFloat(svg.attr('width'));

You can also use the unary operator to parse into a float:

const height = +svg.attr('height');

Now, we can use the variables instead of hard-coded values to define our SVG elements:

circle 
    .attr('r', height / 2)   
    .attr('cx', width / 2)
    .attr('cy', height / 2); 

We can also add fill and stroke attributes to give the circle a fill color and outline color:

const circle = svg.append('circle') 
    .attr('r', height / 2)    
    .attr('cx', width / 2)  
    .attr('cy', height / 2)
    .attr('fill', 'yellow')
    .attr('stroke', 'black')

Now, we have the circle for the head:

Creating the Eyes and Mouth

We will make the eyes using circles, similar to how we made the head:

const leftEye = svg.append('circle')
    .attr('r', 10)    
    .attr('cx', width / 2 - 40)     
    .attr('cy', height / 2 - 40)
    .attr('fill', 'black')

const rightEye = svg.append('circle')
    .attr('r', 10)    
    .attr('cx', width / 2 + 40)     
    .attr('cy', height / 2 - 40)
    .attr('fill', 'black')

As you can see, we are defining the leftEye and rightEye variables in the same line in which we are method chaining the attributes.

For the mouth, we will create a group element and add a transform property to the group element to translate it to the center of the face:

const g = svg.append('g')    
    .attr('transform', `translate(${width / 2}, ${height / 2})`);

Then, we will use a d3.arc:

const mouth = g.append('path')
    .attr('d', d3.arc()({     
        innerRadius: 40,
        outerRadius: 50, 
        startAngle: Math.PI / 2, 
        endAngle: Math.PI * 3/2
    }))

Group Elements

Instead of constantly repeating width / 2 and height / 2 to move elements to the center, we can put everything in the group element so that we can leverage that fact that it’s translated by (width/2) and (height/2).

This update doesn’t change the look or functionality, but it just makes our code a little bit cleaner.

First, move the group element to the top of the screen, under the width and height variable declarations.

Then, append the face, eye and mouth elements to the group element instead of to the svg element directly.

const circle = g.append('circle') 

Now, we can remove the cx and cy attributes from the face because it is put in the center by default because its parent, g, is translated into the center:

const circle = g.append('circle') 
    .attr('r', height / 2)    
    .attr('fill', 'yellow')
    .attr('stroke', 'black')

We can also update the eyes:

const leftEye = g.append('circle')
    .attr('r', 10)    
    .attr('cx', -40)     
    .attr('cy', -40)
    .attr('fill', 'black')

const rightEye = g.append('circle')
    .attr('r', 10)    
    .attr('cx', 40)     
    .attr('cy', -40)
    .attr('fill', 'black')

Final Code

The final code is at https://github.com/jbowen4/d3-smiley-face.

The final code in index.html:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="https://d3js.org/d3.v5.min.js" defer></script> 
    <script src="app.js" defer></script>
    <title>Let's Make a Face </title>
  </head>
  <body>
    <svg width="500" height="250"></svg>
  </body>
</html>

The final code in app.js:

const svg = d3.select('svg');

const width = parseFloat(svg.attr('width')); 
const height = +svg.attr('height'); 

const g = svg.append('g')  
    .attr('transform', `translate(${width / 2}, ${height / 2})`);

const circle = g.append('circle') 
    .attr('r', height / 2)    
    .attr('fill', 'yellow')
    .attr('stroke', 'black')

const leftEye = g.append('circle')
    .attr('r', 15)    
    .attr('cx', -50)     
    .attr('cy', -40)
    .attr('fill', 'black')

const rightEye = g.append('circle')
    .attr('r', 15)    
    .attr('cx', 50)     
    .attr('cy', -40)
    .attr('fill', 'black')

const mouth = g.append('path')
    .attr('d', d3.arc()({     
        innerRadius: 70,
        outerRadius: 80, 
        startAngle: Math.PI / 2, 
        endAngle: Math.PI * 3/2
    }))

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 )

Connecting to %s