D3 in Depth




https://www.d3indepth.com/introduction/

Introduction to D3

D3 is a JavaScript library used to create bespoke, interactive charts and maps on the web. While most charting libraries (such as Chart.js and Highcharts) provide ready made charts D3 consists of a large set of building blocks from which custom charts or maps can be constructed. D3’s approach is much lower level than other charting libraries. Creating a bar chart with Chart.js is just a few lines of code. Creating the same chart with D3 you need to: . create SVG rect elements and join them to the data . position the rect elements . size the rect elements according to the data . add axes You might also want to: . animate the bars when the chart first loads . adapt the chart to the container size . add a tooltip This is all additional effort but it gives you complete control over the chart’s appearance and behaviour. If a standard bar, line or pie chart is sufficient you should consider a library such as Chart.js. However if you wish to create a bespoke chart to an exact specification then D3 is worth considering. D3’s features include: . data-driven modification of HTML/SVG elements . loading and transforming data (e.g. CSV data) . generation of complex charts such as treemaps, packed circles and networks . a powerful transition system for animating between views . powerful user interaction support, including panning, zooming and dragging

Data-driven modification of HTML/SVG elements

Given an array of objects such as:
[
  {
    "name": "Andy",
    "score": 37
  },
  {
    "name": "Beth",
    "score": 39
  },
  {
    "name": "Craig",
    "score": 31
  },
  {
    "name": "Diane",
    "score": 35
  },
  {
    "name": "Evelyn",
    "score": 38
  }
]
With D3 we can: . add/remove div elements based on the array length . add a label and bar to each div element . update the width of the bar based on the person’s score View source | Edit in GistRun

Data transformation

D3 provides many functions for manipulating data. For example it has functions to request CSV (comma separated value) data and transform it into an array of objects. Suppose you have a CSV file named films.csv on your server:
Film,Genre,Lead Studio,Audience score %,Worldwide Gross,Year
27 Dresses,Comedy,Fox,71,160.308654,2008
(500) Days of Summer,Comedy,Fox,81,60.72,2009
A Dangerous Method,Drama,Independent,89,8.972895,2011
A Serious Man,Drama,Universal,64,30.68,2009
Across the Universe,Romance,Independent,84,29.367143,2007
Beginners,Comedy,Independent,80,14.31,2011
you can request it using:
d3.csv('films.csv',function(err,data){
  // Do something with the data
})
D3 transforms the CSV data into an array of objects:
[
  {
    "Film": "27 Dresses",
    "Genre": "Comedy",
    "Lead Studio": "Fox",
    "Audience score %": "71",
    "Worldwide Gross": "160.308654",
    "Year": "2008"
  },
  {
    "Film": "(500) Days of Summer",
    "Genre": "Comedy",
    "Lead Studio": "Fox",
    "Audience score %": "81",
    "Worldwide Gross": "60.72",
    "Year": "2009"
  },
  {
    "Film": "A Dangerous Method",
    "Genre": "Drama",
    "Lead Studio": "Independent",
    "Audience score %": "89",
    "Worldwide Gross": "8.972895",
    "Year": "2011"
  },
  {
    "Film": "A Serious Man",
    "Genre": "Drama",
    "Lead Studio": "Universal",
    "Audience score %": "64",
    "Worldwide Gross": "30.68",
    "Year": "2009"
  },
  {
    "Film": "Across the Universe",
    "Genre": "Romance",
    "Lead Studio": "Independent",
    "Audience score %": "84",
    "Worldwide Gross": "29.367143",
    "Year": "2007"
  },
  {
    "Film": "Beginners",
    "Genre": "Comedy",
    "Lead Studio": "Independent",
    "Audience score %": "80",
    "Worldwide Gross": "14.31",
    "Year": "2011"
  }
]
Notice that D3 has used the CSV column names (Film, Genre, Lead Studio etc.) as property names for each object. (The CSV file is from Information is Beautiful.)

Shape generation

D3 is probably best known for its role in producing interactive data visualisations. These are typically made of up SVG (Scalable Vector Graphic) elements such as line, circle, path and text. Suppose you have co-ordinates
var data=[[0,50],[100,80],[200,40],[300,60],[400,30]];
which you’d like to connect with lines. D3 can generate the SVG: View source You can choose to interpolate the points with a curve: View source D3 can also create axes: View source As with most D3 elements, you have a lot of configuration available. For example you can change the axis orientation as well as the number and format of the tick marks: View source

Layouts

D3 provides a number of layouts which are functions that help transform your data into a visual layout. For example, if we have hierarchical (or tree shaped) data, we can use layouts to create a tree view: View source a packed circle view (with leaf nodes sized by revenue): View source and a treemap: View source Under the hood, a layout adds properties (such as position, radius, width and height) to each data element. These properties can then be used when updating the DOM elements.

Transitions

D3 makes it easy to introduce a transition effect between DOM states. Not only can position and size (e.g. width, height, radius) be smoothly transitioned, but also colours: View source As well as producing pleasing visual effects, transitions help users keep track of elements between different states.

User interaction

D3 has some useful tools to enable effect user interaction such as voronoi grids (to optimise hover/click/touch areas), brushing, zooming and dragging. For example, suppose we have a number of small points with a hover-over effect, it’s quite hard to position the mouse pointer exactly over a circle: View source However if the voronoi grid is enabled (click ‘Enable Voronoi’ above) polygons are enabled which help determine the closest point to the user’s hover/click/touch. It’s now much easier to locate a point. (Click ‘View Voronoi’ to see the underlying polygons.)

Selections

D3 selections allow DOM elements to be selected in order to do something with them, be it changing style, modifying their attributes, performing data-joins or inserting/removing elements. For example, given 5 circles: we can use d3.selectAll to select the circles and .style and .attr to modify them:
d3.selectAll('circle')
  .style('fill','orange')
  .attr('r',function(){
    return10+Math.random()*40;
  });

Making selections

D3 has two functions to make selections d3.select and d3.selectAll. d3.select selects the first matching element whilst d3.selectAll selects all matching elements. Each function takes a single argument which specifies the selector string. For example to select all elements with class item use d3.selectAll('.item').

Modifying elements

Once we’ve made a selection we can modify the elements in it using the following functions:
NameBehaviourExample
.styleUpdate the styled3.selectAll('circle').style('fill', 'red')
.attrUpdate an attributed3.selectAll('rect').attr('width', 10)
.classedAdd/remove a class attributed3.select('.item').classed('selected', true)
.propertyUpdate an element's propertyd3.selectAll('.checkbox').property('checked', false)
.textUpdate the text contentd3.select('div.title').text('My new book')
.htmlChange the html contentd3.select('.legend').html('<div class="block"></div><div>0 - 10</div>')
Whether .select or .selectAll is used, all elements in the selection will be modified. Here’s an example of all of these functions in use: View source

Updating selections with functions

In addition to passing constant values such as red, 10 and true to .style, .attr, .classed, .property, .text and .html we can pass in a function:
d3.selectAll('circle')
  .attr('cx',function(d,i){
    returni*100;
  });
The function typically accepts two arguments d and i. The first argument d is the joined data (see the data joins section) and i is the index of the element within the selection. If we want to update elements in a selection according to their position within the selection, we can use the i argument. For example to position some rect elements horizontally we can use:
d3.selectAll('rect')
  .attr('x',function(d,i){
    returni*40;
  });
View source In the majority of cases when functions are passed in, anonymous functions are used. However we can also use named functions e.g.
functionpositionRects(d,i){
  returni*40;
}

d3.selectAll('rect')
  .attr('x',positionRects);

Handling events

We can add event handlers to selected elements using .on which expects a callback function into which is passed two arguments d and i. As before, d is the joined data (see the data joins section) and i is the index of the element within the selection.) The most common events include (see MDN event reference for more details):
Event nameDescription
clickElement has been clicked
mouseenterMouse pointer has moved onto the element
mouseoverMouse pointer has moved onto the element or its children
mouseleaveMouse pointer has moved off the element
mouseoutMouse pointer has moved off the element or its children
mousemoveMouse pointer has moved over the element
Let’s set up an event handler to update a status element with the index of the clicked element:
d3.selectAll('circle')
  .on('click',function(d,i){
    d3.select('.status')
      .text('You clicked on circle '+i);
  });
View source In the event callback function the this variable is bound to the DOM element. This allows us to do things such as:
d3.selectAll('circle')
  .on('click',function(d,i){
    d3.select(this)
      .style('fill','orange');
  });
View source Note that this is a DOM element and not a D3 selection so if we wish to modify it using D3 we must first select it using d3.select(this).

Inserting and removing elements

Elements can be added to a selection using .append and .insert whilst elements can be removed using .remove. .append appends an element to the children of each element in the selection. The first argument specifies the type of element. As an example let’s start with 3 g elements, each containing a circle:
<gclass="item"transform="translate(0, 0)">
  <circler="40"/>
</g>
<gclass="item"transform="translate(120, 0)">
  <circler="40"/>
</g>
<gclass="item"transform="translate(240, 0)">
  <circler="40"/>
</g>
We can append a text element to each using:
d3.selectAll('g.item')
  .append('text')
  .text(function(d,i){
    returni+1;
  });
resulting in a text being added to each g.item:
<gclass="item"transform="translate(0, 0)">
  <circler="40"/>
  <text>1</text>
</g>
<gclass="item"transform="translate(120, 0)">
  <circler="40"/>
  <text>2</text>
</g>
<gclass="item"transform="translate(240, 0)">
  <circler="40"/>
  <text>3</text>
</g>
View source (.append is commonly used in the context of enter/exit where it has different behaviour.) .insert is similar to .append but it allows us to specify a before element to which, you guessed it, the new element is attached. Therefore if we run the same example again, but choosing to insert the text element before the circle element we get:
d3.selectAll('g.item')
  .insert('text','circle')
  .text(function(d,i){
    returni+1;
  });
and the DOM will look like:
<gclass="item"transform="translate(0, 0)">
  <text>1</text>
  <circler="40"/>
</g>
<gclass="item"transform="translate(120, 0)">
  <text>2</text>
  <circler="40"/>
</g>
<gclass="item"transform="translate(240, 0)">
  <text>3</text>
  <circler="40"/>
</g>
View source .remove removes all the elements in a selection. For example, given some circles, we can remove them using:
d3.selectAll('circle')
  .remove();
View source

Chaining

Most selection functions return the selection, meaning that selection functions such as .style, .attr and .on can be chained:
d3.selectAll('circle')
  .style('fill','orange')
  .attr('r',20)
  .on('click',function(d,i){
    d3.select('.status')
      .text('You clicked on circle '+i);
  });
View source

Each and call

.each allows a function to be called on each element of a selection and .call allows a function to be called on the selection itself. In the case of .each D3 passes in the joined datum (usually represented by d) and the index (usually represented by i). Not only can .each enable reusable components but it also allows computations to be shared across calls to .style, .attr etc. Here’s an example of using .each to call a reusable component:
functionaddNumberedCircle(d,i){
  d3.select(this)
    .append('circle')
    .attr('r',40);

  d3.select(this)
    .append('text')
    .text(i+1)
    .attr('y',50)
    .attr('x',30);
}

d3.selectAll('g.item')
  .each(addNumberedCircle);
View source Here’s an example of .each used for the latter:
d3.selectAll('circle')
  .each(function(d,i){
    var odd=i%2===1;

    d3.select(this)
      .style('fill',odd?'orange':'#ddd')
      .attr('r',odd?40:20);
  });
View source In the case of .call D3 passes in the selection itself. This is a common pattern for reusable components. In the following example we create a similar component to before using .call. This time the selection gets passed into the component (rather than d and i):
functionaddNumberedCircle(selection){
  selection
    .append('circle')
    .attr('r',40);

  selection
    .append('text')
    .text(function(d,i){
      returni+1;
    })
    .attr('y',50)
    .attr('x',30);
}

d3.selectAll('g.item')
  .call(addNumberedCircle);

Filtering and sorting selections

We can filter a selection using .filter. A function is usually passed into .filter which returns true if the element should be included. .filter returns the filtered selection. In this example we filter through even-numbered elements and colour them orange:
d3.selectAll('circle')
  .filter(function(d,i){
    returni%2===0;
  })
  .style('fill','orange');
View source Sorting only really makes sense if data has been joined to the selection, so please read up on data joins first. We can sort elements in a selection by calling .sort and passing in a comparator function. The comparator function has two arguments, usually a and b, which represent the datums on the two elements being compared. If the comparator function returns a negative number, a will be placed before b and if positive, a will be placed after b. Thus if we have the following data joined to a selection:
[
  {
    "name": "Andy",
    "score": 37
  },
  {
    "name": "Beth",
    "score": 39
  },
  {
    "name": "Craig",
    "score": 31
  },
  {
    "name": "Diane",
    "score": 35
  },
  {
    "name": "Evelyn",
    "score": 38
  }
]
we can sort by score using:
  d3.selectAll('.person')
    .sort(function(a,b){
      returnb.score-a.score;
    });
View source

Data joins

Given an array of data and a D3 selection we can attach or ‘join’ each array element to each element of the selection. This creates a close relationship between your data and graphical elements which makes data-driven modification of the elements straightforward. For example if we have some SVG circles:
<circler="40"/>
<circler="40"cx="120"/>
<circler="40"cx="240"/>
<circler="40"cx="360"/>
<circler="40"cx="480"/>
and some data:
var scores=[
  {
    "name":"Andy",
    "score":25
  },
  {
    "name":"Beth",
    "score":39
  },
  {
    "name":"Craig",
    "score":42
  },
  {
    "name":"Diane",
    "score":35
  },
  {
    "name":"Evelyn",
    "score":48
  }
]
we can select the circles and then join the array to it:
d3.selectAll('circle')
  .data(scores);
We can now manipulate the circles according to the joined data:
d3.selectAll('circle')
  .attr('r',function(d){
    returnd.score;
  });
The above code sets the radius of each circle to each person’s score. View source

Making a data join

Given an array myData and a selection s a data join is created using the function .data:
var myData=[10,40,20,30];

var s=d3.selectAll('circle');

s.data(myData);
The array can contain any type e.g. objects:
var cities=[
  {name:'London',population:8674000},
  {name:'New York',population:8406000},
  {name:'Sydney',population:4293000}
];

var s=d3.selectAll('circle');

s.data(cities);
Although a couple of things occur when .data is called (see Under the Hood and Enter/Exit) you probably won’t notice much change after joining your data. The real magic happens when you want to modify the elements in your selection according to your data.

Data-driven modification of elements

Once we’ve joined data to a selection we can modify elements by passing a function into the likes of .style and .attr (which we covered in Selections):
d3.selectAll('circle')
  .attr('r',function(d){
    returnd;
  });
For each element in the selection D3 will call this function, passing in the element’s joined data as the first argument d. The function’s return value is used to set the style or attribute value. For example, given some circles:
<circle/>
<circle/>
<circle/>
<circle/>
<circle/>
and some data:
var myData=[10,40,20,30,50];
let’s perform the data join:
var s=d3.selectAll('circle');

// Do the join
s.data(myData);
Now let’s update the radius of each circle in the selection to be equal to the corresponding data values:
s.attr('r',function(d){
  returnd;
});
The function that’s passed into .attr is called 5 times (once for each element in the selection). The first time round d will be 10 and so the circle’s radius will be set to 10. The second time round it’ll be 40 and so on. In the above example the function simply returns d meaning that the first circle’s radius will be set to 10, the second’s radius to 40 and so. We can return anything we like from the function, so long as it’s a valid value for the style, attribute etc. that we’re modifying. (It’s likely that some expression involving d will be returned.) For example we can set the radius to twice d using:
s.attr('r',function(d){
  return2*d;
});
Now let’s set a class on each element if the value is greater or equal to 40:
s.classed('high',function(d){
  returnd>=40;// returns true or false
});
and finally we’ll position the circles horizontally using the i argument (see Selections):
s.attr('cx',function(d,i){
  returni*120;
});
Putting this all together we get:
var myData=[10,40,20,30,50];

var s=d3.selectAll('circle');

// Do the data join
s.data(myData);

// Modify the selected elements
s.attr('r',function(d){
  returnd;
  })
  .classed('high',function(d){
    returnd>=40;
  })
  .attr('cx',function(d,i){
    returni*120;
  });
View source

Arrays of objects

If we have an array of objects we can join it in the usual manner:
var cities=[
  {name:'London',population:8674000},
  {name:'New York',population:8406000},
  {name:'Sydney',population:4293000},
  {name:'Paris',population:2244000},
  {name:'Beijing',population:11510000}
];

var s=d3.selectAll('circle');

s.data(cities);
Now when we modify elements based on the joined data, d will represent the joined object. Thus for the first element in the selection, d will be { name: 'London', population: 8674000}. Let’s set the circle radii proportionally to each city’s population:
s.attr('r',function(d){
    var scaleFactor=0.000005;
    returnd.population*scaleFactor;
  })
  .attr('cx',function(d,i){
    returni*120;
  });
View source Of course, we not restricted to modifying circle elements. Supposing we had some rect and text elements, we can build a simple bar chart using what we’ve learnt:
var cities=[
  {name:'London',population:8674000},
  {name:'New York',population:8406000},
  {name:'Sydney',population:4293000},
  {name:'Paris',population:2244000},
  {name:'Beijing',population:11510000}
];

// Join cities to rect elements and modify height, width and position
d3.selectAll('rect')
  .data(cities)
  .attr('height',19)
  .attr('width',function(d){
    var scaleFactor=0.00004;
    returnd.population*scaleFactor;
  })
  .attr('y',function(d,i){
    returni*20;
  })

// Join cities to text elements and modify content and position
d3.selectAll('text')
  .data(cities)
  .attr('y',function(d,i){
    returni*20+13;
  })
  .attr('x',-4)
  .text(function(d){
    returnd.name;
  });
View source

Under the hood

When D3 performs a data join it adds an attribute __data__ to each DOM element in the selection and assigns the joined data to it. We can inspect this in Google Chrome by right clicking on an element and choosing Inspect. This’ll reveal Chrome’s debug window. Look for a tab named ‘Properties’ and open it. Expand the element then expand the __data__ attribute. This is the data that D3 has joined to the element. (See screencast.) Being able to check the joined data in this manner is particularly useful when debugging as it allows us to check whether our data join is behaving as expected.

What if our array’s longer (or shorter) than the selection?

So far we’ve looked at data joins where the selection is exactly the same length as the data array. Clearly this won’t always be the case and D3 handles this using enter and exit. To learn more see the enter and exit section.

What’s .datum for?

There are a few instances (such as when dealing with geographic visualisations) where it’s useful to join a single bit of data with a selection (usually containing a single element). Supposing we have an object:
var featureCollection={type:'FeatureCollection',features:features};
we can join it to a single element using .datum:
d3.select('path#my-map')
  .datum(featureCollection);
This just adds a __data__ attribute to the element and assigns the joined data (featureCollection in this case) to it. See the geographic visualisations section for a deeper look at this. Most of the time .data will be used for data joins. .datum is reserved for special cases such as the above.

Enter and exit

In the Data joins section we show how to join an array of data to a D3 selection. To recap, given some DOM elements:
<divid="content">
  <div></div>
  <div></div>
  <div></div>
</div>
and some data:
var myData=[10,40,20];
we join the array to the div elements using:
d3.select('#content')
  .selectAll('div')
  .data(myData);
In this example myData is the same length as the selection. However, what happens if the array has more (or less) elements than the selection? . if the array is longer than the selection there’s a shortfall of DOM elements and we need to add elements . if the array is shorter than the selection there’s a surplus of DOM elements and we need to remove elements Fortunately D3 can help in adding and removing DOM elements using two functions .enter and .exit.

.enter

.enter identifies any DOM elements that need to be added when the joined array is longer than the selection. It’s defined on an update selection (the selection returned by .data):
d3.select('#content')
  .selectAll('div')
  .data(myData)
  .enter();
.enter returns an enter selection which basically represents the elements that need to be added. It’s usually followed by .append which adds elements to the DOM:
d3.select('#content')
  .selectAll('div')
  .data(myData)
  .enter()
  .append('div');
Let’s look at an example. Suppose we have the following div elements:
<divid="content">
  <div></div>
  <div></div>
  <div></div>
</div>
and this data:
var myData=['A','B','C','D','E'];
we use .enter and .append to add div elements for D and E:
  d3.select('#content')
    .selectAll('div')
    .data(myData)
    .enter()
    .append('div');
View source Note that we can join an array to an empty selection which is a very common pattern in the examples on the D3 website. View source

.exit

.exit returns an exit selection which consists of the elements that need to be removed from the DOM. It’s usually followed by .remove:
d3.select('#content')
  .selectAll('div')
  .data(myData)
  .exit()
  .remove();
Let’s repeat the example above, but using .exit. Starting with elements:
<divid="content">
  <div></div>
  <div></div>
  <div></div>
</div>
and data (notice that it’s shorter than the selection):
var myData=['A'];
we use .exit and .remove to remove the surplus elements:
d3.select('#content')
  .selectAll('div')
  .data(myData)
  .exit()
  .remove();
View source

Putting it all together

So far in this section we’ve not concerned ourselves with modifying elements using functions such as .style, .attr and .classed. D3 allows us to be specific about which elements are modified when new elements are entering. We can modify: . the existing elements . the entering elements . both existing and entering elements (Most of the time the last option is sufficient, but sometimes we might want to style entering elements differently.) The existing elements are represented by the update selection. This is the selection returned by .data and is assigned to u in this example:
var myData=['A','B','C','D','E'];

var u=d3.select('#content')
  .selectAll('div')
  .data(myData);

u.enter()
  .append('div');

u.text(function(d){
  returnd;
});
View source When the button is clicked, new elements are added, but because .text is only called on the update selection, it’s only the existing elements that are modified. (Note that if the button is clicked a second time, all the elements are modified. This is because the selection will contain all 5 div elements. Don’t worry about this too much if you’re new here!) The entering elements are represented by the enter selection. This is the selection returned by .enter. We can modify the enter selection using:
var myData=['A','B','C','D','E'];

var u=d3.select('#content')
  .selectAll('div')
  .data(myData);

u.enter()
  .append('div')
  .text(function(d){
    returnd;
  });
View source When the button is clicked, new elements are added and their text content is updated. Only the entering elements have their text updated because we call .text on the enter selection. If we want to modify the existing and entering elements we could call .text on the update and enter selections. However D3 has a function .merge which can merge selections together. This means we can do the following:
var myData=['A','B','C','D','E'];

var u=d3.select('#content')
  .selectAll('div')
  .data(myData);

u.enter()
  .append('div')
  .merge(u)
  .text(function(d){
    returnd;
  });
View source
This is a big departure from v3. The entering elements were implicitly included in the update selection so there was no need for .merge.

General update pattern

A common pattern (proposed by D3’s creator Mike Bostock) is to encapsulate the above behaviour of adding, removing and updating DOM elements in a single function:
functionupdate(data){
  var u=d3.select('#content')
    .selectAll('div')
    .data(data);

  u.enter()
    .append('div')
    .merge(u)
    .text(function(d){
      returnd;
    });

  u.exit().remove();
}
View source Typically the update function is called whenever the data changes. Here’s another example where we colour entering elements orange:
functionupdate(data){
  var u=d3.select('#content')
    .selectAll('div')
    .data(data);

  u.enter()
    .append('div')
    .classed('new',true)
    .text(function(d){
      returnd;
    });

  u.text(function(d){
      returnd;
    })
    .classed('new',false);

  u.exit().remove();
}
View source

Data join key function

When we do a data join D3 binds the first array element to the first element in the selection, the second array element to the second element in the selection and so on. However, if the order of array elements changes (such as during element sorting, insertion or removal), the array elements might get joined to different DOM elements. We can solve this problem by providing .data with a key function. This function should return a unique id value for each array element, allowing D3 to make sure each array element stays joined to the same DOM element. Let’s look at an example, first using a key function, and then without. We start with an array ['Z'] and each time the button is clicked a new letter is added at the start of the array. Because of the key function each letter will stay bound to the same DOM element meaning that when a new letter is inserted each existing letter transitions into a new position: View source Without a key function the DOM elements’ text is updated (rather than position) meaning we lose a meaningful transition effect: View source There’s many instances when key functions are not required but if there’s any chance that your data elements can change position (e.g. through insertion or sorting) and you’re using transitions then you should probably use them.

Scale functions

Scale functions are JavaScript functions that: . take an input (usually a number, date or category) and . return a value (such as a coordinate, a colour, a length or a radius) They’re typically used to transform (or ‘map’) data values into visual variables (such as position, length and colour). For example, suppose we have some data:
[0,2,3,5,7.5,9,10]
we can create a scale function using:
var myScale=d3.scaleLinear()
  .domain([0,10])
  .range([0,600]);
D3 creates a function myScale which accepts input between 0 and 10 (the domain) and maps it to output between 0 and 600 (the range). We can use myScale to calculate positions based on the data:
myScale(0);   // returns 0
myScale(2);   // returns 120
myScale(3);   // returns 180
...
myScale(10);  // returns 600
View source Scales are mainly used for transforming data values to visual variables such as position, length and colour. For example they can transform: . data values into lengths between 0 and 500 for a bar chart . data values into positions between 0 and 200 for line charts . % change data (+4%, +10%, -5% etc.) into a continuous range of colours (with red for negative and green for positive) . dates into positions along an x-axis.

Constructing scales

(In this section we’ll just focus on linear scales as these are the most commonly used scale type. We’ll cover other types later on.) To create a linear scale we use:
var myScale=d3.scaleLinear();
Version 4 uses a different naming convention to v3. We use d3.scaleLinear() in v4 and d3.scale.linear() in v3.
As it stands the above function isn’t very useful so we can configure the input bounds (the domain) as well as the output bounds (the range):
myScale
  .domain([0,100])
  .range([0,800]);
Now myScale is a function that accepts input between 0 and 100 and linearly maps it to between 0 and 800.
myScale(0);    // returns 0
myScale(50);   // returns 400
myScale(100);  // returns 800
Try experimenting with scale functions by copying code fragments and pasting them into the console or using a web-based editor such as JS Bin.

D3 scale types

D3 has around 12 different scale types (scaleLinear, scalePow, scaleQuantise, scaleOrdinal etc.) and broadly speaking they can be classified into 3 groups: . scales with continuous input and continuous output . scales with continuous input and discrete output . scales with discrete input and discrete output We’ll now look at these 3 categories one by one.

Scales with continuous input and continuous output

In this section we cover scale functions that map from a continuous input domain to a continuous output range.

scaleLinear

Linear scales are probably the most commonly used scale type as they are the most suitable scale for transforming data values into positions and lengths. If there’s one scale type to learn about this is the one. They use a linear function (y = m * x + b) to interpolate across the domain and range.
var linearScale=d3.scaleLinear()
  .domain([0,10])
  .range([0,600]);

linearScale(0);   // returns 0
linearScale(5);   // returns 300
linearScale(10);  // returns 600
Typical uses are to transform data values into positions and lengths, so when creating bar charts, line charts (as well as many other chart types) they are the scale to use. The output range can also be specified as colours:
var linearScale=d3.scaleLinear()
  .domain([0,10])
  .range(['yellow','red']);

linearScale(0);   // returns "rgb(255, 255, 0)"
linearScale(5);   // returns "rgb(255, 128, 0)"
linearScale(10);  // returns "rgb(255, 0, 0)"
This can be useful for visualisations such as choropleth maps, but also consider scaleQuantize, scaleQuantile and scaleThreshold.

scalePow

More included for completeness, rather than practical usefulness, the power scale interpolates using a power (y = m * x^k + b) function. The exponent k is set using .exponent():
var powerScale=d3.scalePow()
  .exponent(0.5)
  .domain([0,100])
  .range([0,30]);

powerScale(0);   // returns 0
powerScale(50);  // returns 21.21...
powerScale(100);// returns 30

scaleSqrt

The scaleSqrt scale is a special case of the power scale (where k = 0.5) and is useful for sizing circles by area (rather than radius). (When using circle size to represent data, it’s considered better practice to set the area, rather than the radius proportionally to the data.)
var sqrtScale=d3.scaleSqrt()
  .domain([0,100])
  .range([0,30]);

sqrtScale(0);   // returns 0
sqrtScale(50);  // returns 21.21...
sqrtScale(100);// returns 30
View source

scaleLog

Log scales interpolate using a log function (y = m * log(x) + b) and can be useful when the data has an exponential nature to it.
var logScale=d3.scaleLog()
  .domain([10,100000])
  .range([0,600]);

logScale(10);     // returns 0
logScale(100);    // returns 150
logScale(1000);   // returns 300
logScale(100000);// returns 600
View source

scaleTime

scaleTime is similar to scaleLinear except the domain is expressed as an array of dates. (It’s very useful when dealing with time series data.)
timeScale=d3.scaleTime()
  .domain([newDate(2016,0,1),newDate(2017,0,1)])
  .range([0,700]);

timeScale(newDate(2016,0,1));   // returns 0
timeScale(newDate(2016,6,1));   // returns 348.00...
timeScale(newDate(2017,0,1));   // returns 700
View source

scaleSequential

scaleSequential is used for mapping continuous values to an output range determined by a preset (or custom) interpolator. (An interpolator is a function that accepts input between 0 and 1 and outputs an interpolated value between two numbers, colours, strings etc.) D3 provides a number of preset interpolators including many colour ones. For example we can use d3.interpolateRainbow to create the well known rainbow colour scale:
var sequentialScale=d3.scaleSequential()
  .domain([0,100])
  .interpolator(d3.interpolateRainbow);

sequentialScale(0);   // returns 'rgb(110, 64, 170)'
sequentialScale(50);  // returns 'rgb(175, 240, 91)'
sequentialScale(100);// returns 'rgb(110, 64, 170)'
View source Note that the interpolator determines the output range so you don’t need to specify the range yourself. The example below shows some of the other colour interpolators provided by D3: View source There’s also a plug-in d3-scale-chromatic which provides the well known ColorBrewer colour schemes.

Clamping

By default scaleLinear, scalePow, scaleSqrt, scaleLog, scaleTime and scaleSequential allow input outside the domain. For example:
var linearScale=d3.scaleLinear()
  .domain([0,10])
  .range([0,100]);

linearScale(20);  // returns 200
linearScale(-10);// returns -100
In this instance the scale function uses extrapolation for values outside the domain. If we’d like the scale function to be restricted to input values inside the domain we can ‘clamp’ the scale function using .clamp():
linearScale.clamp(true);

linearScale(20);  // returns 100
linearScale(-10);// returns 0
We can switch off clamping using .clamp(false).

Nice

If the domain has been computed automatically from real data (e.g. by using d3.extent) the start and end values might not be round figures. This isn’t necessarily a problem, but if using the scale to define an axis, it can look a bit untidy:
var data=[0.243,0.584,0.987,0.153,0.433];
var extent=d3.extent(data);

var linearScale=d3.scaleLinear()
  .domain(extent)
  .range([0,100]);
View source Therefore D3 provides a function .nice() on the scales in this section which will round the domain to ‘nice’ round values:
linearScale.nice();
View source Note that .nice() must be called each time the domain is updated.

Multiple segments

The domain and range of scaleLinear, scalePow, scaleSqrt, scaleLog and scaleTime usually consists of two values, but if we provide 3 or more values the scale function is subdivided into multiple segments:
var linearScale=d3.scaleLinear()
  .domain([-10,0,10])
  .range(['red','#ddd','blue']);

linearScale(-10);  // returns "rgb(255, 0, 0)"
linearScale(0);    // returns "rgb(221, 221, 221)"
linearScale(5);    // returns "rgb(111, 111, 238)"
View source Typically multiple segments are used for distinguishing between negative and positive values (such as in the example above). We can use as many segments as we like as long as the domain and range are of the same length.

Inversion

The .invert() method allows us to determine a scale function’s input value given an output value (provided the scale function has a numeric domain):
var linearScale=d3.scaleLinear()
  .domain([0,10])
  .range([0,100]);

linearScale.invert(50);   // returns 5
linearScale.invert(100);  // returns 10
A common use case is when we want to convert a user’s click along an axis into a domain value: View source

Scales with continuous input and discrete output

scaleQuantize

scaleQuantize accepts continuous input and outputs a number of discrete quantities defined by the range.
var quantizeScale=d3.scaleQuantize()
  .domain([0,100])
  .range(['lightblue','orange','lightgreen','pink']);

quantizeScale(10);   // returns 'lightblue'
quantizeScale(30);  // returns 'orange'
quantizeScale(90);  // returns 'pink'
Each range value is mapped to an equal sized chunk in the domain so in the example above: . 0 ≤ u < 25 is mapped to ‘lightblue’ . 25 ≤ u < 50 is mapped to ‘orange’ . 50 ≤ u < 75 is mapped to ‘lightgreen’ . 75 ≤ u < 100 is mapped to ‘pink’ where u is the input value. View source Note also that input values outside the domain are clamped so in our example quantizeScale(-10) returns ‘lightblue’ and quantizeScale(110) returns ‘pink’.

scaleQuantile

scaleQuantile maps continuous numeric input to discrete values. The domain is defined by an array of numbers:
var myData=[0,5,7,10,20,30,35,40,60,62,65,70,80,90,100];

var quantileScale=d3.scaleQuantile()
  .domain(myData)
  .range(['lightblue','orange','lightgreen']);

quantileScale(0);   // returns 'lightblue'
quantileScale(20);  // returns 'lightblue'
quantileScale(30);  // returns 'orange'
quantileScale(65);  // returns 'lightgreen'
View source The (sorted) domain array is divided into n equal sized groups where n is the number of range values. Therefore in the above example the domain array is split into 3 groups where: . the first 5 values are mapped to ‘lightblue’ . the next 5 values to ‘orange’ and . the last 5 values to ‘lightgreen’. The split points of the domain can be accessed using .quantiles():
quantileScale.quantiles();  // returns [26.66..., 63]
If the range contains 4 values quantileScale computes the quartiles of the data. In other words, the lowest 25% of the data is mapped to range[0], the next 25% of the data is mapped to range[1] etc.

scaleThreshold

scaleThreshold maps continuous numeric input to discrete values defined by the range. n-1 domain split points are specified where n is the number of range values. In the following example we split the domain at 0, 50 and 100 . u < 0 is mapped to ‘#ccc’ . 0 ≤ u < 50 to ‘lightblue’ . 50 ≤ u < 100 to ‘orange’ . u ≥ 100 to ‘#ccc’ where u is the input value.
var thresholdScale=d3.scaleThreshold()
  .domain([0,50,100])
  .range(['#ccc','lightblue','orange','#ccc']);

thresholdScale(-10);  // returns '#ccc'
thresholdScale(20);   // returns 'lightblue'
thresholdScale(70);   // returns 'orange'
thresholdScale(110);  // returns '#ccc'
View source

Scales with discrete input and discrete output

scaleOrdinal

scaleOrdinal maps discrete values (specified by an array) to discrete values (also specified by an array). The domain array specifies the possible input values and the range array the output values. The range array will repeat if it’s shorter than the domain array.
var myData=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec']

var ordinalScale=d3.scaleOrdinal()
  .domain(myData)
  .range(['black','#ccc','#ccc']);

ordinalScale('Jan');  // returns 'black';
ordinalScale('Feb');  // returns '#ccc';
ordinalScale('Mar');  // returns '#ccc';
ordinalScale('Apr');  // returns 'black';
View source By default if a value that’s not in the domain is used as input, the scale will implicitly add the value to the domain:
ordinalScale('Monday');  // returns 'black';
If this isn’t the desired behvaiour we can specify an output value for unknown values using .unknown():
ordinalScale.unknown('Not a month');
ordinalScale('Tuesday');// returns 'Not a month'
D3 can also provide preset colour schemes (from ColorBrewer):
var ordinalScale=d3.scaleOrdinal()
  .domain(myData)
  .range(d3.schemePaired);
View source (Note that the Brewer colour schemes are defined within a separate file d3-scale-chromatic.js.)

scaleBand

When creating bar charts scaleBand helps to determine the geometry of the bars, taking into account padding between each bar. The domain is specified as an array of values (one value for each band) and the range as the minimum and maximum extents of the bands (e.g. the total width of the bar chart). In effect scaleBand will split the range into n bands (where n is the number of values in the domain array) and compute the positions and widths of the bands taking into account any specified padding.
var bandScale=d3.scaleBand()
  .domain(['Mon','Tue','Wed','Thu','Fri'])
  .range([0,200]);

bandScale('Mon');// returns 0
bandScale('Tue');// returns 40
bandScale('Fri');// returns 160
The width of each band can be accessed using .bandwidth():
bandScale.bandwidth();  // returns 40
Two types of padding may be configured: . paddingInner which specifies (as a percentage of the band width) the amount of padding between each band . paddingOuter which specifies (as a percentage of the band width) the amount of padding before the first band and after the last band Let’s add some inner padding to the example above:
bandScale.paddingInner(0.05);

bandScale.bandWidth();  // returns 38.38...
bandScale('Mon');       // returns 0
bandScale('Tue');       // returns 40.40...
Putting this all together we can create this bar chart: View source

scalePoint

scalePoint creates scale functions that map from a discrete set of values to equally spaced points along the specified range:
var pointScale=d3.scalePoint()
  .domain(['Mon','Tue','Wed','Thu','Fri'])
  .range([0,500]);

pointScale('Mon');  // returns 0
pointScale('Tue');  // returns 125
pointScale('Fri');  // returns 500
View source The distance between the points can be accessed using .step():
pointScale.step();  // returns 125
Outside padding can be specified as the ratio of the padding to point spacing. For example, for the outside padding to be a quarter of the point spacing use a value of 0.25:
pointScale.padding(0.25);

pointScale('Mon');  // returns 27.77...
pointScale.step();  // returns 111.11...

Further reading

ColorBrewer schemes for D3 Mike Bostock on d3-scale

Shapes

This chapter looks at the functions D3 provides for taking the effort out of creating vector shapes such as lines: curves: pie chart segments: and symbols:

SVG

First a little background on Scalable Vector Graphics (SVG). The shapes in the examples above are made up of SVG path elements. Each of them has a d attribute (path data) which defines the shape of the path. The path data consists of a list of commands (e.g. M0,80L100,100L200,30L300,50L400,40L500,80) such as ‘move to’ and ‘draw a line to’ (see the SVG specification for more detail). We could create path data ourselves but D3 can help us using functions known as generators. These come in various forms:
lineGenerates path data for a multi-segment line (typically for line charts)
areaGenerates path data for an area (typically for stacked line charts and streamgraphs)
stackGenerates stack data from multi-series data
arcGenerates path data for an arc (typically for pie charts)
pieGenerates pie angle data from array of data
symbolGenerates path data for symbols such as plus, star, diamond

Line generator

D3’s line generator produces a path data string given an array of co-ordinates. We start by constructing a line generator using d3.line():
var lineGenerator=d3.line();
lineGenerator is just a function that accepts an array of co-ordinates and outputs a path data string. So let’s go ahead and define an array of co-ordinates:
var points=[
  [0,80],
  [100,100],
  [200,30],
  [300,50],
  [400,40],
  [500,80]
];
and now call lineGenerator, passing in our array:
var pathData=lineGenerator(points);
// pathData is "M0,80L100,100L200,30L300,50L400,40L500,80"
All lineGenerator has done is create a string of M (move to) and L (line to) commands from our array of points. We can now use pathData to set the d attribute of a path element:
d3.select('path')
  .attr('d',pathData);
View source We can also configure our line generator in a number of ways: . .x() and .y() accessor functions, . .defined() (to handle missing data), . .curve (to specify how the points are interpolated) and . .context() to render to a canvas element.

.x() and .y() accessor functions

By default each array element represents a co-ordinate defined by a 2-dimensional array (e.g. [0, 100]). However we can specify how the line generator interprets each array element using accessor functions .x() and .y(). For example suppose our data is an array of objects:
var data=[
  {value:10},
  {value:50},
  {value:30},
  {value:40},
  {value:20},
  {value:70},
  {value:50}
];
We can define the accessors like so:
lineGenerator
  .x(function(d,i){
    returnxScale(i);
  })
  .y(function(d){
    returnyScale(d.value);
  });
In this example we’re using the index of the array to define the x position. Note also that we’re using scale functions: View source

.defined()

We can configure the behaviour when there’s missing data. Suppose our data has a gap in it:
var points=[
  [0,80],
  [100,100],
  null,
  [300,50],
  [400,40],
  [500,80]
];
we can tell our line generator that each co-ordinate is valid only if it’s non-null:
lineGenerator
  .defined(function(d){
    returnd!==null;
  });
Now when we call lineGenerator it leaves a gap in the line: View source (Without configuring .defined this last call returns an error.)

.curve()

We can also configure how the points are interpolated. For example we can interpolate each data point with a B-spline:
var lineGenerator=d3.line()
  .curve(d3.curveCardinal);
View source Although there’s a multitude of different curve types available they can be divided into two camps: those which pass through the points (curveLinear, curveCardinal, curveCatmullRom, curveMonotone, curveNatural and curveStep) and those that don’t (curveBasis and curveBundle). See the curve explorer for more information.

Rendering to canvas

By default the shape generators output SVG path data. However they can be configured to draw to a canvas element using the .context() function:
var context=d3.select('canvas').node().getContext('2d');

lineGenerator.context(context);

context.strokeStyle='#999';
context.beginPath();
lineGenerator(points);
context.stroke();
View source

Radial line

The radial line generator is similar to the line generator but the points are transformed by angle (working clockwise from 12 o’clock) and radius, rather than x and y:
var radialLineGenerator=d3.radialLine();

var points=[
  [0,80],
  [Math.PI*0.25,80],
  [Math.PI*0.5,30],
  [Math.PI*0.75,80],
  [Math.PI,80],
  [Math.PI*1.25,80],
  [Math.PI*1.5,80],
  [Math.PI*1.75,80],
  [Math.PI*2,80]
];

var pathData=radialLineGenerator(points);
View source Accessor functions .angle() and .radius() are also available:
radialLineGenerator
  .angle(function(d){
    returnd.a;
  })
  .radius(function(d){
    returnd.r;
  });

var points=[
  {a:0,r:80},
  {a:Math.PI*0.25,r:80},
  {a:Math.PI*0.5,r:30},
  {a:Math.PI*0.75,r:80},
  ...
];

var pathData=radialLineGenerator(points);

Area generator

The area generator outputs path data that defines an area between two lines. By default it generates the area between y=0 and a multi-segment line defined by an array of points:
var areaGenerator=d3.area();

var points=[
  [0,80],
  [100,100],
  [200,30],
  [300,50],
  [400,40],
  [500,80]
];

var pathData=areaGenerator(points);
View source We can configure the baseline using the .y0() accessor function:
areaGenerator.y0(150);
View source We can also feed a function into the .y0() accessor, likewise the .y1() accessor:
areaGenerator
  .x(function(d){
    returnd.x;
  })
  .y0(function(d){
    returnyScale(d.low);
  })
  .y1(function(d){
    returnyScale(d.high);
  });

var points=[
  {x:0,low:30,high:80},
  {x:100,low:80,high:100},
  {x:200,low:20,high:30},
  {x:300,low:20,high:50},
  {x:400,low:10,high:40},
  {x:500,low:50,high:80}
];
Typically .y0() defines the baseline and .y1() the top line. Note that we’ve also used the .x() accessor to define the x co-ordinate. View source As with the line generator we can specify the way in which the points are interpolated (.curve()), handle missing data (.defined()) and render to canvas (.context());

Radial area

The radial area generator is similar to the area generator but the points are transformed by angle (working clockwise from 12 o’clock) and radius, rather than x and y:
var radialAreaGenerator=d3.radialArea()
  .angle(function(d){
    returnd.angle;
  })
  .innerRadius(function(d){
    returnd.r0;
  })
  .outerRadius(function(d){
    returnd.r1;
  });

var points=[
  {angle:0,r0:30,r1:80},
  {angle:Math.PI*0.25,r0:30,r1:70},
  {angle:Math.PI*0.5,r0:30,r1:80},
  {angle:Math.PI*0.75,r0:30,r1:70},
  {angle:Math.PI,r0:30,r1:80},
  {angle:Math.PI*1.25,r0:30,r1:70},
  {angle:Math.PI*1.5,r0:30,r1:80},
  {angle:Math.PI*1.75,r0:30,r1:70},
  {angle:Math.PI*2,r0:30,r1:80}
];
View source

Stack generator

The stack generator takes an array of multi-series data and generates an array for each series where each array contains lower and upper values for each data point. The lower and upper values are computed so that each series is stacked on top of the previous series.
var data=[
  {day:'Mon',apricots:120,blueberries:180,cherries:100},
  {day:'Tue',apricots:60,  blueberries:185,cherries:105},
  {day:'Wed',apricots:100,blueberries:215,cherries:110},
  {day:'Thu',apricots:80,  blueberries:230,cherries:105},
  {day:'Fri',apricots:120,blueberries:240,cherries:105}
];

var stack=d3.stack()
  .keys(['apricots','blueberries','cherries']);

var stackedSeries=stack(data);

// stackedSeries = [
//   [ [0, 120],   [0, 60],   [0, 100],    [0, 80],    [0, 120] ],   // Apricots
//   [ [120, 300], [60, 245], [100, 315],  [80, 310],  [120, 360] ], // Blueberries
//   [ [300, 400], [245, 350], [315, 425], [310, 415], [360, 465] ]  // Cherries
// ]
The .keys() configuration function specifies which series are included in the stack generation. The data output by the stack generator can be used however you like, but typically it’ll be used to produce stacked bar charts: View source or when used in conjunction with the area generator, stacked line charts: View source

.order()

The order of the stacked series can be configured using .order():
stack.order(d3.stackOrderInsideOut);
Each series is summed and then sorted according to the chosen order. The possible orders are:
stackOrderNone(Default) Series in same order as specified in .keys()
stackOrderAscendingSmallest series at the bottom
stackOrderDescendingLargest series at the bottom
stackOrderInsideOutLargest series in the middle
stackOrderReverseReverse of stackOrderNone

.offset()

By default the stacked series have a baseline of zero. However we can configure the offset of the stack generator to achieve different effects. For example we can normalise the stacked series so that they fill the same height:
stack.offset(d3.stackOffsetExpand);
View source The available offsets are:
stackOffsetNone(Default) No offset
stackOffsetExpandSum of series is normalised (to a value of 1)
stackOffsetSilhouetteCenter of stacks is at y=0
stackOffsetWiggleWiggle of layers is minimised (typically used for streamgraphs)
Here’s a streamgraph example using stackOffsetWiggle: View source

Arc generator

Arc generators produce path data from angle and radius values. An arc generator is created using:
var arcGenerator=d3.arc();
It can then be passed an object containing startAngle, endAngle, innerRadius and outerRadius properties to produce the path data:
var pathData=arcGenerator({
  startAngle:0,
  endAngle:0.25*Math.PI,
  innerRadius:50,
  outerRadius:100
});

// pathData is "M6.123233995736766e-15,-100A100,100,0,0,1,70.71067811865476,-70.710678
// 11865474L35.35533905932738,-35.35533905932737A50,50,0,0,0,3.061616997868383e-15,-50Z"
(startAngle and endAngle are measured clockwise from the 12 o’clock in radians.) View source

Configuration

We can configure innerRadius, outerRadius, startAngle, endAngle so that we don’t have to pass them in each time:
arcGenerator
  .innerRadius(20)
  .outerRadius(100);

pathData=arcGenerator({
  startAngle:0,
  endAngle:0.25*Math.PI
});

// pathData is "M6.123233995736766e-15,-100A100,100,0,0,1,70.71067811865476,-70.71067811
// 865474L14.142135623730951,-14.14213562373095A20,20,0,0,0,1.2246467991473533e-15,-20Z"
View source We can also configure corner radius (cornerRadius) and the padding between arc segments (padAngle and padRadius):
arcGenerator
  .padAngle(.02)
  .padRadius(100)
  .cornerRadius(4);
View source Arc padding takes two parameters padAngle and padRadius which when multiplied together define the distance between adjacent segments. Thus in the example above, the padding distance is 0.02 * 100 = 2. Note that the padding is calculated to maintain (where possible) parallel segment boundaries.
You might ask why there isn't a single parameter padDistance for defining the padding distance. It's split into two parameters so that the pie generator (see later) doesn't need to concern itself with radius.

Accessor functions

We also define accessor functions for startAngle, endAngle, innerRadius and outerRadius e.g.
arcGenerator
  .startAngle(function(d){
    returnd.startAngleOfMyArc;
  })
  .endAngle(function(d){
    returnd.endAngleOfMyArc;
  });

arcGenerator({
  startAngleOfMyArc:0,
  endAngleOfMyArc:0.25*Math.PI
});
View source

Centroid

It’s sometimes useful to calculate the centroid of an arc, such as when positioning labels, and D3 has a function .centroid() for doing this:
arcGenerator.centroid({
  startAngle:0,
  endAngle:0.25*Math.PI
});
// returns [22.96100594190539, -55.43277195067721]
Here’s an example where .centroid() is used to compute the label positions: View source

Pie generator

The pie generator goes hand in hand with the arc generator. Given an array of data, the pie generator will output an array of objects containing the original data augmented by start and end angles:
var pieGenerator=d3.pie();
var data=[10,40,30,20,60,80];
var arcData=pieGenerator(data);

// arcData is an array of objects: [
//   {
//     data: 10,
//     endAngle: 6.28...,
//     index: 5,
//     padAngle: 0,
//     startAngle: 6.02...,
//     value: 10
//   },
//   ...
// ]
We can then use an arc generator to create the path strings:
var arcGenerator=d3.arc()
  .innerRadius(20)
  .outerRadius(100);

d3.select('g')
  .selectAll('path')
  .data(arcData)
  .enter()
  .append('path')
  .attr('d',arcGenerator);
Notice that the output of pieGenerator contains the properties startAngle and endAngle. These are the same properties required by arcGenerator. View source The pie generator has a number of configuration functions including .padAngle(), .startAngle(), .endAngle() and .sort(). .padAngle() specifies an angular padding (in radians) between neighbouring segments. .startAngle() and .endAngle() configure the start and end angle of the pie chart. This allows, for example, the creation of semi-circular pie charts:
var pieGenerator=d3.pie()
  .startAngle(-0.5*Math.PI)
  .endAngle(0.5*Math.PI);
View source By default the segment start and end angles are specified such that the segments are in descending order. However we can change the sort order using .sort:
var pieGenerator=d3.pie()
  .value(function(d){returnd.quantity;})
  .sort(function(a,b){
    returna.name.localeCompare(b.name);
  });

var fruits=[
  {name:'Apples',quantity:20},
  {name:'Bananas',quantity:40},
  {name:'Cherries',quantity:50},
  {name:'Damsons',quantity:10},
  {name:'Elderberries',quantity:30},
];
View source

Symbols

The symbol generator produces path data for symbols commonly used in data visualisation:
var symbolGenerator=d3.symbol()
  .type(d3.symbolStar)
  .size(80);

var pathData=symbolGenerator();
We can then use pathData to define the d attribute of a path element:
d3.select('path')
  .attr('d',pathData);
Here’s a simple chart using the symbol generator: View source D3 provides a number of symbol types: View source

Layouts

D3 layouts help you create more advanced visualisations such as treemaps: packed circles: and network graphs: In essence a layout is just a JavaScript function that takes your data as input and adds visual variables such as position and size to it. For example the tree layout takes a hierarchical data structure and adds x and y values to each node such that the nodes form a tree-like shape: D3 has a number of hierarchy layouts for dealing with hierarchical (or tree) data as well as a chord layout (for network flows) and a general purpose force layout (physics-based simulation). We’ll cover the force layout in a separate chapter. (It’s also possible to create your own layouts. For example a simple function that adds positional information to an array of data can be considered a layout.)

Hierarchical layouts

D3 has a number of hierarchical layouts to help with visualising hierarchies (or trees) e.g.
{
  "name": "A1",
  "children": [
    {
      "name": "B1",
      "children": [
        {
          "name": "C1",
          "value": 100
        },
        {
          "name": "C2",
          "value": 300
        },
        {
          "name": "C3",
          "value": 200
        }
      ]
    },
    {
      "name": "B2",
      "value": 200
    }
  ]
}
In this section we’ll look at the tree, cluster, treemap, pack and partition layouts. Note that treemap, pack and partition are designed to lay out hierarchies where the nodes have an associated numeric value (e.g. revenue, population etc.). D3 version 4 requires the hierarchical data to be in the form of a d3.hierarchy object which we’ll cover next.

d3.hierarchy

A d3.hierarchy object is a data structure that represents a hierarchy. It has a number of functions defined on it for retrieving things like ancestor, descendant and leaf nodes and for computing the path between nodes. It can be created from a nested JavaScript object such as:
var data={
  "name":"A1",
  "children":[
    {
      "name":"B1",
      "children":[
        {
          "name":"C1",
          "value":100
        },
        {
          "name":"C2",
          "value":300
        },
        {
          "name":"C3",
          "value":200
        }
      ]
    },
    {
      "name":"B2",
      "value":200
    }
  ]
}

var root=d3.hierarchy(data)
Typically you don’t need to operate on the hierarchy object itself but there are some useful functions defined on it such as:
root.descendants();
root.links()
root.descendants() returns a flat array of root’s descendants and root.links() returns a flat array of objects containing all the parent-child links. More examples of hierarchy functions We’ll now look at the tree, cluster, treemap, pack and partition layouts.

tree layout

The tree layout arranges the nodes of a hierarchy in a tree like arrangement. We start by creating the tree layout using:
var treeLayout=d3.tree();
We can configure the tree’s size using .size:
treeLayout.size([400,200]);
We can then call treeLayout, passing in our hierarchy object root:
treeLayout(root);
This’ll write x and y values on each node of root. We can now: . use root.descendants() to get an array of all the nodes . join this array to circles (or any other type of SVG element) . use x and y to position the circles and . use root.links() to get an array of all the links . join the array to line (or path) elements . use x and y of the link’s source and target to position the line (In the case of root.links() each array element is an object containing two properties source and target which represent the link’s source and target nodes.)
// Nodes
d3.select('svg g.nodes')
  .selectAll('circle.node')
  .data(root.descendants())
  .enter()
  .append('circle')
  .classed('node',true)
  .attr('cx',function(d){returnd.x;})
  .attr('cy',function(d){returnd.y;})
  .attr('r',4);

// Links
d3.select('svg g.links')
  .selectAll('line.link')
  .data(root.links())
  .enter()
  .append('line')
  .classed('link',true)
  .attr('x1',function(d){returnd.source.x;})
  .attr('y1',function(d){returnd.source.y;})
  .attr('x2',function(d){returnd.target.x;})
  .attr('y2',function(d){returnd.target.y;});
View source

cluster layout

The cluster layout is very similar to the tree layout the main difference being all leaf nodes are placed at the same depth.
var clusterLayout=d3.cluster()
  .size([400,200])

var root=d3.hierarchy(data)

clusterLayout(root)
View source

treemap layout

Treemaps were invented by Ben Shneiderman to visually represent hierarchies where each item has an associated value. For example, we can think of country population data as a hierarchy where the first level represents the region and the next level represents each country. A treemap will represent each country as a rectangle (sized proportionally to the population) and group each region together: D3’s treemap layout is created using:
var treemapLayout=d3.treemap();
As usual we can configure our layout e.g.
treemapLayout
  .size([400,200])
  .paddingOuter(10);
Before applying this layout to our hierarchy we must run .sum() on the hierarchy. This traverses the tree and sets .value on each node to the sum of its children:
root.sum(function(d){
  returnd.value;
});
Note that we pass an accessor function into .sum() to specify which property to sum. We can now call treemapLayout, passing in our hierarchy object:
treemapLayout(root);
The layout adds 4 properties x0, x1, y0 and y1 to each node which specify the dimensions of each rectangle in the treemap. Now we can join our nodes to rect elements and update the x, y, width and height properties of each rect:
d3.select('svg g')
  .selectAll('rect')
  .data(root.descendants())
  .enter()
  .append('rect')
  .attr('x',function(d){returnd.x0;})
  .attr('y',function(d){returnd.y0;})
  .attr('width',function(d){returnd.x1-d.x0;})
  .attr('height',function(d){returnd.y1-d.y0;})
View source If we’d like labels in each rectangle we could join g elements to the array and add rect and text elements to each g:
var nodes=d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform',function(d){return'translate('+[d.x0,d.y0]+')'})

nodes
  .append('rect')
  .attr('width',function(d){returnd.x1-d.x0;})
  .attr('height',function(d){returnd.y1-d.y0;})

nodes
  .append('text')
  .attr('dx',4)
  .attr('dy',14)
  .text(function(d){
    returnd.data.name;
  })
View source treemap layouts can be configured in a number of ways: . the padding around a node’s children can be set using .paddingOuter . the padding between sibling nodes can be set using .paddingInner . outer and inner padding can be set at the same time using .padding . the outer padding can also be fine tuned using .paddingTop, .paddingBottom, .paddingLeft and .paddingRight. View source In the example above paddingTop is 20 and paddingInner is 2. Treemaps can use different tiling strategies and D3 has several built in (treemapBinary, treemapDice, treemapSlice, treemapSliceDice, treemapSquarify) and the configuration function .tile is used to select one:
treemapLayout.tile(d3.treemapDice)
treemapBinary strives for a balance between horizontal and vertical partitions, treemapDice partitions horizontally, treemapSlice partitions vertically, treemapSliceDice alternates between horizontal and vertical partioning and treemapSquarify allows the aspect ratio of the rectangles to be influenced. The effect of different squarify ratios can be seen here.

pack layout

The pack layout is similar to the tree layout but circles instead of rectangles are used to represent nodes. In the example below each country is represented by a circle (sized according to population) and the countries are grouped by region. D3’s pack layout is created using:
var packLayout=d3.pack();
As usual we can configure its size:
packLayout.size([300,300]);
As with the treemap we must call .sum() on the hierarchy object root before applying the pack layout:
rootNode.sum(function(d){
  returnd.value;
});

packLayout(rootNode);
The pack layout adds x, y and r (for radius) properties to each node. Now we can add circle elements for each descendant of root:
d3.select('svg g')
  .selectAll('circle')
  .data(rootNode.descendants())
  .enter()
  .append('circle')
  .attr('cx',function(d){returnd.x;})
  .attr('cy',function(d){returnd.y;})
  .attr('r',function(d){returnd.r;})
View source Labels can be added by creating g elements for each descendant:
var nodes=d3.select('svg g')
  .selectAll('g')
  .data(rootNode.descendants())
  .enter()
  .append('g')
  .attr('transform',function(d){return'translate('+[d.x,d.y]+')'})

nodes
  .append('circle')
  .attr('r',function(d){returnd.r;})

nodes
  .append('text')
  .attr('dy',4)
  .text(function(d){
    returnd.children===undefined?d.data.name:'';
  })
View source The padding around each circle can be configured using .padding():
packLayout.padding(10)
View source

partition layout

The partition layout subdivides a rectangular space into a layer for each layer of the hierarchy. Each layer is subdivided for each node in the layer: D3’s partition layout is created using:
var partitionLayout=d3.partition();
As usual we can configure its size:
partitionLayout.size([400,200]);
As with the treemap we must call .sum() on the hierarchy object root and before applying the partition layout:
rootNode.sum(function(d){
  returnd.value;
});

partitionLayout(rootNode);
The partition layout adds x0, x1, y0 and y1 properties to each node. We can now add rect elements for each descendant of root:
d3.select('svg g')
  .selectAll('rect')
  .data(rootNode.descendants())
  .enter()
  .append('rect')
  .attr('x',function(d){returnd.x0;})
  .attr('y',function(d){returnd.y0;})
  .attr('width',function(d){returnd.x1-d.x0;})
  .attr('height',function(d){returnd.y1-d.y0;});
View source Padding can be added between nodes using .padding():
partitionLayout.padding(2)
View source If we’d like to change the orientation of the partition layout so that the layers run left to right we can swap x0 with y0 and x1 with y1 when defining the rect elements:
  .attr('x',function(d){returnd.y0;})
  .attr('y',function(d){returnd.x0;})
  .attr('width',function(d){returnd.y1-d.y0;})
  .attr('height',function(d){returnd.x1-d.x0;});
View source We can also map the x dimension into a rotation angle and y into a radius to create a sunburst partition: View source

chord layout

Chord diagrams visualise links (or flows) between a group of nodes, where each flow has a numeric value. For example, they can show migration flows between countries. (Personally I find them difficult to interpret!) The data needs to be in the form of an n x n matrix (where n is the number of items):
var data=[
  [10,20,30],
  [40,60,80],
  [100,200,300]
];
The first row represents flows from the 1st item to the 1st, 2nd and 3rd items etc. We create the layout using:
var chordGenerator=d3.chord();
and we configure it using .padAngle() (to set the angle between adjacent groups in radians), .sortGroups() (to specify the order of the groups), .sortSubgroups() (to sort within each group) and .sortChords() to determine the z order of the chords. We apply the layout using:
var chords=chordGenerator(data);
which returns an array of chords. Each element of the array is an object with source and target properties. Each source and target has startAngle and endAngle properties which will define the shape of each chord. We use the ribbon shape generator which converts the chord properties into path data (see the Shapes chapter for more information on shape generators).
var ribbonGenerator=d3.ribbon().radius(200);

d3.select('g')
  .selectAll('path')
  .data(chords)
  .enter()
  .append('path')
  .attr('d',ribbonGenerator)
View source

Force layout

D3’s force layout uses a physics based simulator for positioning visual elements. Forces can be set up between elements, for example: . all elements repel one another . elements are attracted to center(s) of gravity linked elements (e.g. . friendship) are a fixed distance apart (network visualisation) . elements may not overlap (collision detection) The force layout allows us to position elements in a way that would be difficult to achieve using other means. As an example we have a number of circles (each of which has a category A, B or C) and add the following forces: . all circles attract one another (to clump circles together) . collision detection (to stop circles overlapping) . circles are attracted to one of three centers, depending on their category The force layout requires a larger amount of computation (typically requiring a few seconds of time) than other D3 layouts and and the solution is calculated in a step by step (iterative) manner. Usually the positions of the SVG/HTML elements are updated as the simulation iterates, which is why we see the circles jostling into position.

Setting up a force simulation

Broadly speaking there are 4 steps to setting up a force simulation: . create an array of objects . call forceSimulation, passing in the array of objects add one or more force functions (e.g. . forceManyBody, forceCenter, forceCollide) to the system . set up a callback function to update the element positions after each tick Let’s start with a minimal example:
var width=300,height=300
var nodes=[{},{},{},{},{}]

var simulation=d3.forceSimulation(nodes)
  .force('charge',d3.forceManyBody())
  .force('center',d3.forceCenter(width/2,height/2))
  .on('tick',ticked);
Here we’ve created a simple array of 5 objects and have added two force functions forceManyBody and forceCenter to the system. (The first of these makes the elements repel each other while the second attracts the elements towards a centre point.) Each time the simulation iterates the function ticked will be called. This function joins the nodes array to circle elements and updates their positions:
functionticked(){
  var u=d3.select('svg')
    .selectAll('circle')
    .data(nodes)

  u.enter()
    .append('circle')
    .attr('r',5)
    .merge(u)
    .attr('cx',function(d){
      returnd.x
    })
    .attr('cy',function(d){
      returnd.y
    })

  u.exit().remove()
}
View source The power and flexibility of the force simulation is centred around force functions which adjust the position and velocity of elements to achieve a number of effects such as attraction, repulstion and collision detection. We can define our own force functions but D3 comes with a number of useful ones built in: . forceCenter (for setting the center of gravity of the system) . forceManyBody (for making elements attract or repel one another) . forceCollide (for preventing elements overlapping) . forceX and forceY (for attracting elements to a given point) . forceLink (for creating a fixed distance between connected elements) Force functions are added to the simulation using .force() where the first argument is a user defined id and the second argument the force function:
simulation.force('charge',d3.forceManyBody())
Let’s look at the inbuilt force functions one by one.

forceCenter

forceCenter is useful (if not essential) for centering your elements as a whole about a center point. (Without it elements might disappear off the page.) It can either be initialised with a center position:
d3.forceCenter(100,100)
or using the configuration functions .x() and .y():
d3.forceCenter().x(100).y(100)
We add it to the system using:
simulation.force('center',d3.forceCenter(100,100))
See below for usage examples.

forceManyBody

forceManyBody causes all elements to attract or repel one another. The strength of the attraction or repulsion can be set using .strength() where a positive value will cause elements to attract one another while a negative value causes elements to repel each other. The default value is -30.
simulation.force('charge',d3.forceManyBody().strength(-20))
View source As a rule of thumb, when creating network diagrams we want the elements to repel one another while for visualisations where we’re clumping elements together, attractive forces are necessary.

forceCollide

forceCollide is used to stop elements overlapping and is particularly useful when ‘clumping’ circles together. We must specify the radius of the elements using .radius():
var numNodes=100
var nodes=d3.range(numNodes).map(function(d){
  return{radius:Math.random()*25}
})

var simulation=d3.forceSimulation(nodes)
  .force('charge',d3.forceManyBody().strength(5))
  .force('center',d3.forceCenter(width/2,height/2))
  .force('collision',d3.forceCollide().radius(function(d){
    returnd.radius
  }))
View source

forceX and forceY

forceX and forceY cause elements to be attracted towards specified position(s). We can use a single center for all elements or apply the force on a per-element basis. The strength of attraction can be configured using .strength(). As an example suppose we have a number of elements, each of which has a category 0, 1 or 2. We can add a forceX force function to attract the elements to an x-coordinate 100, 300 or 500 based on the element’s category:
simulation.force('x',d3.forceX().x(function(d){
  returnxCenter[d.category];
}))
View source Note the above example also uses forceCollide. If our data has a numeric dimension we can use forceX or forceY to position elements along an axis:
simulation.force('x',d3.forceX().x(function(d){
  returnxScale(d.value);
}))
.force('y',d3.forceY().y(function(d){
  return0;
}))
View source Do use the above with caution as the x position of the elements is only approximate.

forceLink

forceLink pushes linked elements to be a fixed distance apart. It requires an array of links that specify which elements we want to link together. Each link object specifies a source and target element, where the value is the element’s array index:
var links=[
  {source:0,target:1},
  {source:0,target:2},
  {source:0,target:3},
  {source:1,target:6},
  {source:3,target:4},
  {source:3,target:7},
  {source:4,target:5},
  {source:4,target:7}
]
We can then pass our links array into the forceLink function using .links():
simulation.force('link',d3.forceLink().links(links))
View source The distance and strength of the linked elements can be configured using .distance() (default value is 30) and .strength().

Geographic

This chapter looks at D3’s approach to rendering geographic information. For example: View source D3’s approach differs to so called raster methods such as Leaflet and Google Maps. These pre-render map features as image tiles and these are served up and pieced together in the browser to form a map. Typically D3 requests vector geographic information in the form of GeoJSON and renders this to SVG or Canvas in the browser. Raster maps often look more like traditional print maps where a lot of detail (e.g. place names, roads, rivers etc.) can be shown without an impact on performance. However, dynamic content such as animation and interaction is more easily implemented using a vector approach. (It’s also quite common to combine the two approaches.)

D3 mapping concepts

The 3 concepts that are key to understanding map creation using D3 are: . GeoJSON (a JSON-based format for specifying geographic data) . projections (functions that convert from latitude/longitude co-ordinates to x & y co-ordinates) . geographic path generators (functions that convert GeoJSON shapes into SVG or Canvas paths) Let’s look at these one by one.

GeoJSON

GeoJSON is a standard for representing geographic data using the JSON format and the full specification is at geojson.org. It’s fairly straightforward to get the hang of and the example below shows a typical GeoJSON object:
{
  "type": "FeatureCollection",
  "features": [
    {
      "type": "Feature",
      "properties": {
        "name": "Africa"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[[-6, 36], [33, 30], ... , [-6, 36]]]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "name": "Australia"
      },
      "geometry": {
        "type": "Polygon",
        "coordinates": [[[143, -11], [153, -28], ... , [143, -11]]]
      }
    },
    {
      "type": "Feature",
      "properties": {
        "name": "Timbuktu"
      },
      "geometry": {
        "type": "Point",
        "coordinates": [-3.0026, 16.7666]
      }
    }
  ]
}
In the above example we have a FeatureCollection containing an array of 3 features: . Africa . Australia . the city of Timbuktu Each feature consists of geometry (simple polygons in the case of the countries and a point for Timbuktu) and properties. Properties can contain any information about the feature such as name, id, and other data such as population, GDP etc. As we’ll see later, D3 takes care of most of the detail when rendering GeoJSON so you only need a basic understanding of GeoJSON to get started with D3 mapping.

Projections

A projection function takes a longitude and latitude co-ordinate (in the form of an array [lon, lat]) and transforms it into an x and y co-ordinate:
functionprojection(lonLat){
  var x=...// some formula here to calculate x
  var y=...// some formula here to calculate y
  return[x,y];
}

projection([-3.0026,16.7666])
// returns [474.7594743879618, 220.7367625635119]
Projection mathematics can get quite complex but fortunately D3 provides a large number of projection functions. For example we can create an equi-rectangular projection function using:
var projection=d3.geoEquirectangular();

projection([-3.0026,16.7666])
// returns [474.7594743879618, 220.7367625635119]
We’ll look at projections in much more detail later.

Geographic path generators

A geographic path generator is a function that takes a GeoJSON object and converts it into an SVG path string. (In fact, it’s just another type of shape generator.) We create a generator using the method .geoPath() and configure it with a projection function:
var projection=d3.geoEquirectangular();

var geoGenerator=d3.geoPath()
  .projection(projection);

var geoJson={
  "type":"Feature",
  "properties":{
    "name":"Africa"
  },
  "geometry":{
    "type":"Polygon",
    "coordinates":[[[-6,36],[33,30],...,[-6,36]]]
  }
}

geoGenerator(geoJson);
// returns "M464.0166237760863,154.09974265651798L491.1506253268278,154.8895088551978 ... 
L448.03311471280136,183.1346693994119Z"
As usual with shape generators the generated path string is used to set the d attribute on an SVG path element.

Putting it all together

Once we have some GeoJSON, a projection function and a geographic path generator we can use them together to create a basic map:
var geoJson={
  "type":"FeatureCollection",
  "features":[
    {
      "type":"Feature",
      "properties":{
        "name":"Africa"
      },
      "geometry":{
        "type":"Polygon",
        "coordinates":[[[-6,36],[33,30],...,[-6,36]]]
      }
    },
    ...
  ]
}

var projection=d3.geoEquirectangular();

var geoGenerator=d3.geoPath()
  .projection(projection);

// Join the FeatureCollection's features array to path elements
var u=d3.select('#content g.map')
  .selectAll('path')
  .data(geojson.features);

// Create path elements and update the d attribute using the geo generator
u.enter()
  .append('path')
  .attr('d',geoGenerator);
View source (Note that to keep things simple the GeoJSON in the above example uses just a few co-ordinates to define the country boundaries.) The above example shows the essence of creating maps using D3 and I recommend spending time to understand each concept (GeoJSON, projections and geo generators) and how they fit together. Now that we’ve covered the basics we’ll look at each concept in more detail.

GeoJSON

GeoJSON is a JSON-based structure for specifying geographic data. More often than not it’s converted from shapefile data (a geospatial vector data format widely used in the GIS field) using tools such as mapshaper, ogr2ogr, shp2json or QGIS. A popular source of world map shapefiles is Natural Earth and if starting out I recommend trying out mapshaper for importing shapefiles and exporting as GeoJSON. It can also filter by properties (e.g. if you wanted to filter countries by continent). For a more in depth look at conversion look at Mike Bostock’s Let’s Make a Map tutorial. You can create maps without understanding the GeoJSON specification in minute detail because tools such as mapshaper and D3 do such a good job of abstracting away the detail. However, if you did want to understand GeoJSON in greater depth I recommend checking out the official specification. So far we’ve embedded a GeoJSON object in our example files. In practice the GeoJSON would be in a separate file and loaded using an ajax request. We cover requests in more detail in the requests chapter but for the remainder of this chapter we’ll load a GeoJSON file using:
d3.json('ne_110m_land.json',function(err,json){
  createMap(json);
})
It’s worth mentioning TopoJSON which is another JSON based standard for describing geographic data and tends to result in significantly smaller file sizes. It requires a bit more work to use, and we don’t cover it in this chapter. However for further information check out the documentation.

Projections

There are numerous (if not infinite) ways of converting (or ‘projecting’) a point on a sphere (e.g. the earth) to a point on a flat surface (e.g. a screen) and people have written countless articles (such as this one) on the pros and cons of different projections. In short there is no perfect projection as every projection will distort shape, area, distance and/or direction. Choosing a projection is a case of choosing which property you don’t want to be distorted and accepting that there’ll be distortion in the other properties (or choose a projection that strives for a balanced approach). For example, if it’s important that the size of countries are represented accurately then choose a projection that strives to preserve area (probably to the cost of shape, distance and direction). D3 has a number of core projections that should cover most use cases: . geoAzimuthalEqualArea . geoAzimuthalEquidistant . geoGnomonic . geoOrthographic . geoStereographic . geoAlbers . geoConicConformal . geoConicEqualArea . geoConicEquidistant . geoEquirectangular . geoMercator . geoTransverseMercator Some projections preserve area (e.g. geoAzimuthalEqualArea & geoConicEqualArea), others distance (e.g. geoAzimuthalEquidistant & geoConicEquidistant) and others relative angles (e.g. geoEquirectangular & geoMercator). For a more in depth discussion of the pros and cons of each projection try resources such as Carlos A. Furuti’s Map Projection Pages. The grid below shows each core projection on a world map together with a longitude/latitude grid and equal radius circles. View source

Projection functions

A projection function takes input [longitude, latitude] and outputs a pixel co-ordinate [x, y]. You’re free to write your own projection functions but much easier is to ask D3 to make one for you. To do this choose a projection method (e.g. d3.geoAzimuthalEqualArea), call it and it’ll return a projection function:
var projection=d3.geoAzimuthalEqualArea();

projection([-3.0026,16.7666]);
// returns [473.67353385539417, 213.6120079887163]
The core projections have configuration functions for setting the following parameters:
scaleScale factor of the projection
centerProjection center [longitude, latitude]
translatePixel [x,y] location of the projection center
rotateRotation of the projection [lambda, phi, gamma] (or [yaw, pitch, roll])
The precise meaning of each parameter is dependent on the mathematics behind each projection but broadly speaking: scale specifies the scale factor of the projection. . The higher the number the larger the map. . center specifies the center of projection (with a [lon, lat] array) . translate specifies where the center of projection is located on the screen (with a [x, y] array) . rotate specifies the rotation of the projection (with a [λ, φ, γ] array) where the parameters correspond to yaw, pitch and roll, respectively: For example we might create and configure a projection function such that Timbuktu is centred in a 960x500 map using:
var projection=d3.geoAzimuthalEqualArea()
  .scale(300)
  .center([-3.0026,16.7666])
  .translate([480,250]);
To get a feel for how each parameter behaves use the projection explorer below. The (equal radius) circles and grid allow you to assess the projection’s distortion of area and angle. View source

.invert()

We can convert from a pixel co-ordinate [x, y] back to [longitude, latitude] using the projection’s .invert() method:
var projection=d3.geoAzimuthalEqualArea();

projection([-3.0026,16.7666])
// returns [473.67353385539417, 213.6120079887163]

projection.invert([473.67353385539417,213.6120079887163])
// returns [-3.0026, 16.766]

Fitting

Given a GeoJSON object, a projection’s .fitExtent() method sets the projection’s scale and translate such that the geometry fits within a given bounding box:
projection.fitExtent([[0,0],[900,500]],geojson);
In the example below the canvas element has a light grey background and the bounding box into which we’re fitting the geoJSON is shown as a dotted outline: View source If our bounding box’s top left corner is at [0, 0] we can use the shorthand:
projection.fitSize([900,500],geojson);

Geographic path generators

A geographic path generator is a function that transforms GeoJSON into an SVG path string (or into canvas element calls):
geoGenerator(geoJson);
// e.g. 
returns a SVG path string "M464.01,154.09L491.15,154.88 ... 
L448.03,183.13Z"
We create the generator using d3.geoPath() and usually configure it’s projection type:
var projection=d3.geoEquirectangular();

var geoGenerator=d3.geoPath()
  .projection(projection);
We can now use the generator to help us create an SVG or canvas map. The SVG option is a bit easier to implement, especially when it comes to user interaction as event handlers and hover states can be added. The canvas approach requires a bit more work, but is typically quicker (and more memory efficient) to render.

Rendering SVG

To render SVG we typically join a GeoJSON features array to SVG path elements and update the d attribute using the geographic path generator:
var geoJson={
  "type":"FeatureCollection",
  "features":[
    {
      "type":"Feature",
      "properties":{
        "name":"Africa"
      },
      "geometry":{
        "type":"Polygon",
        "coordinates":[[[-6,36],[33,30],...,[-6,36]]]
      }
    },
    ...
  ]
}

var projection=d3.geoEquirectangular();

var geoGenerator=d3.geoPath()
  .projection(projection);

// Join the FeatureCollection's features array to path elements
var u=d3.select('#content g.map')
  .selectAll('path')
  .data(geojson.features);

// Create path elements and update the d attribute using the geo generator
u.enter()
  .append('path')
  .attr('d',geoGenerator);
View source

Rendering to canvas

To render to a canvas element we need to configure the context of the geo generator with the canvas’s element:
var context=d3.select('#content canvas')
  .node()
  .getContext('2d');

var geoGenerator=d3.geoPath()
  .projection(projection)
  .context(context);
We can then begin a canvas path and call geoGenerator which will produce the necessary canvas calls for us:
context.beginPath();
geoGenerator({type:'FeatureCollection',features:geojson.features})
context.stroke();
View source

Lines and arcs

The geographic path generator is clever enough to distinguish between polygonal (typically for geographic areas) and point (typically for lon/lat locations) features. As can be seen in the above examples it renders polygons as line segments and points as arcs. We can set the radius of the circles using .pointRadius():
var geoGenerator=d3.geoPath()
  .pointRadius(5)
  .projection(projection);

Path geometry

The geographic path generator can also be used to compute the area (in pixels), centroid, bounding box and path length (in pixels) of a projected GeoJSON feature:
var feature=geojson.features[0];

// Compute the feature's area (in pixels)
geoGenerator.area(feature);
// returns 30324.86518469876

// Compute the feature's centroid (in pixel co-ordinates)
geoGenerator.centroid(feature);
// returns [266.9510120424504, 127.35819206325564]

// Compute the feature's centroid (in pixel co-ordinates)
geoGenerator.bounds(feature);
// returns [[140.6588054321928, 24.336293856408275], [378.02358370342165, 272.17304763960306]]

// Compute the path length (in pixels)
geoGenerator.measure(feature);
// returns 775.7895349902461
View source

Shapes

If we need to add lines and/or circles to a map we can achieve it by adding features to our GeoJSON. Lines can be added as a LineString feature and will be projected into great-arcs (i.e. the shortest distance across the surface of the globe). Here’s an example where we add a line between London and New York:
geoGenerator({
  type:'Feature',
  geometry:{
    type:'LineString',
    coordinates:[[0.1278,51.5074],[-74.0059,40.7128]]
  }
});
Circle features can be generated using d3.geoCircle(). Typically the center ([lon, lat]) and the angle (degrees) between the points are set:
var circle=d3.geoCircle()
  .center([0.1278,51.5074])
  .radius(5);

circle();
// returns a GeoJSON object representing a circle

geoGenerator(circle());
// returns a path string representing the projected circle
A GeoJSON grid of longitude and latitude lines (known as a graticule) can be generated using d3.graticule():
var graticule=d3.geoGraticule();

graticule();
// returns a GeoJSON object representing the graticule

geoGenerator(graticule());
// returns a path string representing the projected graticule
(See the official documentation for detailed information on graticule configuration.) Here’s an example using all three shapes: View source

Spherical geometry

There’s a handful of D3 methods that may come in useful from time to time. The first of these .geoArea(), .geoBounds(), .geoCentroid(), .geoDistance() and geoLength() are similar to the path geometry methods described above but operate in spherical space.

Interpolation

The d3.geoInterpolate() method creates a function that accepts input between 0 and 1 and interpolates between two [lon, lat] locations:
var londonLonLat=[0.1278,51.5074];
var newYorkLonLat=[-74.0059,40.7128];
var geoInterpolator=d3.geoInterpolate(londonLonLat,newYorkLonLat);

geoInterpolator(0);
// returns [0.1278, 51.5074]

geoInterpolator(0.5);
// returns [-41.182023242967695, 52.41428456719971] (halfway between the two locations)
View source

geoContains

If we’re using the canvas element to render our geometry we don’t have the luxury of being able to add event handlers onto path objects. Instead we need to check whether mouse or touch events occur inside the boundary of a feature. We can do this using d3.geoContains which accepts a GeoJSON feature and a [lon, lat] array and returns a boolean:
d3.geoContains(ukFeature,[0.1278,51.5074]);
// returns true
View source