First presented at the SydJS January 2014 meetup.
SydJS has had Atlassian as venue sponsor for a long time, and in that time Atlassian moved offices across town. So I took words from the titles of presentations at each location and made two word clouds. Then, I added them to a Leaflet map, overlaid on each office location!
Details and some code snippets are in my talk slides.
Based heavily on Jason Davies' d3-cloud and Mike Bostock's Leaflet integration example.
The full talk, including this example, are from the repository azza-bazoo/js-devs-dont-get-lost.
xxxxxxxxxx
<meta charset="utf-8">
<link rel="stylesheet" href="https://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css">
<style type="text/css">
svg {
position: relative;
}
body {
margin: 0;
padding: 0;
}
#map {
height: 600px;
width: 960px;
}
</style>
<body>
<div id="map"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/0.7.1/leaflet.min.js"></script>
<script src="https://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="d3.layout.cloud.js"></script>
<script src="sydjs-words.js"></script>
<script>
$(function() {
// one of D3's built-in scales, with 10 more-or-less random (but reasonable) colours
var fill = d3.scale.category10();
// draw a text element for one word into the passed SVG group; these functions based on
// https://github.com/jasondavies/d3-cloud/blob/master/examples/simple.html
var draw = function(word_data, group_el) {
group_el
.selectAll("text")
.data(word_data)
.enter().append("text")
.style("font-size", function(d) { return d.size + "px"; })
.style("font-family", "Impact, sans-serif")
.style("fill", function(d, i) { return fill(i); })
.attr("text-anchor", "middle")
.attr("transform", function(d) {
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")";
})
.text(function(d) { return d.key; })
}
// run the word cloud layout on the whole wordset, rendering into the i'th group element
var layoutWords = function(wordset, i, fontScale) {
var layout = d3.layout.cloud()
.size([450, 450])
.text(function(d) { return d.key; })
.font("Impact")
.fontSize(function(d) { return fontScale(+d.value); })
.rotate(function(d) { return ~~(Math.random() * 4) * 45 - 90; })
.padding(1)
.on("end", function(word_data) {
draw(word_data, g[i]);
})
.words(wordset)
.start();
}
var stopwords = ["and", "the", "with", "for", "to", "of", "in", "from", "a", "can", "but", "get", "an", "so"];
var wordsets = [];
var max_count = 0; // used to set font size scale
// fairly braindead text processing: filter out stopwords, count occurrences,
// generate an array of objects for use in the word cloud code
data.forEach(function(item) {
var word_hash = {};
item.words.forEach(function(word) {
if (stopwords.indexOf(word) !== -1) {
return;
}
if (word in word_hash) {
word_hash[word] ++;
} else {
word_hash[word] = 1;
}
});
var wordset = [];
for (var word in word_hash) {
wordset.push({ key: word, value: word_hash[word] });
if (word_hash[word] > max_count) { max_count = word_hash[word]; }
}
wordsets.push(wordset);
});
// setup Leaflet map: centre location and zoom level are handpicked for presentation
var mapOriginalZoom = 17;
var map = new L.Map("map", {center: [-33.8691, 151.20538], zoom: mapOriginalZoom})
.addLayer(new L.TileLayer("https://{s}.tile.cloudmade.com/20431b4bbf9e4c3f90399422715b6fa0/998/256/{z}/{x}/{y}.png"));
// add elements for use with D3 into the overlay pane
var svg = d3.select(map.getPanes().overlayPane).append("svg");
var g = [];
// reposition the SVG when the user zooms or pans, so the whole word cloud is visible
var resetSVG = function() {
// Loosely based on https://bost.ocks.org/mike/leaflet/
// TODO: possible alternative approach at https://stackoverflow.com/a/16473294/1523020
var ctopleft = map.containerPointToLatLng(L.point(0,0));
var ltopleft = map.latLngToLayerPoint(ctopleft);
var zoom_level = Math.pow(2, (map.getZoom() - mapOriginalZoom));
// the SVG moves when the user pans, so put it back to being exactly on top of the map
svg.style("left", ltopleft.x + "px")
.style("top", ltopleft.y + "px");
for (var i = 0; i < g.length; i ++) {
// recalculate pixel coords of where the word cloud centre should be (changes when the user zooms)
var word_centre = map.latLngToContainerPoint(new L.LatLng(data[i].lat, data[i].long));
g[i].attr("transform", "scale(" + zoom_level + ")translate(" + (word_centre.x / zoom_level) + "," + (word_centre.y / zoom_level) + ")");
}
}
// handpicked scale to look reasonable at our chosen zoom level
var fontScale = d3.scale.log().range([5, max_count * 3]);
wordsets.forEach(function(wordset, idx) {
g[idx] = svg.append("g")
.attr("class", "leaflet-zoom-hide");
layoutWords(wordset, idx, fontScale);
});
// initial SVG setup: it should be exactly on top of the map (same size and position)
svg.attr("width", $("#map").width())
.attr("height", $("#map").height())
.style("left", "0px")
.style("top", "0px");
// register map-moving event handler with Leaflet, and do an initialisation so it looks right on load
map.on("moveend", resetSVG);
resetSVG();
});
</script>
Modified http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js to a secure url
Modified http://code.jquery.com/jquery-2.0.3.min.js to a secure url
Modified http://d3js.org/d3.v3.min.js to a secure url
https://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js
https://code.jquery.com/jquery-2.0.3.min.js
https://d3js.org/d3.v3.min.js