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
    }))

Complete Guide to JavaScript ES6 Destructuring

Destructuring assignment is a JavaScript technique in which you take the values from an array or the properties from an object and assign them to local variables.

Using destructuring allows you to write code that is cleaner, more concise, and more readable.

We will first look at destructuring rules for arrays then for objects.

Arrays

Let’s say we have an array of emojis:

const emojis = ['🐶', '🐱', '🐭'];

We want to pull out the values and assign them to local variables. We have two options of doing this:

 Option 1: One-by-One

const dog = emojis[0]; 
const cat = emojis[1];
const mouse = emojis[2];

By using destructuring assignment, you can accomplish the same thing with just one simple line.

 Option 2: Destructuring

const [dog, cat, mouse] = emojis;

Syntax for Destructuring an Array

Follow the const with a set of brackets []. Inside the brackets, you can assign a variable name for each index in the array. The variable’s value will coincide with the index at which it matches in the array.

Omit values from destructuring

If there is an array value that you don’t want to assign to a variable, you can omit it from the destructuring by adding a comma without a variable name to skip that index. In the example below, we are omitting cat:

const [dog, , mouse] = emojis;

Put remaining values in separate array

If you only want to name the first couple values and accumulate the rest into a smaller array, use the spread syntax (...)

In the example below, we are creating a variable for the first element then putting the rest in a shortened array:

const [dog, ...rest] = emojis;
console.log(rest) // outputs ['🐱', '🐭']

Provide a default value

You can set a default value for the element at an index in case the value in the array is undefined. Think of it like a “fallback” value.

const emojis = [undefined, '🐱', '🐭'];
const [dog = '🐕', cat, mouse] = emojis;
console.log(dog) // outputs '🐕'

Objects

Let’s say we have an object describing a person:

const person = {
name: 'Alex',
age: 23,
inSchool: true
}

We want to pull out the properties of the object and assign them to local variables. We have two options of doing this:

 Option 1: One-by-One

const name = person.name;
const age = person.age;
const inSchool = person.inSchool;

 Option 2: Destructuring

const { name, age, inSchool } = person;

Syntax for Destructuring an Object

Follow the const with a set of braces {}. Inside the braces, put the names of object properties that you want.

The variable names in the destructured object (on the left of the equals) must match the property names exactly.

Use custom variable name for property

To use a different name for the variable than what’s provided on the object itself, put the property name then a colon followed by the new variable name.

const { name: personName, age, inSchool } = person;
console.log(personName) // outputs 'Alex'

Defining a new name is useful when dealing with name collisions or when destructuring with JSON objects whose property names are not valid variable names because they’re strings.

Nested property destructuring

You can also access the properties of objects within objects through destructuring.

const people = {
   person = {
      name = 'Bob'
   }
}

const { person: { name } } = people;
console.log(name) // outputs 'Bob'

Provide a default value

You can provide a default value for a property in case the object does not have that property.

Since the person object we defined above doesn’t have a job property, it will be set to the default value ( ‘Unemployed’):

const { name, age, inSchool, job = 'Unemployed' } = person;

Challenge

We have an array of dogs:

const dogs = [
{ name: "Sally", age: 6, children: { name: "Blue", age: 1 }},
{ name: "Fido", age: 4 },
{ name: "Sissy", age: 3},
]

How would you use destructuring to assign a variable for Sally’s child’s name with just two lines?


Answer:

const [sallyInfo,] = dogs;
const { children: { blueName } } = sallyInfo;
console.log(blueName)   // outputs 'Blue'

I hope you enjoyed this lesson. Thanks for reading! Comment any questions.