WORK IN PROGRESS
Attempt for #d3makeover, presenting data on how many laptops from the One Laptop Per Child (OLPC) project are present in each country.
Code based on djjupa's block: World Map of trust, which I updated to D4 v 4, and includes d3.zoom, with zoom.translateExtent. Also includes d3-tip, based on Micah Stubbs' example.
xxxxxxxxxx
<meta charset="utf-8">
<style>
body {
font-family: sans-serif;
}
h1, a {
color: darkturquoise;
}
.legend {
font-size: 12px;
}
.sea {
fill: black;
}
.land {
fill: #999999;
opacity: .5;
}
.boundary {
fill: none;
stroke: lightgrey;
stroke-linejoin: round;
stroke-linecap: round;
}
circle {
opacity: 0.5;
fill: turquoise;
stroke: darkturquoise;
cursor: pointer;
}
circle:hover {
opacity: 0.8;
}
/* Tooltip CSS */
.d3-tip {
line-height: 1.5;
font-weight: 400;
font-family: sans-serif;
padding: 6px;
background: rgba(0, 0, 0, 0.9);
color: #FFA500;
border-color: turquoise;
border-width: turquoise;
border-radius: 1px;
pointer-events: none;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 8px;
width: 100%;
line-height: 1.5;
color: rgba(0, 0, 0, 0.6);
position: absolute;
pointer-events: none;
}
/* Northward tooltips */
.d3-tip.n:after {
content: "\25BC";
margin: -1px 0 0 0;
top: 100%;
left: 0;
text-align: center;
}
/* Eastward tooltips */
.d3-tip.e:after {
content: "\25C0";
margin: -4px 0 0 0;
top: 50%;
left: -8px;
}
/* Southward tooltips */
.d3-tip.s:after {
content: "\25B2";
margin: 0 0 1px 0;
top: -8px;
left: 0;
text-align: center;
}
/* Westward tooltips */
.d3-tip.w:after {
content: "\25B6";
margin: -4px 0 0 -1px;
top: 50%;
left: 100%;
}
</style>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<script src="d3-tip.js"></script>
<body>
<h1>One Laptop Per Child (#D3makeover)</h1>
<p>The circles represent the number of laptops from the One Laptop Per Child (OLPC) project are present in each country, per capita.</p>
<div id="map"></div>
<p>Legend (laptops per capita):</p>
<div id="legend"></div>
<p>Source: <a href="https://one.laptop.org/map">One Laptop Per Child</a></p>
<script>
const format = d3.format(',');
const tip = d3.tip()
.attr('class', 'd3-tip')
.offset([0, -10])
.html(function(d){
return "<strong>Country: </strong><span class='details'>" + d.country
+ "<br></span><strong>Laptop per capita: </strong><span class='details'>" + roundNumber(d.percapita) + "</span>"
+ "<br></span><strong>Total laptops: </strong><span class='details'>" + format(d.count) + "</span>";
});
var width = 900,
height = 500;
var projection = d3.geoEquirectangular()
.translate([width / 2, height / 2])
.scale(width / 2 / Math.PI);
var zoom = d3.zoom()
.scaleExtent([1, 8])
.translateExtent([[0,0],[width,height]])
.on("zoom", zoomed);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("#map").append("svg")
.attr("width", width)
.attr("height", height);
svg.call(tip);
var g = svg.append("g").attr("class","map").call(zoom);
g.append("rect")
.attr("class", "sea")
.attr("x",0)
.attr("y",0)
.attr("width",width)
.attr("height",height);
d3.json("world-50m.json", function(error, world) {
g.append("path")
.datum(topojson.feature(world, world.objects.countries))
.attr("class", "land")
.attr("d", path);
g.append("path")
.datum(topojson.mesh(world, world.objects.countries, function(a, b) { return a !== b; }))
.attr("class", "boundary")
.attr("d", path);
d3.csv("olpc.csv", convertTextToNumbers, function(error, data) {
data.sort(function (a,b) {return d3.descending(a.percapita, b.percapita); });
var radius = d3.scaleSqrt()
.domain([0,d3.max(data, function(d){ return d.percapita; })])
.range([5,30]);
var circles = g.append("g").attr("class","circles");
circles.selectAll("circle")
.data(data)
.enter()
.append("circle")
.attr("cx", function(d){
return projection([d.longitude, d.latitude])[0];
})
.attr("cy", function(d){
return projection([d.longitude, d.latitude])[1];
})
.attr("r", function(d){ return radius(d.percapita); })
.on('mouseover', function(d){
tip.direction('w').show(d);
d3.select(this).style('stroke-width', 3);
})
.on('mouseout', function(d){
tip.hide(d);
d3.select(this).style('stroke-width',0.3);
});
var legendWidth = 400;
var legendHeight = 75;
var legendData = [1,10,50,100,200];
var legendDataLength = legendData.length;
var legendOffset = 10;
var legend = d3.select("#legend").append("svg")
.attr("width", legendWidth)
.attr("height", legendHeight);
var legendCircles = legend.selectAll("circle")
.data(legendData)
.enter()
.append("g")
.attr("transform", function(d,i) {
let j = 0;
var x = (legendOffset * (1 + i));
for (j; j < i; j++ ){
x = x + (2 * radius(legendData[j]));
};
return "translate(" + x + "," + (legendHeight/2) + ")";
});
legendCircles.append("circle")
.attr("cx", 0)
.attr("cy", 0)
.attr("r", function(d){ return radius(d); });
legendCircles.append("text")
.text(function(d){return d;})
.attr("class", "legend")
.attr("x", 0)
.attr("y", "0.35em")
.style("text-anchor", "middle")
.style("fill", "#000")
});
});
function zoomed() {
var transform = d3.zoomTransform(this);
g.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");
};
function roundNumber(n){
const precision = 1000;
var roundedNumber = Math.round(n*precision);
return roundedNumber/precision;
};
function convertTextToNumbers(d) {
d.count = +d.count;
d.pop = +d.pop;
d.percapita = +d.percapita;
return d;
};
</script>
Modified http://d3js.org/topojson.v1.min.js to a secure url
https://d3js.org/d3.v4.min.js
https://d3js.org/topojson.v1.min.js