Skip to content

Instantly share code, notes, and snippets.

@azza-bazoo
Last active August 29, 2015 13:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save azza-bazoo/9381206 to your computer and use it in GitHub Desktop.
Save azza-bazoo/9381206 to your computer and use it in GitHub Desktop.
Word clouds on a Leaflet map

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.

// Word cloud layout by Jason Davies, http://www.jasondavies.com/word-cloud/
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
(function() {
function cloud() {
var size = [256, 256],
text = cloudText,
font = cloudFont,
fontSize = cloudFontSize,
fontStyle = cloudFontNormal,
fontWeight = cloudFontNormal,
rotate = cloudRotate,
padding = cloudPadding,
spiral = archimedeanSpiral,
words = [],
timeInterval = Infinity,
event = d3.dispatch("word", "end"),
timer = null,
cloud = {};
cloud.start = function() {
var board = zeroArray((size[0] >> 5) * size[1]),
bounds = null,
n = words.length,
i = -1,
tags = [],
data = words.map(function(d, i) {
d.text = text.call(this, d, i);
d.font = font.call(this, d, i);
d.style = fontStyle.call(this, d, i);
d.weight = fontWeight.call(this, d, i);
d.rotate = rotate.call(this, d, i);
d.size = ~~fontSize.call(this, d, i);
d.padding = padding.call(this, d, i);
return d;
}).sort(function(a, b) { return b.size - a.size; });
if (timer) clearInterval(timer);
timer = setInterval(step, 0);
step();
return cloud;
function step() {
var start = +new Date,
d;
while (+new Date - start < timeInterval && ++i < n && timer) {
d = data[i];
d.x = (size[0] * (Math.random() + .5)) >> 1;
d.y = (size[1] * (Math.random() + .5)) >> 1;
cloudSprite(d, data, i);
if (d.hasText && place(board, d, bounds)) {
tags.push(d);
event.word(d);
if (bounds) cloudBounds(bounds, d);
else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
// Temporary hack
d.x -= size[0] >> 1;
d.y -= size[1] >> 1;
}
}
if (i >= n) {
cloud.stop();
event.end(tags, bounds);
}
}
}
cloud.stop = function() {
if (timer) {
clearInterval(timer);
timer = null;
}
return cloud;
};
cloud.timeInterval = function(x) {
if (!arguments.length) return timeInterval;
timeInterval = x == null ? Infinity : x;
return cloud;
};
function place(board, tag, bounds) {
var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
startX = tag.x,
startY = tag.y,
maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
s = spiral(size),
dt = Math.random() < .5 ? 1 : -1,
t = -dt,
dxdy,
dx,
dy;
while (dxdy = s(t += dt)) {
dx = ~~dxdy[0];
dy = ~~dxdy[1];
if (Math.min(dx, dy) > maxDelta) break;
tag.x = startX + dx;
tag.y = startY + dy;
if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
// TODO only check for collisions within current bounds.
if (!bounds || !cloudCollide(tag, board, size[0])) {
if (!bounds || collideRects(tag, bounds)) {
var sprite = tag.sprite,
w = tag.width >> 5,
sw = size[0] >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
}
x += sw;
}
delete tag.sprite;
return true;
}
}
}
return false;
}
cloud.words = function(x) {
if (!arguments.length) return words;
words = x;
return cloud;
};
cloud.size = function(x) {
if (!arguments.length) return size;
size = [+x[0], +x[1]];
return cloud;
};
cloud.font = function(x) {
if (!arguments.length) return font;
font = d3.functor(x);
return cloud;
};
cloud.fontStyle = function(x) {
if (!arguments.length) return fontStyle;
fontStyle = d3.functor(x);
return cloud;
};
cloud.fontWeight = function(x) {
if (!arguments.length) return fontWeight;
fontWeight = d3.functor(x);
return cloud;
};
cloud.rotate = function(x) {
if (!arguments.length) return rotate;
rotate = d3.functor(x);
return cloud;
};
cloud.text = function(x) {
if (!arguments.length) return text;
text = d3.functor(x);
return cloud;
};
cloud.spiral = function(x) {
if (!arguments.length) return spiral;
spiral = spirals[x + ""] || x;
return cloud;
};
cloud.fontSize = function(x) {
if (!arguments.length) return fontSize;
fontSize = d3.functor(x);
return cloud;
};
cloud.padding = function(x) {
if (!arguments.length) return padding;
padding = d3.functor(x);
return cloud;
};
return d3.rebind(cloud, event, "on");
}
function cloudText(d) {
return d.text;
}
function cloudFont() {
return "serif";
}
function cloudFontNormal() {
return "normal";
}
function cloudFontSize(d) {
return Math.sqrt(d.value);
}
function cloudRotate() {
return (~~(Math.random() * 6) - 3) * 30;
}
function cloudPadding() {
return 1;
}
// Fetches a monochrome sprite bitmap for the specified text.
// Load in batches for speed.
function cloudSprite(d, data, di) {
if (d.sprite) return;
c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
var x = 0,
y = 0,
maxh = 0,
n = data.length;
--di;
while (++di < n) {
d = data[di];
c.save();
c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
var w = c.measureText(d.text + "m").width * ratio,
h = d.size << 1;
if (d.rotate) {
var sr = Math.sin(d.rotate * cloudRadians),
cr = Math.cos(d.rotate * cloudRadians),
wcr = w * cr,
wsr = w * sr,
hcr = h * cr,
hsr = h * sr;
w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
} else {
w = (w + 0x1f) >> 5 << 5;
}
if (h > maxh) maxh = h;
if (x + w >= (cw << 5)) {
x = 0;
y += maxh;
maxh = 0;
}
if (y + h >= ch) break;
c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
if (d.rotate) c.rotate(d.rotate * cloudRadians);
c.fillText(d.text, 0, 0);
if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
c.restore();
d.width = w;
d.height = h;
d.xoff = x;
d.yoff = y;
d.x1 = w >> 1;
d.y1 = h >> 1;
d.x0 = -d.x1;
d.y0 = -d.y1;
d.hasText = true;
x += w;
}
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
sprite = [];
while (--di >= 0) {
d = data[di];
if (!d.hasText) continue;
var w = d.width,
w32 = w >> 5,
h = d.y1 - d.y0;
// Zero the buffer
for (var i = 0; i < h * w32; i++) sprite[i] = 0;
x = d.xoff;
if (x == null) return;
y = d.yoff;
var seen = 0,
seenRow = -1;
for (var j = 0; j < h; j++) {
for (var i = 0; i < w; i++) {
var k = w32 * j + (i >> 5),
m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
sprite[k] |= m;
seen |= m;
}
if (seen) seenRow = j;
else {
d.y0++;
h--;
j--;
y++;
}
}
d.y1 = d.y0 + seenRow;
d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
}
}
// Use mask-based collision detection.
function cloudCollide(tag, board, sw) {
sw >>= 5;
var sprite = tag.sprite,
w = tag.width >> 5,
lx = tag.x - (w << 4),
sx = lx & 0x7f,
msx = 32 - sx,
h = tag.y1 - tag.y0,
x = (tag.y + tag.y0) * sw + (lx >> 5),
last;
for (var j = 0; j < h; j++) {
last = 0;
for (var i = 0; i <= w; i++) {
if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
& board[x + i]) return true;
}
x += sw;
}
return false;
}
function cloudBounds(bounds, d) {
var b0 = bounds[0],
b1 = bounds[1];
if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
}
function collideRects(a, b) {
return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
}
function archimedeanSpiral(size) {
var e = size[0] / size[1];
return function(t) {
return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
};
}
function rectangularSpiral(size) {
var dy = 4,
dx = dy * size[0] / size[1],
x = 0,
y = 0;
return function(t) {
var sign = t < 0 ? -1 : 1;
// See triangular numbers: T_n = n * (n + 1) / 2.
switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
case 0: x += dx; break;
case 1: y += dy; break;
case 2: x -= dx; break;
default: y -= dy; break;
}
return [x, y];
};
}
// TODO reuse arrays?
function zeroArray(n) {
var a = [],
i = -1;
while (++i < n) a[i] = 0;
return a;
}
var cloudRadians = Math.PI / 180,
cw = 1 << 11 >> 5,
ch = 1 << 11,
canvas,
ratio = 1;
if (typeof document !== "undefined") {
canvas = document.createElement("canvas");
canvas.width = 1;
canvas.height = 1;
ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
canvas.width = (cw << 5) / ratio;
canvas.height = ch / ratio;
} else {
// Attempt to use node-canvas.
canvas = new Canvas(cw << 5, ch);
}
var c = canvas.getContext("2d"),
spirals = {
archimedean: archimedeanSpiral,
rectangular: rectangularSpiral
};
c.fillStyle = c.strokeStyle = "red";
c.textAlign = "center";
if (typeof module === "object" && module.exports) module.exports = cloud;
else (d3.layout || (d3.layout = {})).cloud = cloud;
})();
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="http://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="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script>
<script src="http://code.jquery.com/jquery-2.0.3.min.js"></script>
<script src="http://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("http://{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 http://bost.ocks.org/mike/leaflet/
// TODO: possible alternative approach at http://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>
var data = [
{
lat: -33.8708,
long: 151.2036,
words: [
"Jared", "Wiles", "Sharpening", "the", "edge", "of", "your", "HTML5", "Animate,", "Reflow,", "Code,", "Inspect,", "Type,", "and", "Build", "Learn", "why", "you", "need", "that", "Creative", "Cloud", "membership", "Stoyan", "Stefanov", "Liking", "performance", "Even", "if", "you", "don't", "love", "the", "Like", "button,", "you'll", "hate", "not", "hearing", "how", "much", "love", "has", "gone", "into", "making", "it", "perform", "Douglas", "Crockford", "Faster,", "easier", "and", "safer", "Not", "just", "the", "most", "successful", "online", "payment", "solution,", "also", "the", "one", "that", "is", "keeping", "Crockford", "sharp", "If", "we're", "lucky,", "we'll", "get", "to", "hear", "about", "Paypal", "the", "Good", "Parts", "Steve", "Haffenden", "Putting", "the", "Hey", "you", "in", "JSConfAU", "We'll", "take", "a", "running", "start", "and", "try", "to", "fit", "all", "the", "majesty", "of", "JSConfAU", "in", "to", "16", "minute", "the", "two", "minutes", "an", "hour,", "not", "accounting", "for", "the", "before", "and", "after", "parties", "Hang", "on", "to", "your", "hats!", "Daniel", "Friedman", "Nodebots,", "Nodecopters,", "and", "now", "Ninja", "Blocks", "Ninja", "Blocks", "are", "back", "better", "than", "ever", "and", "are", "running", "on", "a", "Node.JS", "server", "near", "you", "And", "that", "means", "REALLY", "near", "you", "We'll", "get", "the", "inside", "scoop", "on", "where", "the", "platform's", "heading", "John", "Bristowe", "Surviving", "the", "Mayan", "Apocalypse", "with", "Kendo", "UI", "you", "have", "until", "December", "21st", "to", "finish", "your", "web", "application", "and", "to", "immortalised", "in", "the", "ashes", "of", "digital", "civilisation", "you", "need", "a", "framework", "Fast", "Enter", "Kendo", "UI", "to", "save", "the", "day", "At", "least,", "until", "the", "meteor", "hits", "JudgementDay.js", "anyone?", ";)", "Nick", "Hodge", "IE10", "web,", "phone", "and", "native", "apps", "Nick", "will", "introduce", "a", "technique", "so", "cunning", "it", "could", "have", "featured", "on", "Black", "Adder's", "a", "Christmas", "Carol", "John", "Scott", "the", "Joy", "of", "AngularJS", "a", "web", "framework", "from", "Google", "that", "brings", "web", "designers", "and", "JavaScript", "developers", "together", "in", "harmonious", "co-operation", "Aaron", "Powel", "Something,", "something,", "TypeScript", "Application", "scale,", "type", "safe,", "class-designed,", "compiled", "JavaScript", "Because,", "reason", "Glen", "Maddern", "Sup,", "AngularJS?", "a", "developer's", "view", "of", "AngularJS'", "features", "with", "developer's", "pragmatism", "Alex", "Dickson", "Building", "debugger", "disassembler", "in", "JavaScript", "Chris", "Darroch", "JavaScript", "tips", "and", "tricks", "from", "the", "front-line", "Adam", "Ahmed", "Real", "world", "Node.JS", "drones", "Cameron", "Adams", "Pure", "JavaScript", "Earle", "Castledine", "jQuery", "Jason", "Crane", "YUI", "Christopher", "Hunt", "Typesafe", "Play-ing", "with", "WebJars", "Nikolay", "Nemshilov", "Lovely.IO", "Mark", "Byers", "JavaScript", "based", "ERP", "building", "the", "full", "experience", "Simon", "Rodwell", "I18n", "why,", "how,", "and", "where", "the", "name", "came", "from", "Christopher", "Saunders", "WebGL,", "the", "Goo", "Engine,", "and", "your", "Blackberry", "Michael", "Neale", "Jenkins-CI", "JavaScript", "building", "all", "the", "javascript", "server", "side", "and", "client", "side", "Patrick", "Roumanoff", "the", "future", "is", "a", "promise", "Promises", "and", "A+", "John", "Allsopp", "Building", "an", "HTML5", "security", "camera", "LIVE", "in", "under", "15", "minutes", "Don", "Nguyen", "Server", "Side", "JavaScript", "building", "a", "game", "right", "before", "your", "eyes", "Ben", "Schwarz", "Built", "an", "application,", "and", "will", "show", "you", "what", "he", "learnt", "Aaron", "Powell", "Everything", "you", "wanted", "to", "know", "about", "IE11,", "and", "even", "some", "things", "you", "didn't", "know", "you", "wanted", "Vaughn", "Knight", "How", "I", "turned", "away", "from", "JavaScript", "so", "I", "could", "embrace", "JavaScript", "Benjamin", "Lupton", "DocPad", "How,", "when,", "where,", "and", "why", "David", "Banham", "Deploy", "Your", "Node", "IaaS", "and", "PaaS", "players", "from", "a", "nodeJS", "perspective", "Nikolay", "Nemshilov", "JavaScript", "and", "CSS", "Totally", "changing", "the", "course", "of", "humans", "history", "Damon", "Oehlman", "[LIGHTNING TALK]", "RANT", "RANT", "RANT", "Benjamin", "Lupton", "David", "Banham", "Nikolay", "Nemshilov", "Damon", "Oehlman", "Jed", "Watson", "KeystoneJS", "a", "head-start", "on", "the", "features", "you", "need", "Simon", "Swain", "Berzerk", "Interactive", "charting", "via", "Backbone", "Ben", "Wong", "Alex", "Dickson", "a", "node-powered", "arcade", "machine", "Patrick", "Klug", "a", "cross-platform", "game", "Boris", "Bozic", "a", "gold-class", "Gold", "Class", "application", "using", "Cordova", "Alex", "Danillo", "advances", "in", "the", "encoding", "space", "and", "will", "outline", "base128", "encoding", "Hourann", "Bosci", "JavaScript", "Devs", "don't", "get", "lost", "John", "Bristowe", "a", "Brief", "Introduction", "to", "Polymer", "Tom", "Walker", "Cognizance"
]
},
{
lat: -33.8672,
long: 151.2069,
words: [
"Gabe", "Hollombe", "Archers", "vs", "Zombies!", "Making", "a", "game", "with", "EaselJS", "and", "Coffeescript", "Joseph", "Gentle", "Stop,", "Collaborate,", "and", "Listen", "Ben", "Buchanan", "Code", "Combat", "writing", "and", "refactoring", "for", "fun", "and", "profit", "Paul", "Theriault", "An", "Introduction", "to", "OWASP", "the", "Open", "Web", "Application", "Security", "Project", "Filippo", "Vitale", "Modelling", "a", "view", "of", "the", "Model", "View", "ViewModel", "Simon", "Swain", "Lugging", "and", "Plugging", "building", "a", "custom", "Interface", "from", "the", "ground", "up", "with", "jQueryUI", "and", "Raphael", "John", "Allsopp", "Offline", "apps", "with", "HTML5", "Steve", "Haffenden", "Everybody", "loves", "Strings,", "an", "Introduction", "Chris", "Darroch", "Making", "testing", "less", "testing", "with", "Sinon.js", "Dominic", "Lovell", "What's", "next", "for", "webOS?", "Valery", "Yushchenko", "Modern", "Tea", "Ceremony", "Powered", "by", "Node.js", "Jonathan", "Creenaune", , "walk", "through", "JavaScript's", "event-driven", "model", "and", "beyond", "Brian", "McKenna", "AltJS", "languages", "that", "compile", "to", "JS", "Adam", "Ahmed", "Inside", "large", "scale", "applications", "Wesley", "Walser", "Getting", "loud", "about", "Amplify.js", "John", "Bristowe", "Bringing", "the", "Awesome", "with", "Kendo", "UI", "Graeme", "Merrall", "Everything", "you", "wanted", "to", "know", "about", "Dart", "but", "were", "too", "afraid", "to", "ask", "Jared", "Wyles", "Missing", "the", "Forrest", "for", "the", "trees", "Ben", "Schwarz", "Never", "accept", "no", "for", "an", "answer", "Joseph", "Gentle", "a2D", "physics", "engine", "to", "JavaScript", "Gabe", "Hollombe", "Steam", "Network", "whitepapers", "Earle", "Castledine", "Deadlines", "and", "deprecation", "jQuery", "you", "can't", "see", "Marcus", "and", "Maddy", "Ninja", "Blocks", "the", "online", "world", "you", "can", "see", "Simon", "Swain", "Now", "you've", "got", "two", "problems", "Plumbing", "up", "some", "live", "data", "with", "Node", "Real", "time", "Node.js,", "socket.io,", "redis", "and", "visual", "basic", "Lachlan", "Hardy", "Windows 8", "and", "Websockets,", "mate!", "Chris", "Broadfoot", "Big", "and", "Beautiful", "Data", "Visualisation", "with", "WebSockets", "and", "WebGL", "Vaughan", "Knight", "From", "Way", "Out", "Wars", "and", "Dodo", "GoGo,", "and", "everything", "I've", "learnt", "between", "Rob", "Hawkes", "Boot", "to", "Gecko", "(B2G)", "Pursuing", "the", "goal", "of", "building", "a", "complete,", "standalone", "operating", "system", "for", "the", "open", "web", "Filippo", "Vitale", "Project", "Euler", "208", "How", "to", "love", "javascript", "enough", "you'll", "even", "want", "to", "solve", "a", "math", "problem", "Andrew", "Dodson", "OAuth", "Now", "that's", "something", "we", "can", "all", "identify", "with", "Dave", "Elkin", "Template", "sharing", "between", "the", "server", "and", "the", "client", "with", "node.js", "and", "Hogan", "Jo", "Cranford", "Testing", "like", "a", "boss", "Writing", "unit", "tests", "for", "JavaScript", "can", "be", "daunting", "Even", "once", "you've", "written", "the", "tests,", "how", "do", "you", "get", "them", "into", "the", "build?", "See", "how", "to", "write", "tests", "quickly,", "and", "get", "them", "running", "continuously", "Chris", "Saunders", "Power", "and", "Flexibility", "the", "BlackBerry", "Web", "Platform", "Learn", "about", "the", "commitments", "BlackBerry", "is", "making", "to", "the", "web", "community", "and", "the", "open", "source", "projects", "that", "they", "are", "supporting", "He", "will", "also", "have", "a", "BlackBerry", "PlayBook", "up", "for", "grabs", "so", "stay", "tuned", "to", "find", "out", "how", "you", "can", "win!", "Sugendran", "Ganess", "Making", "a", "scott", "free", "Ski", "Free", "Just", "create", "a", "game", "in", "the", "browser,", "wrap", "it", "in", "some", "sort", "of", "container", "and", "sell", "it", "for", "lots", "of", "monies", "in", "every", "store", "How", "hard", "could", "it", "be?", "Valery", "Yushchenko", "Touching", "on", "Sencha", "Touch", "Our", "resident", "strongman", "looks", "at", "the", "strong", "and", "not", "so", "strong", "points", "of", "Sencha", "Touch", "David", "Banham", "there", "I", "fixed", "it!", "Sometimes", "an", "elegant", "solution", "starts", "its", "life", "as", "a", "conglomeration", "of", "a", "few", "ways", "we", "get", "things", "done", "that", "at", "first", "look", "like", "horrible", "kludges,", "but", "actually", "turn", "out", "to", "be", "anything", "but!"
]
}
]
<!DOCTYPE html>
<meta charset="utf-8">
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.css">
<style type="text/css">
svg {
position: relative;
}
#map {
height: 750px;
}
</style>
<body>
<div id="map"></div>
<script src="http://cdn.leafletjs.com/leaflet-0.7.1/leaflet.js"></script>
<script src="js/jquery-2.0.3.min.js"></script>
<script src="js/d3.v3.min.js"></script>
<script src="js/d3.layout.cloud.js"></script>
<script src="sydjs-words.js"></script>
<script>
$(function() {
var fill = d3.scale.category10();
var draw = function(word_data, svg_el) {
svg_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; })
}
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;
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);
});
var svg = d3.select("#map").append("svg")
var g = [];
// var transform = d3.geo.transform({point: projectPoint}),
// path = d3.geo.path().projection(transform);
var fontScale = d3.scale.log().range([5, max_count * 3]);
wordsets.forEach(function(wordset, idx) {
// var word_centre = map.latLngToLayerPoint(new L.LatLng(data[idx].lat, data[idx].long));
g[idx] = svg.append("g")
.attr("transform", "translate(" + (450 * (idx + 0.5)) + " 320)");
layoutWords(wordset, idx, fontScale);
});
// Use Leaflet to implement a D3 geometric transformation.
// function projectPoint(x, y) {
// var point = map.latLngToLayerPoint(new L.LatLng(y, x));
// this.stream.point(point.x, point.y);
// }
});
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment