Last active
October 10, 2019 17:18
-
-
Save aaizemberg/bec828fd5d9209e4e888016a98f2fe51 to your computer and use it in GitHub Desktop.
nube de palabras - los temas del día
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(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g=(g.d3||(g.d3 = {}));g=(g.layout||(g.layout = {}));g.cloud = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | |
// Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/ | |
// Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf | |
var dispatch = require("d3-dispatch").dispatch; | |
var cloudRadians = Math.PI / 180, | |
cw = 1 << 11 >> 5, | |
ch = 1 << 11; | |
module.exports = function() { | |
var size = [256, 256], | |
text = cloudText, | |
font = cloudFont, | |
fontSize = cloudFontSize, | |
fontStyle = cloudFontNormal, | |
fontWeight = cloudFontNormal, | |
rotate = cloudRotate, | |
padding = cloudPadding, | |
spiral = archimedeanSpiral, | |
words = [], | |
timeInterval = Infinity, | |
event = dispatch("word", "end"), | |
timer = null, | |
random = Math.random, | |
cloud = {}, | |
canvas = cloudCanvas; | |
cloud.canvas = function(_) { | |
return arguments.length ? (canvas = functor(_), cloud) : canvas; | |
}; | |
cloud.start = function() { | |
var contextAndRatio = getContext(canvas()), | |
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(contextAndRatio, 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; | |
}; | |
function getContext(canvas) { | |
canvas.width = canvas.height = 1; | |
var ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2); | |
canvas.width = (cw << 5) / ratio; | |
canvas.height = ch / ratio; | |
var context = canvas.getContext("2d"); | |
context.fillStyle = context.strokeStyle = "red"; | |
context.textAlign = "center"; | |
return {context: context, ratio: ratio}; | |
} | |
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)) { | |
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]) 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.on = function() { | |
var value = event.on.apply(event, arguments); | |
return value === event ? cloud : value; | |
}; | |
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(contextAndRatio, d, data, di) { | |
if (d.sprite) return; | |
var c = contextAndRatio.context, | |
ratio = contextAndRatio.ratio; | |
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; | |
} | |
function cloudCanvas() { | |
return document.createElement("canvas"); | |
} | |
function functor(d) { | |
return typeof d === "function" ? d : function() { return d; }; | |
} | |
var spirals = { | |
archimedean: archimedeanSpiral, | |
rectangular: rectangularSpiral | |
}; | |
},{"d3-dispatch":2}],2:[function(require,module,exports){ | |
(function (global, factory) { | |
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : | |
typeof define === 'function' && define.amd ? define(['exports'], factory) : | |
factory((global.dispatch = {})); | |
}(this, function (exports) { 'use strict'; | |
function Dispatch(types) { | |
var i = -1, | |
n = types.length, | |
callbacksByType = {}, | |
callbackByName = {}, | |
type, | |
that = this; | |
that.on = function(type, callback) { | |
type = parseType(type); | |
// Return the current callback, if any. | |
if (arguments.length < 2) { | |
return (callback = callbackByName[type.name]) && callback.value; | |
} | |
// If a type was specified… | |
if (type.type) { | |
var callbacks = callbacksByType[type.type], | |
callback0 = callbackByName[type.name], | |
i; | |
// Remove the current callback, if any, using copy-on-remove. | |
if (callback0) { | |
callback0.value = null; | |
i = callbacks.indexOf(callback0); | |
callbacksByType[type.type] = callbacks = callbacks.slice(0, i).concat(callbacks.slice(i + 1)); | |
delete callbackByName[type.name]; | |
} | |
// Add the new callback, if any. | |
if (callback) { | |
callback = {value: callback}; | |
callbackByName[type.name] = callback; | |
callbacks.push(callback); | |
} | |
} | |
// Otherwise, if a null callback was specified, remove all callbacks with the given name. | |
else if (callback == null) { | |
for (var otherType in callbacksByType) { | |
if (callback = callbackByName[otherType + type.name]) { | |
callback.value = null; | |
var callbacks = callbacksByType[otherType], i = callbacks.indexOf(callback); | |
callbacksByType[otherType] = callbacks.slice(0, i).concat(callbacks.slice(i + 1)); | |
delete callbackByName[callback.name]; | |
} | |
} | |
} | |
return that; | |
}; | |
while (++i < n) { | |
type = types[i] + ""; | |
if (!type || (type in that)) throw new Error("illegal or duplicate type: " + type); | |
callbacksByType[type] = []; | |
that[type] = applier(type); | |
} | |
function parseType(type) { | |
var i = (type += "").indexOf("."), name = type; | |
if (i >= 0) type = type.slice(0, i); else name += "."; | |
if (type && !callbacksByType.hasOwnProperty(type)) throw new Error("unknown type: " + type); | |
return {type: type, name: name}; | |
} | |
function applier(type) { | |
return function() { | |
var callbacks = callbacksByType[type], // Defensive reference; copy-on-remove. | |
callback, | |
callbackValue, | |
i = -1, | |
n = callbacks.length; | |
while (++i < n) { | |
if (callbackValue = (callback = callbacks[i]).value) { | |
callbackValue.apply(this, arguments); | |
} | |
} | |
return that; | |
}; | |
} | |
} | |
function dispatch() { | |
return new Dispatch(arguments); | |
} | |
dispatch.prototype = Dispatch.prototype; // allow instanceof | |
exports.dispatch = dispatch; | |
})); | |
},{}]},{},[1])(1) | |
}); |
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> | |
<head> | |
<meta charset="utf-8"> | |
<meta name="viewport" content="width=device-width"> | |
<title>nube de palabras</title> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.2.3/d3.min.js"></script> | |
<script src="https://cdn.rawgit.com/jasondavies/d3-cloud/v1.2.1/build/d3.layout.cloud.js"></script> | |
<link href="https://fonts.googleapis.com/css?family=Montserrat:500" rel="stylesheet"> | |
<style> | |
a:link { | |
font-weight: normal; | |
text-decoration: none; | |
} | |
a:hover, a:focus { | |
font-weight: bold; | |
text-decoration: underline; | |
} | |
</style> | |
</head> | |
<body> | |
<script> | |
var colores = d3.schemeCategory20; | |
function fill(i, vector) { | |
return vector[i % vector.length]; | |
} | |
var scale = d3.scaleLinear(); | |
var today = new Date(); | |
var dd = today.getDate(); | |
var mm = today.getMonth()+1; | |
var yyyy = today.getFullYear(); | |
if (dd < 10) { dd = '0'+dd } | |
if (mm < 10) { mm = '0'+mm } | |
today = yyyy + "-" + mm + "-" + dd; // today en formato 'YYYY-MM-DD' | |
var width = 750, height = 500; | |
var limit = 200; // cantidad de palabras a recuperar | |
var d_from = today; // fecha desde formato 'yyyy-mm-dd' | |
var d_to = today; // fecha hasta formato 'yyyy-mm-dd' | |
var search = ""; | |
var url = "http://pf2.it.itba.edu.ar/api/nube-de-palabras?d_from="+d_from+"&d_to="+d_to+"&words="+search+"&limit="+limit; | |
var font = "Montserrat"; | |
var datos; | |
function hex2a(hexx) { | |
var hex = hexx.toString(); | |
var str = ''; | |
for (var i = 0; i < hex.length; i += 2) str += String.fromCharCode(parseInt(hex.substr(i, 2), 16)); | |
return str; | |
} | |
function draw(words) { | |
d3.select("body").append("svg") | |
.attr("width", width ) | |
.attr("height", height ) | |
.append("g") | |
.attr("transform", "translate(" + width/2 + "," + height/2 + ")") | |
.selectAll("text") | |
.data(words) | |
.enter() | |
.append("a") | |
.attr("xlink:href", function(d) { return ( d.text==hex2a('6d61637269') ? "//goo.gl/dzsVGa" : "//news.google.com/news/search/section/q/" + d.text + "/" + d.text + "?hl=es-419&gl=AR&ned=es_ar" ); }) | |
.attr("target", "_blank") | |
.append("text") | |
.style("font-size", function(d) { return d.size + "px"; }) | |
.style("font-family", font) | |
.style("fill", function(d, i) { return fill(i,colores); }) | |
.attr("text-anchor", "middle") | |
.attr("transform", function(d) { | |
return "translate(" + [d.x, d.y] + ")rotate(" + d.rotate + ")"; | |
}) | |
.text(function(d) { return d.text; }); | |
} | |
var layout; | |
d3.json(url, function(data) { | |
if (data.status == "success") { | |
datos = data.data; | |
datos.forEach(function(d) { | |
d.text = d.word; | |
d.size = d.word_count; | |
}); | |
scale | |
.domain([1, d3.max(datos.map(function(d){return d.size;})) ]) | |
.range([4,50]); | |
layout = d3.layout.cloud() | |
.size( [width, height] ) | |
.words(datos) | |
.padding(1) | |
.rotate(function() { return ~~(Math.random() * 2) * 90; }) | |
.font( font ) | |
.fontSize(function(d) { return scale(d.word_count); }) | |
.on("end", draw); | |
layout.start(); | |
} | |
}); | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment