-
-
Save mg1075/8bb2b55d2c5cf5667b01 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(exports) { | |
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 = cloudPadding.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 (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); | |
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; | |
x += w; | |
} | |
var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data, | |
sprite = []; | |
while (--di >= 0) { | |
d = data[di]; | |
var w = d.width, | |
w32 = w >> 5, | |
h = d.y1 - d.y0, | |
p = d.padding; | |
// 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; | |
if (p) { | |
if (j) sprite[k - w32] |= m; | |
if (j < w - 1) sprite[k + w32] |= m; | |
m |= (m << 1) | (m >> 1); | |
} | |
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 { | |
// node-canvas support | |
var Canvas = require("canvas"); | |
canvas = new Canvas(cw << 5, ch); | |
} | |
var c = canvas.getContext("2d"), | |
spirals = { | |
archimedean: archimedeanSpiral, | |
rectangular: rectangularSpiral | |
}; | |
c.fillStyle = "red"; | |
c.textAlign = "center"; | |
exports.cloud = cloud; | |
})(typeof exports === "undefined" ? d3.layout || (d3.layout = {}) : exports); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>D3 CSV</title> | |
</head> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="d3.layout.cloud.js"></script> | |
<script type="text/javascript"> | |
var fill = d3.scale.category20(); | |
var cityData = [], | |
width = 500, | |
height = 500; | |
d3.csv("us-cities.csv", function(data) { | |
// build the list of city names | |
data.forEach( function (d) { | |
cityData.push(d.place); | |
}); | |
d3.layout.cloud().size([500, 500]) | |
.words(cityData.map(function(d) { | |
return {text: d, size: 10 + Math.random() * 90}; | |
})) | |
.rotate(function() { return ~~(Math.random() * 2) * 90; }) | |
.font("Impact") | |
.fontSize(function(d) { return d.size; }) | |
.on("end", draw) | |
.start(); | |
}); | |
function draw(words) { | |
d3.select("body").append("svg") | |
.attr("width", 500) | |
.attr("height", 500) | |
.append("g") | |
.attr("transform", "translate(150,150)") | |
.selectAll("text") | |
.data(words) | |
.enter().append("text") | |
.style("font-size", function(d) { return d.size + "px"; }) | |
.style("font-family", "Impact") | |
.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.text; }); | |
} | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rank | place | population | lat | lon | |
---|---|---|---|---|---|
1 | New York city | 8175133 | 40.71455 | -74.007124 | |
2 | Los Angeles city | 3792621 | 34.05349 | -118.245323 | |
3 | Chicago city | 2695598 | 45.37399 | -92.888759 | |
4 | Houston city | 2099451 | 41.337462 | -75.733627 | |
5 | Philadelphia city | 1526006 | 37.15477 | -94.486114 | |
6 | Phoenix city | 1445632 | 32.46764 | -85.000823 | |
7 | San Antonio city | 1327407 | 37.706576 | -122.440612 | |
8 | San Diego city | 1307402 | 37.707815 | -122.466624 | |
9 | Dallas city | 1197816 | 40.636 | -91.168309 | |
10 | San Jose city | 945942 | 41.209716 | -112.003047 | |
11 | Jacksonville city | 821784 | 30.2887 | -81.391794 | |
12 | Indianapolis city (balance) | 820445 | 39.766911 | -86.149963 | |
13 | Austin city | 790390 | 28.973405 | -95.961284 | |
14 | San Francisco city | 805235 | 28.371795 | -82.187909 | |
15 | Columbus city | 787033 | 41.258735 | -91.374424 | |
16 | Fort Worth city | 741206 | 38.0016 | -89.066334 | |
17 | Charlotte city | 731424 | 39.09931 | -76.817799 | |
18 | Detroit city | 713777 | 42.408871 | -83.002647 | |
19 | El Paso city | 649121 | 41.645415 | -91.599794 | |
20 | Memphis city | 646889 | 35.149681 | -90.04892 | |
21 | Boston city | 617594 | 41.202364 | -112.032315 | |
22 | Seattle city | 608660 | 45.520615 | -123.873929 | |
23 | Denver city | 600158 | 32.96438 | -102.829919 | |
24 | Baltimore city | 620961 | 39.284664 | -76.62022 | |
25 | Washington city | 601723 | 38.899101 | -77.028999 | |
26 | Nashville-Davidson metropolitan government (balance) | 601222 | 45.304048 | -121.756365 | |
27 | Louisville/Jefferson County metro government (balance) | 597337 | 45.304048 | -121.756365 | |
28 | Milwaukee city | 594833 | 43.041809 | -87.906837 | |
29 | Portland city | 583776 | 45.52186 | -123.882594 | |
30 | Oklahoma City city | 579999 | 39.05514 | -96.816104 | |
31 | Las Vegas city | 583756 | 40.4879 | -85.609999 | |
32 | Albuquerque city | 545852 | 35.084179 | -106.648643 | |
33 | Tucson city | 520116 | 41.644727 | -91.601947 | |
34 | Fresno city | 494665 | 38.645741 | -77.321863 | |
35 | Sacramento city | 466488 | 38.915291 | -121.594651 | |
36 | Long Beach city | 462257 | 29.748022 | -94.827603 | |
37 | Kansas City city | 459787 | 39.016682 | -96.864303 | |
38 | Mesa city | 439041 | 30.686452 | -97.700842 | |
39 | Virginia Beach city | 437994 | 36.767408 | -76.047707 | |
40 | Atlanta city | 420003 | 37.691375 | -122.454979 | |
41 | Colorado Springs city | 416427 | 40.17676 | -75.547839 | |
42 | Raleigh city | 403892 | 41.132609 | -73.977405 | |
43 | Omaha city | 408958 | 41.260689 | -95.94059 | |
44 | Miami city | 399457 | 41.63636 | -91.501889 | |
45 | Tulsa city | 391906 | 39.095215 | -121.613384 | |
46 | Oakland city | 390724 | 38.334108 | -87.345139 | |
47 | Cleveland city | 396815 | 36.640475 | -82.582569 | |
48 | Minneapolis city | 382578 | 44.979031 | -93.264931 | |
49 | Wichita city | 382368 | 37.686981 | -97.335579 | |
50 | Arlington city | 365438 | 41.29525 | -88.25278 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment