d3-bboxCollide allows for rectangular collision detection. This means we can do word cloud-like visualizations with more rules and constraints. In this case, the word clouds are split into columns based on speaker and laid out down the y-axis based on the segment of the debate for the term density of that word.
The data comes from the first Presidential debate of 2016, processed using Voyant Tools with custom stop words removed and then processed for term density into the 20 bins.
xxxxxxxxxx
<html>
<head>
<title>Word Trails</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="collide.js"></script>
<script src="clinton.js"></script>
<script src="trump.js"></script>
<script src="holt.js"></script>
<link href="https://fonts.googleapis.com/css?family=Oxygen+Mono" rel="stylesheet">
</head>
<style>
body {
font-family: 'Oxygen Mono', monospace;
}
svg {
height: 1000px;
width: 1000px;
border: 1px solid lightgray;
}
</style>
<body>
<div id="viz">
<svg class="main">
</svg>
</div>
</body>
<footer>
<script>
function rectWidth(word, value) {
return word.length * value
}
valueScale = d3.scaleLinear().domain([1,40]).range([2,80])
var data = [{segment: -1.5, word: "Trump", value: 40, speaker: "trump"},
{segment: -1.5, word: "Holt", value: 40, speaker: "holt"},
{segment: -1.5, word: "Clinton", value: 40, speaker: "clinton"}]
holt.documentTerms.terms.forEach(function (d) {
d.distributions.forEach(function (dist, i) {
if (dist !== 0 && d.term !== "bar" && dist > 3) {
data.push({ segment: i, word: d.term, value: valueScale(dist), speaker: "holt" })
}
})
})
trump.documentTerms.terms.forEach(function (d) {
d.distributions.forEach(function (dist, i) {
if (dist !== 0 && d.term !== "bar" && dist > 3) {
data.push({ segment: i, word: d.term, value: valueScale(dist), speaker: "trump" })
}
})
})
clinton.documentTerms.terms.forEach(function (d) {
d.distributions.forEach(function (dist, i) {
if (dist !== 0 && d.term !== "bar" && dist > 3) {
data.push({ segment: i, word: d.term, value: valueScale(dist), speaker: "clinton" })
}
})
})
var speakerHash = {
holt: "#222127",
trump: "#d4324a",
clinton: "#6475bb"
}
var speakerColumn = {
holt: 1,
trump: 2,
clinton: 0
}
var widthMod = 1
var heightMod = 0.5
var networkCenter = d3.forceCenter().x(400).y(480);
var forceX = d3
// .forceX(function (d) {return 250})
.forceX(function (d) {return speakerColumn[d.speaker] * 200 + 50})
.strength(1)
var forceY = d3.forceY(function (d) {return d.segment * 40})
.strength(1)
var collide = d3.bboxCollide(function (d,i) {
var width = rectWidth(d.word, d.value)
return [[-width / 2, -d.value * heightMod],[width / 2, d.value * heightMod]]
})
.strength(1)
.iterations(2)
var color = d3.scaleOrdinal(d3.schemeCategory20b)
var force = d3.forceSimulation(data)
.velocityDecay(0.6)
.force("center", networkCenter)
.force("x", forceX)
.force("y", forceY)
.force("collide", collide)
.on("tick", updateNetwork);
var nodeEnter = d3.select("svg.main")
.append("g")
.selectAll("g.node")
.data(data)
.enter()
.append("g")
.attr("class", "node")
nodeEnter.append("text")
.style("font-size", function (d) {return d.value})
.style("text-anchor", "middle")
.attr("y", function (d) {return d.value / 4})
.style("fill", function (d, i) {return speakerHash[d.speaker]})
.style("font-weight", 600)
.text(function (d) {return d.word})
function updateNetwork() {
d3.select("svg.main").selectAll("g.node")
.attr("transform", function (d) {return "translate(" + d.x + "," + d.y + ")"})
}
</script>
</footer>
</html>
https://d3js.org/d3.v4.min.js