(This chart based on a template from the d3-charts
collection available [here][collection].)
A map of the countries who’ve purchased malware from the very-hacked Hacking Team based on the list from:
Not depicted due to shapefile/software niggles:
Tweet: https://twitter.com/pessimism/status/618037487403954176.
xxxxxxxxxx
<!--
- Scale SVG dynamically/responsively
- d3.tip
ISSUE: With D3 book's shape file, Alabama disappears
ISSUE: With official shape file, Minnesota disappears
-->
<html lang="en">
<head>
<meta charset="utf-8">
<style>
svg {
display: block;
}
.states {
fill: none; /** Hides TopoJSON artifacts */
stroke: #000;
}
.counties {}
path.country,
path.state {
stroke-width: .5;
stroke: #FFF;
}
path.country:hover,
path.state:hover {
fill: pink !important;
}
/** State chart threshold BEGIN */
text {
font: 10px sans-serif;
}
.caption {
font-weight: bold;
}
.key path {
display: none;
}
.key line {
stroke: #000;
}
/** State chart threshold END */
/** Bar chart BEGIN */
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
}
.x.axis path {
display: none;
}
rect.bar:hover {
fill: orange !important;
}
/** Bar chart END */
.divider {
stroke: #FFF;
opacity: 0.7;
}
/** Grid lines BEGIN */
/** https://www.d3noob.org/2013/01/adding-grid-lines-to-d3js-graph.html */
.grid .tick {
stroke: lightgrey;
opacity: 0.7;
}
.grid path {
stroke-width: 0;
}
/** Grid lines END */
#graph {
/** Layout breaks when minima are removed */
min-width: 400px;
min-height: 260px;
width: 100%;
height: 100%;
max-width: 800px;
max-height: 520px;
margin: 0 auto;
}
</style>
<script src="d3.min.js?v=3.5.5"
type="text/javascript" charset="utf-8"></script>
<script src="queue.min.js"
type="text/javascript" charset="utf-8"></script>
<script src="d3.geo.projection.v0.min.js"
type="text/javascript" charset="utf-8"></script>
<script src="topojson.v1.min.js"
type="text/javascript" charset="utf-8"></script>
</head>
<body>
<div id="graph"></div>
<script type="text/javascript" charset="utf-8">
var defaultWidth = 800
defaultHeight = 520,
aspectRatio = defaultWidth/defaultHeight;
var wrapperWidth = parseInt(d3.select("#graph").style("width"), 10),
wrapperHeight = parseInt(d3.select("#graph").style("height"), 10);
// If landscape mode or square
if (wrapperWidth >= wrapperHeight) {
var height = wrapperHeight,
width = Math.round(height * aspectRatio);
// If protrait mode
} else {
var width = wrapperWidth,
height = Math.round(width / aspectRatio);
}
// Local settings
// ==============
var dataset = "data.csv",
geoDataset = "ne_110m_admin_0_countries_lakes.topojson",
countryValue = "Country",
colors = ["#d7191c", "#2b83ba"];
// Display settings - for multicolour chart
// ================
var multiColor = false,
dataValue = "",
domain = [0, 14877],
useLogScale = true;
// Threshold settings - also for multicolour chart
// ==================
if (multiColor === true) {
var thresholdTitle = "", // No. of patent filings
thresholdValuePrefix = "", // "$", "£"
thresholdValueSuffix = "", // "%", "Patents"
thresholdProportional = true,
formatWithZeroes = d3.format(","),
trimZeroes = true,
formatValue = function(d) {
var index = trimZeroes === true ? d3.formatPrefix(d3.max(domain)) : formatWithZeroes;
return trimZeroes === true ? index.scale(d) + index.symbol : index(d);
};
}
var showLegend = multiColor,
spacing = showLegend === true ? 30 : 0,
padding = 30,
margin = {
"top" : spacing,
"bottom" : 0,
"left" : spacing,
"right" : spacing
};
margin.hor = margin.left + margin.right;
margin.ver = margin.top + margin.bottom;
var color = d3.scale.linear()
.range(colors)
.clamp(useLogScale)
.interpolate(d3.interpolateHcl);
var projection = d3.geo.kavrayskiy7()
.translate([width / 2, height / 2])
.scale(85); // Revise so it isn't a magic number
var path = d3.geo.path()
.projection(projection);
var svg = d3.select("#graph")
.append("svg")
.attr({
"viewBox": "0 0 " + width + " " + height,
"preserveAspectRatio": "xMinYMid"
});
queue()
.defer(d3.csv, dataset)
.defer(d3.json, geoDataset)
.await(render);
// Load the data values
// ====================
function render(error, data, json) {
var errorList = {};
data.map(function(d) { errorList[d[countryValue]] = d[dataValue] ? d[dataValue] : ""; });
if (multiColor === true && useLogScale === true) {
newDomain = domain.map(function(el) { return el === 0 ? 0 : Math.log(el); });
color.domain(newDomain); // Redundant to use newDomain?
} else {
color.domain(domain);
}
// Geodata loaded into the csv scope
// =================================
// Return list of country objects from JSON using topojson function
var countries = topojson.feature(json, json.objects.collection).features;
var dataLength = data.length,
jsonLength = countries.length;
// Data (values) forloop
for (var i = 0; i < dataLength; i++) {
var countryName = data[i][countryValue],
countryData = useLogScale !== true ? data[i][dataValue] : Math.log(data[i][dataValue]);
// JSON (geodata) forloop
for (var j = 0; j < jsonLength; j++) {
// If country exists in dataset
if (countryName === countries[j].properties.name_long) {
// Give it a (multi)colour or `true` to confirm existence in dataset
countries[j].properties.value = multiColor === true ? countryData : true;
// Delete detected country from separate country list.
// If undetected, the country remains on error list.
delete errorList[countryName];
break;
}
}
}
svg.selectAll("path")
.data(countries)
.enter()
.append("path")
.attr({
"d" : path,
"class" : "country",
"transform" : "translate(" + 0 + "," + margin.top + ")"
})
.style("fill", function(d) {
if (multiColor === true) {
return d.properties.value ? color(d.properties.value) : "#DDD";
} else {
return d.properties.value ? color.range()[1] : "#DDD";
}
});
// Error log
// =========
errorList = Object.keys(errorList);
if (errorList.length !== 0) {
console.log("An error occured trying to plot the following countries:");
console.log(errorList);
}
// Threshold
// =========
if (showLegend === true) {
var threshold = d3.scale.threshold()
.domain(color.domain())
.range(color.range());
var keyX = d3.scale.linear()
.domain(d3.extent(threshold.domain()))
.range([0, (width - margin.hor) / 2]); // Total key width
var keyXAxis = d3.svg.axis()
.scale(keyX)
.orient("bottom")
.tickSize(13)
.tickValues(function() {
return thresholdProportional === true ? threshold.domain() : "TODO";
})
.tickFormat(function(d, i) {
return domain[i] ? formatValue(domain[i]) : thresholdValuePrefix + domain[i] + thresholdValueSuffix;
});
var g = svg.append("g")
.attr({
"class" : "key",
"transform" : "translate(" + margin.left + "," + margin.top + ")"
});
// Threshold bars
// ==============
g.selectAll("rect")
.data(threshold.range().map(function(d, i) {
if (thresholdProportional === true) {
startingPoint = i ? keyX(threshold.domain()[i-1]) : keyX.range()[0];
} else {
startingPoint = i ? keyX.range()[1] / domain.length * i : keyX.range()[0];
}
return {
"x0" : startingPoint,
// Unused if thresholdProportional === true
"x1" : i < threshold.domain().length ? keyX(threshold.domain()[i]) : keyX.range()[1],
"z" : d
};
}))
.enter()
.append("rect")
.attr({
"height" : 8,
"x" : function(d) { return d.x0; },
"width" : thresholdProportional === true ? function(d) { return d.x1 - d.x0; } : keyX.range()[1] / domain.length
})
.style("fill", function(d) { return d.z; });
// Threshold title
// ===============
if (thresholdProportional === true) {
g.call(keyXAxis)
.append("text")
.attr({
"class" : "caption",
"y" : -6
})
.text(thresholdTitle);
}
}
}
</script>
</body>
</html>