(This chart is a part of the d3-charts
collection available here.)
This chart lets you plot countries on a world map, either with a single colour or with a colour gradient mapping to corresponding values. You can see an example of the multicolour version here.
The chart displays the countries who were represented at the Paris rally January 11th 2015.
However, the chart returns an error for the following countries at present:
Here is an example of the settings for a chart with one colour:
var dataset = "data.csv",
countryValue = "Country",
colors = ["#d7191c", "#2b83ba"],
// Config only for multicolour chart
multiColor = false,
dataValue = "",
min = "",
max = "",
useLogScale = false,
showLegend = false;
Here is an example of the settings for a chart with multiple colours:
var dataset = "data.csv",
countryValue = "Country Name",
colors = ["#d7191c", "#2b83ba"],
// Config only for multicolour chart
multiColor = true,
dataValue = "2013", // ""
min = 0, // ""
max = 14877, // ""
useLogScale = true,
showLegend = false;
countryValue
refers to the header for the column with country names.The following config variables are only for the multicolour chart only:
dataValue
is for the column with the country values.min
is the lowest (data) value.max
is the highest (data) value.min
is the lowest (data) value.useLogScale
represents the values logarithmicallyshowLegend
shows a legend explaining the values. (Work in progress.)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>