|
<!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> |