A horrible introduction to using modern JavaScript tooling with D3
Chapter 15
When we’re working on D3, we very quickly get to where we’ll be using external files for our data. For example, let’s start with a new index.html
and graph.js
.
index.html
<!doctype html>
<html>
<head>
</head>
<body>
<div id="chart"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<script src="graph.js"></script>
</body>
</html>
graph.js
/* global d3 */
if (typeof module !== 'undefined' && module.hot) {
module.hot.accept(() => {
window.location.reload()
})
}
var margin = { top: 20, right: 50, bottom: 50, left: 50 }
var width = 800 - margin.left - margin.right
var height = 500 - margin.top - margin.bottom
var svg = d3
.select('#chart')
.append('svg')
.attr('width', width + margin.left + margin.right)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')')
var colorScale = d3
.scaleOrdinal()
.range(['red', 'orange', 'blue', 'green', 'purple', 'yellow'])
var xPositionScale = d3
.scaleLinear()
.domain([0, 80000])
.range([0, width])
var yPositionScale = d3
.scaleLinear()
.domain([0, 90])
.range([height, 0])
d3.csv('countries.csv')
.then(ready)
.catch(function(err) {
console.log('Failed on', err)
})
function ready(datapoints) {
console.log('Data is', datapoints)
svg
.selectAll('circle')
.data(datapoints)
.enter()
.append('circle')
.attr('r', 5)
.attr('cx', function(d) {
return xPositionScale(d.gdp_per_capita)
})
.attr('cy', function(d) {
return yPositionScale(d.life_expectancy)
})
.attr('fill', function(d) {
return colorScale(d.continent)
})
.attr('opacity', 0.5)
var yAxis = d3.axisLeft(yPositionScale)
svg
.append('g')
.attr('class', 'axis y-axis')
.call(yAxis)
var xAxis = d3.axisBottom(xPositionScale)
svg
.append('g')
.attr('class', 'axis x-axis')
.attr('transform', 'translate(0,' + height + ')')
.call(xAxis)
}
countries.csv
country,continent,gdp_per_capita,life_expectancy,population
Afghanistan,Asia,663,54.863,22856302
Albania,Europe,4195,74.2,3071856
Algeria,Africa,5098,68.96300000000001,30533827
Angola,Africa,2446,45.233999999999995,13926373
Antigua and Barbuda,N. America,12738,73.544,77656
Argentina,S. America,10571,73.822,36930709
Armenia,Europe,2114,71.494,3076098
Australia,Oceania,29241,79.93,19164351
Austria,Europe,32008,78.33,8004712
Azerbaijan,Europe,2533,66.851,8110723
Bahamas,N. America,22728,72.37,297651
Bahrain,Asia,22015,74.497,638193
Try running python -m http.server
and visit localhost:8000
- everything will work perfect!
Now try running parcel index.html
and visit localhost:1234
- everything will be broken!
If we open up the console, our error seems to be…
GET http://localhost:1234/countries.csv 404 (Not Found)
…which is weird, because we definitely saved countries.csv
. Go look! It’s there!
Big secret: Parcel is serving our content out of another secret folder. After it processes our index.html
and graph.js
, it sends them over to dist/
, where it is running the actual server. If you open up the folder, you’ll see: no countries.csv
!
We have two solutions:
countries.csv
into distThe very bad solution seems good because it keep our code as normal JavaScript, and allows our code to keep working with python -m http.server
. But… I’m sorry, we’re in too deep, we have to stop writing “real” JavaScript and start writing what Parcel wants.
Instead of writing
We need to use require
to tell Parcel that countries.csv
is an external, required file, and it needs to include it in dist/
. The change looks like this:
The ./
part means “inside of this same directory.” Give it a save, the page will auto-refresh, and voilà! It works.
You’re going to forget this all the time. And even when you remember the
require
part, you’re going to forget the./
part. I am so sorry, but it’s just how it has to be.