These county, state and country boundaries are extracted from a single TopoJSON file from us-atlas. Counties are stroked in thin gray, states in thin black, and the country in thick black. See also the fancy drop shadow variant.
forked from mbostock's block: U.S. TopoJSON
xxxxxxxxxx
<meta charset="utf-8">
<style>
.background {
fill: #ccf;
pointer-events: all;
}
.feature {
fill: #4747ff;
cursor: pointer;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
<script src="https://d3js.org/topojson.v1.min.js"></script>
<div id="selector">
<select id="datasets" onchange="dataSetSelection();">
<option selected="selected">Choose a dataset</option>
<option value="0">Total population</option>
<option value="1">Intentional homicide rate</option>
<option value="2">Wind electricity production</option>
<option value="3">Annual population growth rate</option>
<option value="4">0-4 Years deaths per 1000 live births (Males)</option>
<option value="5">0-4 Years deaths per 1000 live births (Females)</option>
</select>
</div>
<div id="legend"></div>
<script>
var colorScale;
var divergingScale;
var currentTransformation;
var countryData;
var countryDataMap;
/// DRAW MAPS
var width = 1000,
height = 800,
active = d3.select(null);
var projection = d3.geoMercator() // updated for d3 v4
.scale(159)
.center([0, 30])
.translate([width / 2, height / 2]);
var zoom = d3.zoom()
.scaleExtent([1, 60])
.on("zoom", zoomed);
var path = d3.geoPath()
.projection(projection);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("click", stopped, true);
svg.append("rect")
.attr("class", "background")
.attr("width", width)
.attr("height", height);
var g = svg.append("g");
svg.call(zoom);
function dataSetSelection() {
var myselect = document.getElementById("datasets");
let val = +myselect.options[myselect.selectedIndex].value;
switch (val) {
case 0 :
loadDataSet("UNdata_population_total_in_thousands.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d["Year(s)"],
unit: "Total population (in thousands)",
value: +d.Value
};
}, null, d3.scaleSequential(d3.interpolateViridis), false);
break;
case 1 :
loadDataSet("UNdata_intentionalHomicide_number_rate_per_100000.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d.Year,
count: +d.Count,
value: +d.Rate,
source: d.Source,
unit: "Intentional homicides per 100'000 inhabitants"
};
}, null, d3.scaleSequential(d3.interpolateViridis), false);
break;
case 2 :
loadDataSet("UNdata_windElectricityProduction.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d.Year,
commodity: d["Commodity - Transaction"],
unit: d.Unit,
value: +d.Quantity
};
}, null, d3.scaleSequential(d3.interpolateViridis), false);
break;
case 3 :
loadDataSet("UNdata_Annual_population_growth_rate_in_percent.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d["Year(s)"],
unit: "% annual population growth rate",
value: +d.Value
};
}, null, d3.scaleLinear().range(["#BE1827", "#F4F8CE", "#313695"]), true);
break;
case 4 :
loadDataSet("UNdata_0-4_years_deaths_per_1000_live_births.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d.year,
source: d.source,
unit: d.Unit,
subgroup: d.Subgroup,
value: +d.Value
};
}, function (d) {
return d.subgroup === "Male 0-4 yr"
},
d3.scaleSequential(d3.interpolateViridis), false);
break;
case 5 :
loadDataSet("UNdata_0-4_years_deaths_per_1000_live_births.csv",
function (d) {
return {
country: d["Country or Area"],
iso: d.ISO3,
year: +d.year,
source: d.source,
unit: d.Unit,
subgroup: d.Subgroup,
value: +d.Value
};
}, function (d) {
return d.subgroup === "Female 0-4 yr"
},
d3.scaleSequential(d3.interpolateViridis), false);
default :
break;
}
}
/*d3.json("urbanAreas.json", function(error, us) {
if (error) throw error;
g.append("path")
.attr("d", path(topojson.feature(us, us.objects.ne_50m_urban_areas)))
.attr("stroke-opacity", "0")
.attr("fill", "#ef9b00");
}); */
/*d3.json("lakes.json", function(error, us) {
if (error) throw error;
g.append("path")
.attr("d", path(topojson.feature(us, us.objects.ne_50m_lakes)))
.attr("stroke-opacity", "0")
.attr("fill", "#0000FF");
});
d3.json("elevationPoints.json", function(error, us) {
if (error) throw error;
g.append("path")
.attr("d", path(topojson.feature(us, us.objects.ne_50m_geography_regions_elevation_points)))
.attr("fill", "#000")
});*/
function reset() {
svg.transition()
.duration(750)
.call(zoom.transform, d3.zoomIdentity);
}
function transformBounds(bounds) {
bounds[0][0] = (bounds[0][0] * currentTransformation.k) + currentTransformation.x;
bounds[0][1] = (bounds[0][1] * currentTransformation.k) + currentTransformation.y;
bounds[1][0] = (bounds[1][0] * currentTransformation.k) + currentTransformation.x;
bounds[1][1] = (bounds[1][1] * currentTransformation.k) + currentTransformation.y;
}
function zoomed() {
currentTransformation = d3.event.transform;
g.style("stroke-width", 1 / currentTransformation.k + "px");
g.attr("transform", currentTransformation);
//console.log(transform);
var selection = d3.selectAll("*[id^=country]");
selection.each(function (d, i) {
var bounds = path.bounds(d);
transformBounds(bounds);
let value = countryDataMap[this.getAttribute("id").substring(8)];
if (value !== undefined) {
let onMap = bounds[0][0] < width && bounds[1][0] > 0
&& bounds[0][1] < height && bounds[1][1] > 0
&& (bounds[0][0] > 0 || bounds[1][0] < width || bounds[0][1] > 0 || bounds[1][1] < height);
value.isOnMap = onMap;
//if (onMap) console.log(this.getAttribute("id").substring(8))
}
resampleAndDraw();
})
}
// If the drag behavior prevents the default click,
// also stop propagation so we don•t click-to-zoom.
function stopped() {
if (d3.event.defaultPrevented) d3.event.stopPropagation();
}
function clicked(d) {
console.log(this.getAttribute("id"));
/*var bounds = path.bounds(d);
d3.selectAll("#boundingBox").remove();
rect = g.insert('rect')
.attr('id', 'boundingBox')
.attr('x', bounds[0][0])
.attr('y', bounds[0][1])
.attr('width', bounds[1][0] - bounds[0][0])
.attr('height', bounds[1][1] - bounds[0][1])
.attr('fill-opacity', 0)
.attr('stroke', '#000')*/
}
function resampleAndDraw() {
let countriesOnMap = countryData.filter(function (d) {
return d.isOnMap;
});
// LEGEND
if (!divergingScale) {
colorScale.domain(d3.extent(countriesOnMap, function (d) {
return d.value;
}));
} else {
colorScale.domain([d3.min(countriesOnMap, function (d) {
return d.value;
}), 0, d3.max(countriesOnMap, function (d) {
return d.value;
})]);
}
drawLegend(countryData[0].unit);
// FILL COUNTRIES WITH DATASET
countriesOnMap.forEach(function (value, key, map) {
g.selectAll("#country-" + value.iso)
.attr("fill", function (d) {
return colorScale(value.value)
})
})
}
function loadDataSet(filename, transformFunction, filterFunction, scale, diverging) {
d3.queue()
.defer(d3.json, "world.json")
.defer(d3.csv, filename, transformFunction)
.await(
function (error, world, values) {
if (error) throw error;
reset();
colorScale = scale;
divergingScale = diverging;
if (filterFunction !== null) {
countryData = values.filter(filterFunction);
} else {
countryData = values;
}
// MAP DATASET
countryDataMap = {};
countryData.forEach(function (d) {
d.isOnMap = false;
countryDataMap[d.iso] = d;
});
// DRAW WORLD MAP AND FILTER COUNTRIES OFF SCREEN
g.selectAll("path").remove();
g.selectAll("path")
.data(topojson.feature(world, world.objects.ne_50m_admin_0_countries_lakes).features)
.enter().append("path")
.filter(function (d) {
return d.properties.iso_a3 !== "ATA" // REMOVE ANTARCTICA
})
.attr("d", path)
.attr("id", function (d) {
let iso = d.properties.iso_a3;
let value = countryDataMap[iso];
if (value !== undefined) {
value.isOnMap = true;
}
return "country-" + iso
})
.attr("fill", "#888")
.attr("stroke", "#000")
.on("click", clicked);
resampleAndDraw()
})
}
function drawLegend(unitName) {
const legendWidth = 800;
const legendHeight = 20;
const legendPadding = 30;
const legendPaddingBottom = 10;
const units = unitName;
// remove previous legend
d3.select("#legend").selectAll("svg").remove();
var legendSVG = d3.select("#legend")
.append("svg")
.attr("width", legendWidth + legendPadding + legendPadding)
.attr("height", legendHeight + legendPadding + legendPaddingBottom);
var defs = legendSVG.append("defs");
var legendGradient = defs.append("linearGradient")
.attr("id", "linear-gradient")
.attr("x1", "100%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "0%");
let noOfSamples = 60;
var stepSize = (colorScale.domain()[0] - colorScale.domain()[1]) / noOfSamples;
for (i = 0; i < noOfSamples; i++) {
legendGradient.append("stop")
.attr("offset", (i / (noOfSamples - 1)))
.attr("stop-color", colorScale(colorScale.domain()[1] + (i * stepSize)));
}
var legendG = legendSVG.append("g")
.attr("class", "legendLinear")
.attr("transform", "translate(" + legendPadding + "," + legendPadding + ")");
legendG.append("text")
.text(Math.floor(colorScale.domain()[0]) + " " + units)
.attr("x", 0)
.attr("y", legendHeight - 35)
.style("font-size", "12px");
legendG.append("text")
.text(Math.ceil(divergingScale ? colorScale.domain()[2] : colorScale.domain()[1]) + " " + units)
.attr("x", legendWidth)
.attr("y", legendHeight - 35)
.style("text-anchor", "end")
.style("font-size", "12px");
legendG.append("rect")
.attr("x", 0)
.attr("y", 0)
.attr("width", divergingScale ? legendWidth / 2 : legendWidth)
.attr("height", legendHeight)
.style("fill", "url(#linear-gradient)");
if (divergingScale) {
legendGradient = defs.append("linearGradient")
.attr("id", "linear-gradient2")
.attr("x1", "100%")
.attr("y1", "0%")
.attr("x2", "0%")
.attr("y2", "0%");
stepSize = (colorScale.domain()[1] - colorScale.domain()[2]) / noOfSamples;
for (i = 0; i < noOfSamples; i++) {
legendGradient.append("stop")
.attr("offset", (i / (noOfSamples - 1)))
.attr("stop-color", colorScale(colorScale.domain()[2] + (i * stepSize)));
}
legendG = legendSVG.append("g")
.attr("class", "legendLinear")
.attr("transform", "translate(" + legendPadding + "," + legendPadding + ")");
legendG.append("text")
.text("0")
.attr("x", legendWidth / 2)
.attr("y", legendHeight - 35)
.style("text-anchor", "middle ")
.style("font-size", "12px");
legendG.append("rect")
.attr("x", legendWidth / 2)
.attr("y", 0)
.attr("width", legendWidth / 2)
.attr("height", legendHeight)
.style("fill", "url(#linear-gradient2)");
}
}
</script>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3-scale-chromatic.v1.min.js
https://d3js.org/topojson.v1.min.js