Block-a-Day #4. French departments (an administrative division that contains multiple districts) are positioned on a triangular plot according to the percentage of their labor force in three categories: I. agriculture, II. industry, and III. commerce, transportation, and services. From an idea by Jacques Bertin.
Click on a category or arrow to reorient the chart.
Data Sources: Jacques Bertin, Semiology of Graphics, p. 100
What I Learned: Rotation transforms never do what you expect on the first try. Actually, I knew that one already.
What I’d Do With More Time: I’m sure there’s a way to get the triangle to rotate smoothly around its center, but I just ran out of time to figure it out. If you know, ping me @cmgiven.
Just what it sounds like. For fifteen days, I will make a D3.js v4 block every single day. Rules:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.axis .domain { display: none; }
line.grid {
stroke: #999;
}
line.dash {
stroke: #333;
stroke-width: .5;
}
text.label {
text-transform: uppercase;
font-family: monospace;
font-weight: 700;
font-size: 24px;
letter-spacing: 0.1;
}
path.arrow, text.label {
fill: #333;
cursor: pointer;
}
circle {
fill: rgba(100,75,170,.8);
stroke: rgb(100,75,170);
stroke-width: 1;
}
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = { top: 50, bottom: 200 }
var width = 960
var height = 800 - margin.top - margin.bottom
var side = height * 2 / Math.sqrt(3)
var svg = d3.select('body')
.append('svg')
.attr('width', width)
.attr('height', height + margin.top + margin.bottom)
.append('g')
.attr('transform', 'translate(' + ((width - side) / 2) + ',' + (margin.top + 0.5) + ')')
.append('g')
var sideScale = d3.scaleLinear()
.domain([0, 1])
.range([0, side])
var perpScale = d3.scaleLinear()
.domain([0, 1])
.range([height, 0])
var r = d3.scaleSqrt().range([0, 10])
var axis = d3.axisLeft()
.scale(perpScale)
.tickFormat(function (n) { return (n * 100).toFixed(0) })
.tickSize(side * -0.3)
.tickPadding(5)
var axes = svg.selectAll('.axis')
.data(['i', 'ii', 'iii'])
.enter().append('g')
.attr('class', function (d) { return 'axis ' + d })
.attr('transform', function (d) {
return d === 'iii' ? ''
: 'rotate(' + (d === 'i' ? 240 : 120) + ',' + (side * 0.5) + ',' + (height / 3 * 2) + ')'
})
.call(axis)
axes.selectAll('line')
.attr('class', 'dash')
.attr('transform', 'translate(' + (side * 0.2) + ',0)')
.attr('stroke-dasharray', '9,7')
.attr('y1', 0)
.attr('y2', 0)
axes.selectAll('text')
.attr('transform', 'translate(' + (side * 0.2) + ',-5)')
axes.selectAll('.tick')
.append('line')
.attr('class', 'grid')
.attr('x1', function (d) { return side * (d * 0.5) })
.attr('x2', function (d) { return side * (-d * 0.5 + 1) })
.attr('y1', 0)
.attr('y2', 0)
axes.append('path')
.attr('class', 'arrow')
.attr('d', 'M0 0 L5 9 L2 9 L2 15 L-2 15 L-2 9 L-5 9 Z')
.attr('transform', 'translate(' + (side * 0.5) + ',10)')
.on('click', rotate)
axes.append('text')
.attr('class', 'label')
.attr('x', side * 0.5)
.attr('y', -6)
.attr('text-anchor', 'middle')
.attr('letter-spacing', '-8px')
.text(function (d) { return d })
.on('click', rotate)
function rotate(d) {
var angle = d === 'i' ? 120 : d === 'ii' ? 240 : 0
svg.transition().duration(600)
.attr('transform', 'rotate(' + angle + ',' + (side / 2) + ',' + (height / 3 * 2) + ')')
}
d3.csv('data.csv', function (d) {
var i = +d.i
var ii = +d.ii
var iii = +d.iii
var total = i + ii + iii
var iShare = i / total
var iiShare = ii / total
var iiiShare = iii / total
return {
department: d.department,
total: total,
i: i,
ii: ii,
iii: iii,
iShare: iShare,
iiShare: iiShare,
iiiShare: iiiShare,
x: iiShare + (iiiShare * 0.5)
}
}, function (error, data) {
if (error) { throw error }
r.domain([0, d3.max(data, function (d) { return d.total })])
svg.selectAll('.point')
.data(data)
.enter().append('circle')
.attr('class', 'point')
.attr('r', function (d) { return r(d.total) })
.attr('cx', function (d) { return sideScale(d.x) })
.attr('cy', function (d) { return perpScale(d.iiiShare) })
.append('title')
.text(function (d) { return d.department })
})
</script>
</body>
department,i,ii,iii
AIN,67,43,40
AISNE,56,71,66
ALLIER,65,45,57
Bses ALPES,15,8,12
Htes ALPES,16,8,13
ALPES Mmes,31,61,122
ARDECHE,48,32,25
ARDENNES,25,53,35
ARIEGE,33,17,14
AUBE,28,48,36
AUDE,50,20,32
AVEYRON,70,32,29
BOUCHES DU RH.,42,143,226
CALVADOS,70,55,69
CANTAL,45,13,20
CHARENTE,65,36,38
CHARENTE Mme,79,39,65
CHER,43,41,36
CORREZE,64,23,29
COTE D'OR,43,41,59
COTES DU NORD,131,35,62
CREUSE,58,13,17
DORDOGNE,104,34,41
DOUBS,35,67,39
DROME,46,38,35
EURE,48,52,45
EURE & LOIR,44,27,38
FINISTERE,164,76,89
GARD,40,51,52
HAUTE GARONNE,64,67,84
GERS,63,10,16
GIRONDE,115,107,170
HERAULT,62,40,71
ILLE & V.,137,60,82
INDRE,54,30,32
INDRE & L.,61,41,55
ISERE,68,136,78
JURA,39,34,27
LANDES,70,25,28
LOIR & CHER,51,27,30
LOIRE,56,160,82
Hte LOIRE,52,23,22
LOIRE INF.,101,108,105
LOIRET,51,51,54
LOT,41,10,16
LOT & GAR.,70,24,30
LOZERE,22,5,7
MAINE & L.,104,65,65
MANCHE,116,42,56
MARNE,44,57,67
Hte MARNE,25,28,28
MAYENNE,74,23,28
MEURTHE & M.,23,127,91
MEUSE,24,31,27
MORBIHAN,132,47,59
MOSELLE,36,173,94
NIEVRE,34,27,33
NORD,81,483,296
OISE,40,69,55
ORNE,65,30,35
P.D.C.,94,242,137
PUY DE DOME,80,79,63
Bses PYRENEES,80,49,62
Htes PYRENEES,37,27,28
PYRENEES ORIENT.,35,20,33
BAS-RHIN,76,122,114
Ht-RHIN,40,121,74
RHONE,44,215,194
Hte SAONE,34,32,23
SAONE & L.,94,77,62
SARTHE,87,45,58
SAVOIE,44,38,35
Hte SAVOIE,52,42,45
PARIS,2,575,940
SEINE,8,574,550
SEINE INF.,75,152,174
SEINE & M.,37,72,76
SEINE & O.,46,328,356
DEUX-SEVRES,71,29,33
SOMME,57,68,61
TARN,55,47,33
TARN & G.,44,13,18
VAR,33,50,81
VAUCLUSE,40,30,41
VENDEE,110,38,40
VIENNE,60,29,39
Hte VIENNE,64,47,45
VOSGES,36,95,43
YONNE,41,28,37
BELFORT,3,25,13