Taking advantage of the forceSimulation-based nature of the cartogram to switch to a bubble chart using the same data.
Demographics from http://library.duke.edu/data/collections/popest
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Force-Based Cartogram to Bubble Chart</title>
<style>
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<svg
width="1200"
height="1000"
version="1.1" id="Ebene_1" xmlns="//www.w3.org/2000/svg" xmlns:xlink="//www.w3.org/1999/xlink" >
</svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-scale-cluster@1.1.7/dist/d3-scale-cluster.min.js"></script>
<script src="keys.js"></script>
<script>
var colors = [
"#d0d9c4",
"#dfa3ad",
"#d0915b",
"#a9573f",
"#844e5e"
]
var selectedValue = "tot_pop"
var selectedIndex = 0
var sizeRange = [1.5,50]
var notcarto = true
d3.csv("county_demo.csv", cartogram)
function cartogram(data) {
var dataKeys = Object.keys(keys)
data.forEach(d => {
dataKeys.forEach(k => {
d[k] = parseInt(d[k])
})
})
var thisKeyValues = data.map(d => d[selectedValue])
var extent = d3.extent(thisKeyValues)
var scale = d3.scaleLinear().domain(extent).range(sizeRange)
var colorScale = d3.scaleCluster().domain(thisKeyValues).range(colors)
var force = d3.forceSimulation()
.force("collision", d3.forceCollide(d => scale(d[selectedValue])).iterations(2))
.force("x", d3.forceX(d => d.cx))
.force("y", d3.forceY(d => d.cy))
.nodes(data)
.alphaMin(0.25)
.on("tick", updateCartogram)
.on("end", resetCartogram)
d3.select("svg")
.selectAll("circle.node")
.data(data)
.enter()
.append("circle")
.attr("class", "node")
.style("stroke-width", 2)
d3.select("svg").append("text")
.style("text-anchor", "middle")
.attr("class", "title")
.text("Total Population")
.style("font-size", "36px")
.attr("x", 600)
.attr("y", 50)
redrawNodes()
function updateCartogram() {
d3.selectAll("circle.node")
.attr("cx", d => d.x)
.attr("cy", d => d.y)
}
function resetCartogram() {
if (notcarto) {
selectedIndex = selectedIndex === dataKeys.length ? 0 : selectedIndex + 1
selectedValue = dataKeys[selectedIndex]
d3.select("text.title")
.text(keys[selectedValue])
thisKeyValues = data.map(d => d[selectedValue])
extent = d3.extent(thisKeyValues)
scale = d3.scaleLinear().domain(extent).range(sizeRange)
colorScale = d3.scaleCluster().domain(thisKeyValues).range(colors)
redrawNodes()
force.force("collision", d3.forceCollide(d => scale(d[selectedValue])).iterations(2))
.force("x", d3.forceX(d => d.cx))
.force("y", d3.forceY(d => d.cy))
.alpha(1)
.restart()
}
else {
var positionScale = d3.scaleCluster().domain(thisKeyValues).range([200,400,600,800,1000])
force
.force("x", d3.forceX(d => positionScale(d[selectedValue])))
.force("y", d3.forceY(400))
.alpha(1)
.restart()
}
notcarto = !notcarto
}
function redrawNodes() {
d3.selectAll("circle.node")
.transition()
.duration(500)
.style("fill", d => colorScale(d[selectedValue]))
.style("stroke", d => d3.hsl(colorScale(d[selectedValue])).darker())
.attr("r", d => scale(d[selectedValue]))
}
}
</script>
var keys = {
tot_pop: "Total population",
tot_male: "Total population, male",
tot_female: "Total population, female",
nh_male: "Total population, non‐Hispanic male",
nh_female: "Total population, non‐Hispanic female",
h_male: "Total population, Hispanic male",
h_female: "Total population, Hispanic female",
wa_male: "Total population, White male",
wa_female: "Total population, White female",
ba_male: "Total population, Black male",
ba_female: "Total population, Black female",
ia_male: "Total population, American Indian/Alaska Native male",
ia_female: "Total population, American Indian/Alaska Native female",
aa_male: "Total population, Asian male",
aa_female: "Total population, Asian female",
na_male: "Total population, Native Hawaiian and Other Pacific Islander male",
na_female: "Total population, Native Hawaiian and Other Pacific Islander female",
tom_male: "Total population, Two or more races male",
tom_female: "Total population, Two or more races female",
nhwa_male: "White male alone, not Hispanic or Latino",
nhwa_female: "White female alone, not Hispanic or Latino",
nhba_male: "Black male alone, not Hispanic or Latino",
nhba_female: "Black female alone, not Hispanic or Latino",
nhia_male: "American Indian or Alaska Native male, not Hispanic or Latino",
nhia_female: "American Indian or Alaska Native female, not Hispanic or Latino",
nhaa_male: "Asian male, not Hispanic or Latino",
nhaa_female: "Asian female, not Hispanic or Latino",
nhna_male: "Native Hawaiian and Other Pacific Islander male alone, not Hispanic or Latino",
nhna_female: "Native Hawaiian and Other Pacific Islander female alone, not Hispanic or Latino",
nhtom_male: "Two or more races male, not Hispanic or Latino",
nhtom_female: "Two or more races female, not Hispanic or Latino",
hwa_male: "White male alone, Hispanic or Latino",
hwa_female: "White female alone, Hispanic or Latino",
hba_male: "Black male alone, Hispanic or Latino",
hba_female: "Black female alone, Hispanic or Latino",
hia_male: "American Indian or Alaska Native male, Hispanic or Latino",
hia_female: "American Indian or Alaska Native female, Hispanic or Latino",
haa_male: "Asian male, Hispanic or Latino",
haa_female: "Asian female, Hispanic or Latino",
hna_male: "Native Hawaiian and Other Pacific Islander male alone, Hispanic or Latino",
hna_female: "Native Hawaiian and Other Pacific Islander male alone, Hispanic or Latino",
htom_male: "Two or more races male, not Hispanic or Latino",
htom_female: "Two or more races female, not Hispanic or Latino"
}