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