Built with blockbuilder.org
Last active
July 20, 2018 16:10
-
-
Save laxmikanta415/8206f8c1c970d316221612dcc72be722 to your computer and use it in GitHub Desktop.
Word Cloud VIsualisation
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
license: mit |
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
(function() { | |
if (typeof define === "function" && define.amd) define(["d3"], cloud); | |
else cloud(this.d3); | |
function cloud(d3) { | |
d3.layoutCloud = 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, | |
overflow = false, | |
random = Math.random, | |
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 = Date.now(); | |
while (Date.now() - start < timeInterval && ++i < n && timer) { | |
var d = data[i]; | |
d.x = (size[0] * (random() + .5)) >> 1; | |
d.y = (size[1] * (random() + .5)) >> 1; | |
cloudSprite(d, data, i); | |
if (d.hasText && place(board, d, bounds)) { | |
tags.push(d); | |
event.call('word', cloud, 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.call('end', cloud, tags, bounds); | |
} | |
} | |
} | |
cloud.stop = function() { | |
if (timer) { | |
clearInterval(timer); | |
timer = null; | |
} | |
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 = random() < .5 ? 1 : -1, | |
t = -dt, | |
dxdy, | |
dx, | |
dy; | |
while (dxdy = s(t += dt)) { | |
if (!isFinite(dxdy[0]) || !isFinite(dxdy[1])) { | |
break; // break on infinite values caused by too small perimeter | |
} | |
dx = ~~dxdy[0]; | |
dy = ~~dxdy[1]; | |
if (Math.min(Math.abs(dx), Math.abs(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]) { | |
if (!overflow) { | |
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.timeInterval = function(_) { | |
return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval; | |
}; | |
cloud.words = function(_) { | |
return arguments.length ? (words = _, cloud) : words; | |
}; | |
cloud.size = function(_) { | |
return arguments.length ? (size = [+_[0], +_[1]], cloud) : size; | |
}; | |
cloud.font = function(_) { | |
return arguments.length ? (font = functor(_), cloud) : font; | |
}; | |
cloud.fontStyle = function(_) { | |
return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle; | |
}; | |
cloud.fontWeight = function(_) { | |
return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight; | |
}; | |
cloud.rotate = function(_) { | |
return arguments.length ? (rotate = functor(_), cloud) : rotate; | |
}; | |
cloud.text = function(_) { | |
return arguments.length ? (text = functor(_), cloud) : text; | |
}; | |
cloud.spiral = function(_) { | |
return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral; | |
}; | |
cloud.fontSize = function(_) { | |
return arguments.length ? (fontSize = functor(_), cloud) : fontSize; | |
}; | |
cloud.padding = function(_) { | |
return arguments.length ? (padding = functor(_), cloud) : padding; | |
}; | |
cloud.random = function(_) { | |
return arguments.length ? (random = _, cloud) : random; | |
}; | |
cloud.overflow = function(_) { | |
return arguments.length ? (overflow = functor(_), cloud) : overflow; | |
}; | |
rebind(cloud, event, "on"); | |
return cloud; | |
}; | |
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]; | |
}; | |
} | |
// d3.functor replacement | |
function functor(v) { | |
return typeof v === 'function' ? v : function() { return v; }; | |
} | |
function rebind(target, source) { | |
var i = 1, n = arguments.length, method; | |
while (++i < n) target[method = arguments[i]] = d3_rebind(target, source, source[method]); | |
return target; | |
}; | |
function d3_rebind(target, source, method) { | |
return function() { | |
var value = method.apply(source, arguments); | |
return value === source ? target : value; | |
}; | |
} | |
// 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"; | |
} | |
})(); |
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> | |
<head> | |
<meta charset="utf-8"> | |
<script src="https://d3js.org/d3.v4.min.js"></script> | |
<script src="d3.layout.cloud.js"></script> | |
<style> | |
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } | |
</style> | |
</head> | |
<body> | |
<script> | |
var width = 700, height = 400, sizeAdjustment = 100,minFontSize = 12, largestWordSize = 0, smallestWordSize = 10000, terms = [], minSize=1000, maxSize=-1; | |
var svg = d3.select("body") | |
.append("svg") | |
.attr('class', 'cirrusGraph') | |
.attr("width", width) | |
.attr("height", height); | |
var vis = svg.append('g').attr('transform', `translate(${width/2},${height/2})`); | |
var layout = d3.layoutCloud() | |
.size([width, height]) | |
.overflow(true) | |
.padding(1) | |
.rotate(function(d) { return ~~(Math.random() * 2) * 90; }) | |
.spiral('archimedean') | |
.font('"Palatino Linotype", "Book Antiqua", Palatino, serif') | |
.fontSize(function(d) {return d.fontSize; }) | |
.text(function(d) { return d.text; }) | |
.on('end', draw) | |
var colorScale = d3.scaleOrdinal(d3.schemeCategory20c); | |
function draw(words, bounds){ | |
var scale = bounds ? Math.min( | |
width / Math.abs(bounds[1].x - width / 2), | |
width / Math.abs(bounds[0].x - width / 2), | |
height / Math.abs(bounds[1].y - height / 2), | |
height / Math.abs(bounds[0].y - height / 2) | |
) / 2 : 1; | |
var t = d3.transition().duration(1000); | |
var nodes = vis.selectAll('text') | |
.data(words, function(d) {return d.text;}); | |
nodes.exit().transition(t) | |
.style('font-size', '1px') | |
.remove(); | |
var colorScale = d3.scaleOrdinal(d3.schemeCategory10); | |
var nodesEnter = | |
nodes | |
.enter() | |
.append('text') | |
.text(function(d) { return d.text; }) | |
.attr('text-anchor', 'middle') | |
.attr('data-freq', function(d) { return d.rawFreq; }) | |
.attr('transform', function(d) { return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')'; }) | |
.style('font-family', '"Palatino Linotype", "Book Antiqua", Palatino, serif"') | |
.style('fill', function(d) { return colorScale(d.text); }) | |
.style('font-size', '1px'); | |
var nodesUpdate = nodes.merge(nodesEnter); | |
nodesUpdate.transition(t) | |
.style('font-family', '"Palatino Linotype", "Book Antiqua", Palatino, serif"' ) | |
.style('fill', function(d) { return colorScale(d.text); }) | |
.attr('transform', function(d) { return 'translate(' + [d.x, d.y] + ')rotate(' + d.rotate + ')'; }) | |
.style('font-size', function(d) { return d.fontSize + 'px'; }); | |
vis.transition(t).attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')scale(' + scale + ')'); | |
} | |
d3.json('terms.json', (err, data) => { | |
terms = data; | |
sketchWordCloud(); | |
}); | |
function sketchWordCloud(){ | |
setWordSizeExtent(); | |
setRelativeSizes(); | |
setAdjustedSizes(); | |
//console.log(terms); | |
layout.words(terms).start(); | |
} | |
function setWordSizeExtent(){ | |
var extents = d3.extent(terms, d => d.rawFreq); | |
minSize = extents[0]; | |
maxSize = extents[1]; | |
largestWordSize = maxSize; | |
smallestWordSize = minSize; | |
} | |
function setRelativeSizes() { | |
if (terms !== undefined) { | |
for (var i = 0; i < terms.length; i++) { | |
var word = terms[i]; | |
word.relativeSize = map(word.rawFreq,smallestWordSize , largestWordSize, 0.1, 1); | |
} | |
} | |
} | |
function map(value, istart, istop, ostart, ostop) { | |
return ostart + | |
(ostop - ostart) * | |
((value - istart) / (istop - istart)); | |
} | |
function setAdjustedSizes() { | |
calculateSizeAdjustment(); | |
if (terms !== undefined) { | |
for (var i = 0; i < terms.length; i++) { | |
var term = terms[i]; | |
var adjustedSize = findNewRelativeSize(term); | |
term.fontSize = adjustedSize > minFontSize ? adjustedSize : minFontSize; | |
} | |
} | |
} | |
function calculateSizeAdjustment() { | |
if (terms !== undefined) { | |
var stageArea = width * height; | |
(stageArea < 100000) ? minFontSize= 8 : minFontSize = 12; | |
var pixelsPerWord = stageArea / terms.length; | |
var totalWordsSize = 0; | |
for (var i = 0; i < terms.length; i++) { | |
var word = terms[i]; | |
var wordArea = calculateWordArea(word); | |
totalWordsSize += wordArea; | |
} | |
sizeAdjustment = (stageArea / totalWordsSize); | |
} | |
} | |
function calculateWordArea(word) { | |
var baseSize = Math.log(word.relativeSize * 10) * Math.LOG10E; | |
var height = (baseSize + word.relativeSize) / 2; | |
var width = 0; | |
for (var i = 0; i < word.text.length; i++ ) { | |
var letter = word.text.charAt(i); | |
if (letter == 'f' || letter == 'i' || letter == 'j' || letter == 'l' || letter == 'r' || letter == 't') width += baseSize / 3; | |
else if (letter == 'm' || letter == 'w') width += baseSize / (4 / 3); | |
else width += baseSize / 1.9; | |
} | |
var wordArea = height * width; | |
return wordArea; | |
} | |
function findNewRelativeSize(word) { | |
var areaMultiplier = sizeAdjustment | |
var area = calculateWordArea(word) * areaMultiplier; | |
var newRelativeSize = (Math.sqrt(6) * Math.sqrt(6 * Math.pow(word.text.length, 2) + area * word.text.length) - 6 * word.text.length) / (2 * word.text.length); | |
return newRelativeSize; | |
} | |
setTimeout(function(){ | |
var newTerms = [{ | |
"text": "revenues", | |
"rawFreq": 117 | |
}, | |
{ | |
"text": "statement", | |
"rawFreq": 115 | |
}, | |
{ | |
"text": "ra", | |
"rawFreq": 113 | |
}, | |
{ | |
"text": "01", | |
"rawFreq": 109 | |
}, | |
{ | |
"text": "annual", | |
"rawFreq": 108 | |
}]; | |
newTerms.forEach(item => { | |
terms.push(item); | |
}); | |
sketchWordCloud(); | |
},5000); | |
</script> | |
</body> |
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
[ | |
{ | |
"text": "town", | |
"rawFreq": 609 | |
}, | |
{ | |
"text": "en", | |
"rawFreq": 441 | |
}, | |
{ | |
"text": "financial", | |
"rawFreq": 407 | |
}, | |
{ | |
"text": "fund", | |
"rawFreq": 375 | |
}, | |
{ | |
"text": "al", | |
"rawFreq": 315 | |
}, | |
{ | |
"text": "er", | |
"rawFreq": 303 | |
}, | |
{ | |
"text": "es", | |
"rawFreq": 298 | |
}, | |
{ | |
"text": "year", | |
"rawFreq": 292 | |
}, | |
{ | |
"text": "bonds", | |
"rawFreq": 267 | |
}, | |
{ | |
"text": "rhode", | |
"rawFreq": 266 | |
}, | |
{ | |
"text": "statements", | |
"rawFreq": 258 | |
}, | |
{ | |
"text": "00", | |
"rawFreq": 250 | |
}, | |
{ | |
"text": "island", | |
"rawFreq": 243 | |
}, | |
{ | |
"text": "funds", | |
"rawFreq": 233 | |
}, | |
{ | |
"text": "general", | |
"rawFreq": 232 | |
}, | |
{ | |
"text": "nd", | |
"rawFreq": 219 | |
}, | |
{ | |
"text": "barrington", | |
"rawFreq": 196 | |
}, | |
{ | |
"text": "fiscal", | |
"rawFreq": 190 | |
}, | |
{ | |
"text": "net", | |
"rawFreq": 182 | |
}, | |
{ | |
"text": "total", | |
"rawFreq": 179 | |
}, | |
{ | |
"text": "june", | |
"rawFreq": 173 | |
}, | |
{ | |
"text": "ce", | |
"rawFreq": 169 | |
}, | |
{ | |
"text": "ue", | |
"rawFreq": 169 | |
}, | |
{ | |
"text": "state", | |
"rawFreq": 168 | |
}, | |
{ | |
"text": "th", | |
"rawFreq": 166 | |
}, | |
{ | |
"text": "la", | |
"rawFreq": 156 | |
}, | |
{ | |
"text": "ta", | |
"rawFreq": 156 | |
}, | |
{ | |
"text": "assets", | |
"rawFreq": 153 | |
}, | |
{ | |
"text": "governmental", | |
"rawFreq": 153 | |
}, | |
{ | |
"text": "years", | |
"rawFreq": 149 | |
}, | |
{ | |
"text": "ct", | |
"rawFreq": 147 | |
}, | |
{ | |
"text": "et", | |
"rawFreq": 146 | |
}, | |
{ | |
"text": "te", | |
"rawFreq": 145 | |
}, | |
{ | |
"text": "plan", | |
"rawFreq": 144 | |
}, | |
{ | |
"text": "information", | |
"rawFreq": 141 | |
}, | |
{ | |
"text": "om", | |
"rawFreq": 136 | |
}, | |
{ | |
"text": "school", | |
"rawFreq": 136 | |
}, | |
{ | |
"text": "debt", | |
"rawFreq": 133 | |
}, | |
{ | |
"text": "ed", | |
"rawFreq": 125 | |
}, | |
{ | |
"text": "expenditures", | |
"rawFreq": 125 | |
}, | |
{ | |
"text": "service", | |
"rawFreq": 121 | |
}, | |
{ | |
"text": "tax", | |
"rawFreq": 120 | |
}, | |
{ | |
"text": "io", | |
"rawFreq": 119 | |
}, | |
{ | |
"text": "se", | |
"rawFreq": 119 | |
}, | |
{ | |
"text": "government", | |
"rawFreq": 118 | |
} | |
] |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment